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>
This commit is contained in:
2026-05-14 22:02:53 -04:00
parent 3358ec86ad
commit 9829d1af37
17 changed files with 82 additions and 45 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

+34 -20
View File
@@ -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)
+41 -18
View File
@@ -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)
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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 ───────────────────────────────────────────────────
+2 -2
View File
@@ -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) {