feat(brand): AP SSID WeVisto-XXXX + logo placeholder + 4-step copy
Rename the AP broadcast SSID from PictureFrame-XXXX to WeVisto-XXXX
(operation.h:ap_ssid_from_mac + main.cpp:enter_provisioning). Tests
updated to match.
Setup screens (both panels):
- Top-right header chip replaced with a draw_logo_placeholder() box —
a 'WeVisto' text mark with a 'PLACEHOLDER' subtitle. When the real
brand asset lands, swap the function for a paste of the file at the
same coordinates; no layout change needed.
- Step list rewritten to Matt's spec (4 steps, not 5):
1. Turn on your WeVisto
2. Unlock your phone
3. Scan QR 1 — This will connect your phone to the WeVisto
4. Scan QR 2 — This will open the WeVisto setup page
Step 5 (type WiFi password) lived only in the on-panel guide; the
user does that on the phone via the captive portal, where the
prompt is already explicit.
- Regenerated both panels' setup_bg / ap_bg / ap_bg_retry assets via
the gen_screens scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
@@ -111,6 +111,32 @@ def leave_qr_white(draw, qr_x, qr_y, qr_px):
|
|||||||
"""Blank the QR overlay region so firmware can write the real QR."""
|
"""Blank the QR overlay region so firmware can write the real QR."""
|
||||||
draw.rectangle([qr_x, qr_y, qr_x+qr_px-1, qr_y+qr_px-1], fill=WH)
|
draw.rectangle([qr_x, qr_y, qr_x+qr_px-1, qr_y+qr_px-1], fill=WH)
|
||||||
|
|
||||||
|
# ── Logo placeholder ─────────────────────────────────────────────────────────
|
||||||
|
# Top-right brand placeholder for both setup screens. When the real logo
|
||||||
|
# lands, replace draw_logo_placeholder() with a paste of assets/logo.png
|
||||||
|
# (or similar) at the same coordinates so the layout stays stable.
|
||||||
|
LOGO_W = 200
|
||||||
|
LOGO_H = 40
|
||||||
|
LOGO_X = 800 - LOGO_W - 16
|
||||||
|
LOGO_Y = (52 - LOGO_H) // 2 # BAR_H=52
|
||||||
|
|
||||||
|
def draw_logo_placeholder(img):
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1], fill=WH)
|
||||||
|
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1],
|
||||||
|
outline=BK, width=2)
|
||||||
|
f_logo = ttf("DejaVuSans-Bold.ttf", 18)
|
||||||
|
bb = draw.textbbox((0, 0), "WeVisto", font=f_logo)
|
||||||
|
tw = bb[2] - bb[0]
|
||||||
|
draw.text((LOGO_X + (LOGO_W - tw) // 2, LOGO_Y + 4), "WeVisto",
|
||||||
|
font=f_logo, fill=BK)
|
||||||
|
f_hint = ttf("DejaVuSans-Bold.ttf", 8)
|
||||||
|
bb = draw.textbbox((0, 0), "PLACEHOLDER", font=f_hint)
|
||||||
|
pw = bb[2] - bb[0]
|
||||||
|
draw.text((LOGO_X + (LOGO_W - pw) // 2, LOGO_Y + LOGO_H - 12),
|
||||||
|
"PLACEHOLDER", font=f_hint, fill=BK)
|
||||||
|
|
||||||
|
|
||||||
def text_center(draw, cx, y, text, font, fill):
|
def text_center(draw, cx, y, text, font, fill):
|
||||||
bb = draw.textbbox((0,0), text, font=font)
|
bb = draw.textbbox((0,0), text, font=font)
|
||||||
tw = bb[2]-bb[0]
|
tw = bb[2]-bb[0]
|
||||||
@@ -200,14 +226,8 @@ def gen_ap(accent=YL, header="SETUP MODE — STEP 1 OF 2", qr_label="SCAN TO C
|
|||||||
draw.rectangle([0, 0, W-1, BAR_H-1], fill=accent)
|
draw.rectangle([0, 0, W-1, BAR_H-1], fill=accent)
|
||||||
draw.text((24, 18), header, font=F_BAR, fill=BK)
|
draw.text((24, 18), header, font=F_BAR, fill=BK)
|
||||||
|
|
||||||
# Right chip: black box with device SSID
|
# Logo placeholder top-right (replaces per-device SSID chip)
|
||||||
chip_x, chip_y = 498, 11
|
draw_logo_placeholder(img)
|
||||||
chip_text = "PictureFrame-91F8"
|
|
||||||
bb = draw.textbbox((0,0), chip_text, font=F_CHIP)
|
|
||||||
chip_w = bb[2]-bb[0] + 22
|
|
||||||
chip_x2 = chip_x + chip_w
|
|
||||||
draw.rectangle([chip_x, chip_y, chip_x2, BAR_H-12], fill=BK)
|
|
||||||
draw.text((chip_x+11, chip_y+7), chip_text, font=F_CHIP, fill=accent)
|
|
||||||
|
|
||||||
# ── Panel dividers ────────────────────────────────────────────
|
# ── Panel dividers ────────────────────────────────────────────
|
||||||
draw.rectangle([DIV1_X, BODY_Y, DIV1_X+1, H-1], fill=BK)
|
draw.rectangle([DIV1_X, BODY_Y, DIV1_X+1, H-1], fill=BK)
|
||||||
@@ -230,11 +250,10 @@ def gen_ap(accent=YL, header="SETUP MODE — STEP 1 OF 2", qr_label="SCAN TO C
|
|||||||
# NEW WORDING 2026-05-09 — beta tester called the prior copy
|
# NEW WORDING 2026-05-09 — beta tester called the prior copy
|
||||||
# "Chinglish." Tighter, plainer, no Safari-specific reference.
|
# "Chinglish." Tighter, plainer, no Safari-specific reference.
|
||||||
steps = [
|
steps = [
|
||||||
("Plug in the frame", ""),
|
("Turn on your WeVisto", ""),
|
||||||
("Unlock your phone", ""),
|
("Unlock your phone", ""),
|
||||||
("Scan QR 1", "joins your phone to PictureFrame"),
|
("Scan QR 1", "This will connect your phone to the WeVisto"),
|
||||||
("Scan QR 2", "opens the setup page"),
|
("Scan QR 2", "This will open the WeVisto setup page"),
|
||||||
("Type your home WiFi password", "and tap Connect"),
|
|
||||||
]
|
]
|
||||||
sy = BODY_Y + 95
|
sy = BODY_Y + 95
|
||||||
step_pitch = 32
|
step_pitch = 32
|
||||||
@@ -345,13 +364,8 @@ def gen_setup():
|
|||||||
draw.rectangle([bx + i*8, BAR_H//2 - bh//2, bx+i*8+5, BAR_H//2 + bh//2], fill=WH)
|
draw.rectangle([bx + i*8, BAR_H//2 - bh//2, bx+i*8+5, BAR_H//2 + bh//2], fill=WH)
|
||||||
draw.text((bx+38, 18), "WIFI CONNECTED — STEP 2 OF 2", font=F_BAR, fill=WH)
|
draw.text((bx+38, 18), "WIFI CONNECTED — STEP 2 OF 2", font=F_BAR, fill=WH)
|
||||||
|
|
||||||
# Right IP chip
|
# Logo placeholder top-right (replaces IP chip)
|
||||||
ip_text = "192.168.x.x"
|
draw_logo_placeholder(img)
|
||||||
bb = draw.textbbox((0,0), ip_text, font=F_CHIP)
|
|
||||||
chip_w = bb[2]-bb[0] + 22
|
|
||||||
chip_x = W - chip_w - 20
|
|
||||||
draw.rectangle([chip_x, 11, chip_x+chip_w, BAR_H-12], fill=WH)
|
|
||||||
draw.text((chip_x+11, 18), ip_text, font=F_CHIP, fill=GR)
|
|
||||||
|
|
||||||
# ── Panel dividers ────────────────────────────────────────────
|
# ── Panel dividers ────────────────────────────────────────────
|
||||||
draw.rectangle([DIV1_X, BODY_Y, DIV1_X+1, H-1], fill=BK)
|
draw.rectangle([DIV1_X, BODY_Y, DIV1_X+1, H-1], fill=BK)
|
||||||
|
|||||||
@@ -120,17 +120,42 @@ def draw_qr_frame(draw, qx, qy, qp, accent):
|
|||||||
draw.rectangle([qx - 4, qy - 4, qx + qp + 3, qy + qp + 3], outline=BK, width=4)
|
draw.rectangle([qx - 4, qy - 4, qx + qp + 3, qy + qp + 3], outline=BK, width=4)
|
||||||
|
|
||||||
|
|
||||||
def draw_header(draw, accent, header_text, ssid_text):
|
def draw_header(draw, accent, header_text):
|
||||||
"""Yellow/red band along the top with a section title + SSID chip."""
|
"""Coloured band along the top with section title on the left. Logo
|
||||||
|
placeholder is rendered separately by draw_logo_placeholder() so the
|
||||||
|
brand mark sits in the top right of every setup screen."""
|
||||||
draw.rectangle([0, 0, W - 1, HEADER_H - 1], fill=accent)
|
draw.rectangle([0, 0, W - 1, HEADER_H - 1], fill=accent)
|
||||||
draw.text((LEFT_PAD, 40), header_text, font=F_BAR, fill=BK)
|
draw.text((LEFT_PAD, 40), header_text, font=F_BAR, fill=BK)
|
||||||
|
|
||||||
if ssid_text:
|
|
||||||
bb = draw.textbbox((0, 0), ssid_text, font=F_CHIP)
|
# ── Logo placeholder ─────────────────────────────────────────────────────────
|
||||||
chip_w = bb[2] - bb[0] + 36
|
# Box dimensions for the top-right placeholder. When the real brand mark
|
||||||
chip_x = W - chip_w - LEFT_PAD
|
# lands, replace draw_logo_placeholder() with a paste of assets/logo.png
|
||||||
draw.rectangle([chip_x, 25, chip_x + chip_w, HEADER_H - 25], fill=BK)
|
# (or similar) at these same coordinates so the layout stays stable.
|
||||||
draw.text((chip_x + 18, 38), ssid_text, font=F_CHIP, fill=accent)
|
LOGO_W = 300
|
||||||
|
LOGO_H = 92
|
||||||
|
LOGO_X = W - LOGO_W - LEFT_PAD # 864
|
||||||
|
LOGO_Y = (HEADER_H - LOGO_H) // 2 # 19
|
||||||
|
|
||||||
|
def draw_logo_placeholder(img):
|
||||||
|
"""White rounded-look box with 'WeVisto' brand text in the top right of
|
||||||
|
the header. Stand-in for the real logo — same position will be reused."""
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1], fill=WH)
|
||||||
|
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1],
|
||||||
|
outline=BK, width=4)
|
||||||
|
|
||||||
|
f_logo = ttf("DejaVuSans-Bold.ttf", 44)
|
||||||
|
bb = draw.textbbox((0, 0), "WeVisto", font=f_logo)
|
||||||
|
tw = bb[2] - bb[0]
|
||||||
|
draw.text((LOGO_X + (LOGO_W - tw) // 2, LOGO_Y + 12), "WeVisto",
|
||||||
|
font=f_logo, fill=BK)
|
||||||
|
|
||||||
|
f_hint = ttf("DejaVuSans-Bold.ttf", 14)
|
||||||
|
bb = draw.textbbox((0, 0), "PLACEHOLDER", font=f_hint)
|
||||||
|
pw = bb[2] - bb[0]
|
||||||
|
draw.text((LOGO_X + (LOGO_W - pw) // 2, LOGO_Y + LOGO_H - 22),
|
||||||
|
"PLACEHOLDER", font=f_hint, fill=BK)
|
||||||
|
|
||||||
|
|
||||||
def draw_divider(draw):
|
def draw_divider(draw):
|
||||||
@@ -256,10 +281,8 @@ def gen_ap(accent=YL,
|
|||||||
img = Image.new("RGB", (W, H), WH)
|
img = Image.new("RGB", (W, H), WH)
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
# Universal brand chip — firmware doesn't write text into static .bin
|
draw_header(draw, accent, header_text)
|
||||||
# assets, so leaving a per-device SSID placeholder here would lie on
|
draw_logo_placeholder(img)
|
||||||
# every other unit. Same image for every frame.
|
|
||||||
draw_header(draw, accent, header_text, "PICTUREFRAME")
|
|
||||||
draw_divider(draw)
|
draw_divider(draw)
|
||||||
|
|
||||||
# ── LEFT COLUMN ──────────────────────────────────────────────────────────
|
# ── LEFT COLUMN ──────────────────────────────────────────────────────────
|
||||||
@@ -275,11 +298,10 @@ def gen_ap(accent=YL,
|
|||||||
# 5 numbered steps — same instructions as the 7.3" but with the larger
|
# 5 numbered steps — same instructions as the 7.3" but with the larger
|
||||||
# type that fits comfortably on a 13.3" portrait body.
|
# type that fits comfortably on a 13.3" portrait body.
|
||||||
steps = [
|
steps = [
|
||||||
("Plug in the frame", ""),
|
("Turn on your WeVisto", ""),
|
||||||
("Unlock your phone", ""),
|
("Unlock your phone", ""),
|
||||||
("Scan QR 1", "joins your phone to PictureFrame"),
|
("Scan QR 1", "This will connect your phone to the WeVisto"),
|
||||||
("Scan QR 2", "opens the setup page"),
|
("Scan QR 2", "This will open the WeVisto setup page"),
|
||||||
("Type your home WiFi", "password and tap Connect"),
|
|
||||||
]
|
]
|
||||||
step_y0 = head_y + 240
|
step_y0 = head_y + 240
|
||||||
step_pitch = 92
|
step_pitch = 92
|
||||||
@@ -375,7 +397,8 @@ def gen_setup():
|
|||||||
img = Image.new("RGB", (W, H), WH)
|
img = Image.new("RGB", (W, H), WH)
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
draw_header(draw, GR, "WIFI CONNECTED — STEP 2 OF 2", "192.168.x.x")
|
draw_header(draw, GR, "WIFI CONNECTED — STEP 2 OF 2")
|
||||||
|
draw_logo_placeholder(img)
|
||||||
|
|
||||||
# Centered heading
|
# Centered heading
|
||||||
text_center(draw, W // 2, BODY_Y + 30, "Almost ready", F_HEAD, BK)
|
text_center(draw, W // 2, BODY_Y + 30, "Almost ready", F_HEAD, BK)
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ static void enter_provisioning(const String& mac, bool retry = false) {
|
|||||||
suffix.replace(":", "");
|
suffix.replace(":", "");
|
||||||
suffix = suffix.substring(suffix.length() - 4);
|
suffix = suffix.substring(suffix.length() - 4);
|
||||||
suffix.toUpperCase();
|
suffix.toUpperCase();
|
||||||
String apSsid = "PictureFrame-" + suffix;
|
String apSsid = "WeVisto-" + suffix;
|
||||||
|
|
||||||
Serial.println(retry ? "AP (retry): " + apSsid : "AP: " + apSsid);
|
Serial.println(retry ? "AP (retry): " + apSsid : "AP: " + apSsid);
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ inline String ap_ssid_from_mac(const String& mac) {
|
|||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
std::string suffix = cleaned.substr(cleaned.size() - 4);
|
std::string suffix = cleaned.substr(cleaned.size() - 4);
|
||||||
return String(("PictureFrame-" + suffix).c_str());
|
return String(("WeVisto-" + suffix).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── WiFi connection attempt ───────────────────────────────────────────────────
|
// ── WiFi connection attempt ───────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -646,12 +646,12 @@ void test_fw_draw_pending_header_absent_when_no_draw_needed() {
|
|||||||
// FW-12/13: AP SSID derivation via ap_ssid_from_mac()
|
// FW-12/13: AP SSID derivation via ap_ssid_from_mac()
|
||||||
void test_fw12_ap_ssid_from_mac_aabbcc() {
|
void test_fw12_ap_ssid_from_mac_aabbcc() {
|
||||||
String ssid = ap_ssid_from_mac(String("AA:BB:CC:DD:EE:FF"));
|
String ssid = ap_ssid_from_mac(String("AA:BB:CC:DD:EE:FF"));
|
||||||
TEST_ASSERT_EQUAL_STRING("PictureFrame-EEFF", ssid.c_str());
|
TEST_ASSERT_EQUAL_STRING("WeVisto-EEFF", ssid.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_fw13_ap_ssid_from_real_mac() {
|
void test_fw13_ap_ssid_from_real_mac() {
|
||||||
String ssid = ap_ssid_from_mac(String("1C:C3:AB:D1:91:F8"));
|
String ssid = ap_ssid_from_mac(String("1C:C3:AB:D1:91:F8"));
|
||||||
TEST_ASSERT_EQUAL_STRING("PictureFrame-91F8", ssid.c_str());
|
TEST_ASSERT_EQUAL_STRING("WeVisto-91F8", ssid.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
|||||||