| Age | Commit message (Collapse) | Author | Files | Lines |
|
- Seed viewport_name with the full base MAC, colons stripped
(e8:f6:0a:e0:90:94 → "e8f60ae09094"). Stable across reboots, globally
unique, 12 alphanumeric chars — well inside the mDNS hostname limit
even with the "viewport-" prefix.
- Add a mac_str field to viewport_state and expose it as the new "mac"
key in GET /state and as a "mac" line on the info screen.
- "Configured" no longer requires a viewport_name (it always has the
MAC default); the scrypted URL alone gates outbound POSTs and the
loading-vs-info screen choice.
- Strip viewport_name[0] fallbacks in http_api / mdns / local_screens
now that the field is never empty.
- Replace the wake-on-boot-stays-on behaviour with a ~600 ms flash
followed by sleep — long enough for the FT5426 touch IC to come out
of its initial unresponsive state, short enough that an idle device
doesn't burn the backlight.
- Drop the touch defensive cruft added when we were chasing a PoE-power
theory: the DEV_MODE=0x00 write at init, the touch_reset_pulse() call
at init, and the runtime wedge-self-heal in the polling loop didn't
actually fix anything — the wake-on-boot flash did. Also delete the
display_touch_reset_pulse() helper since it now has zero callers.
|
|
After a long bench session of taps + power cycles, the FT5426 on this
unit started returning dev_mode=0xff on every read. The polling loop
interpreted that as 15 simultaneous touches forever (td_status bits
0..3 all set), wedged `was_down=true` permanently, and no tap or
long-press ever fired — the screen looked dead.
Two layers of defence in touch_init:
1. retry the dev_mode read up to 10 × 30 ms in case the chip is just
slow to come up after PC_RST_TP_N is released.
2. if still stuck, pulse PC_RST_TP_N via a new
display_touch_reset_pulse() helper (drop reset for 20 ms then
release + 50 ms settle), then retry the read another 10 × 30 ms.
Plus a polling-loop sanity check: an FT5x06 supports max 5 simultaneous
points, so a TD_STATUS lower-nibble value > 5 is bogus and the poll
cycle is skipped. That keeps a wedged chip from producing phantom
"15 touches" reports if it slips into 0xff during runtime.
If both attempts still report 0xff, we log loudly and let the polling
task run — it harmlessly drops cycles via the >5 sanity check. Wake
still works via POST /state from Scrypted; this only disables touch
gestures on a hardware-stuck boot.
|
|
Code-review pass after the M5 colour fix. -126 net lines (-215 / +89).
Dead code removed:
- display_fill, display_test_pattern — M3 bring-up self-tests, no callers
- net_eth_is_up — no callers
- nvs_config_reset — no callers (factory-reset gesture was removed earlier;
NVS wipe now goes through `idf.py erase-flash`)
Helpers inlined or collapsed (each had one call site):
- state_name / orientation_name — ternaries at the cJSON site
- fmt_bytes / fmt_uptime — inline scope in local_screens_show_info
- expected_dims (http_api) + effective_dims (local_screens) — merged into
viewport_state_effective_dims() in viewport_state, one canonical source
state_machine cleanup:
- arm_idle_timer_unlocked / disarm_idle_timer collapsed to one arm_idle_timer(ms)
that takes the snapshotted timeout (ms==0 disables). Removes the misleading
name and the second viewport_state lock acquisition per painted frame.
- state_machine_set + state_machine_frame_painted now snapshot state + idle_ms
under one lock.
mdns_service cleanup:
- snapshot_state / apply_hostname / apply_txt collapsed into a single
apply_state(include_hostname) helper; mdns_service_start grabs hostname
once for hostname_set + log instead of going under the lock three times.
http_api cleanup:
- POST /config brightness-changed path uses the local `bright` it already
validated instead of re-locking + re-reading st->brightness.
- Trimmed verbose bring-up comments (stack-size justification, M5 saga,
DSI shadow struct) to one line each — kept the load-bearing facts.
Smoke-tested on hardware: boot clean, display + JPEG + touch all up
([DJT]), info screen renders, no crashes.
|
|
Two channel-swap bugs were stacked, hiding each other on symmetric
pixels (white text on black background) and showing up only on
saturated colour JPEGs:
1. jpeg_decoder: rgb_order was JPEG_DEC_RGB_ELEMENT_ORDER_RGB which
empirically emits the RGB565 word in big-endian byte order (the
IDF header comment "color component in big endian" is correct;
the enum's "RGB" name is misleading). Our painter reads native
LE uint16_t, so the bytes were always swapped. Flipped to
_BGR which emits LE — verified by dumping decoded bytes for
solid red/green/blue JPEGs.
2. display: rgb565_to_rgb888 wrote bytes [R, G, B], but the
ESP32-P4 DSI engine + TC358762 + Pi panel expect [B, G, R]
in memory despite the "RGB888" label. (MIPI DSI defines bit
order, not byte position in memory.) Verified with an
embedded solid-blue test JPEG: with [R, G, B] the panel
showed solid red; with [B, G, R] it shows solid blue.
Diagnosed by embedding a 480x800 blue test JPEG in firmware, decoding
+ dumping the buffer at boot, and reading the actual bytes over USB
serial (ethernet was disconnected mid-investigation). The temporary
self-test + debug dump are removed; the fixes themselves are tiny.
|
|
The Hosyond 5" panel uses Pi 7" v1.1 architecture (ATTINY ID 0xC3 +
TC358762 DSI-to-DPI bridge). Getting it to paint needed three things
that ESP-IDF doesn't expose:
1. TC358762 bridge configuration — 16 register writes via DSI Generic
Long Write (DT=0x29) packets, transcribed from Linux's tc358762.c.
2. Non-burst video mode — IDF hardcodes BURST_WITH_SYNC_PULSES, but
TC358762 requires NON_BURST_WITH_SYNC_PULSES. Overridden via the
mipi_dsi_host_ll_* API after esp_lcd_new_panel_dpi() and before
panel_init().
3. ATTINY v1.1 power-on sequence + SPI-proxy bridge wake. The legacy
REG_POWERON(0x85) path doesn't apply to 0xC3 firmware; instead
write PORTC/PORTA/PORTB/PORTC in order, then later release bridge
reset and proxy-write TC358762 SYSPMCTRL=0 through the ATTINY's
ADDR_H/L + WR_DATA_H/L registers.
Reaching the LL/HAL APIs requires shadowing esp_lcd_dsi_bus_t so we can
pick the mipi_dsi_hal_context_t out of the private struct. Documented
the layout dependency at the shadow definition.
Tuned config (observed stable on ESP32-P4 per embenix's reference):
- 1 data lane @ 600 Mbps
- DPI 26 MHz (Linux modeline is 25.98)
- timings HSW=2 HBP=46 HFP=210 / VSW=20 VBP=4 VFP=22
- RGB888 end-to-end, R,G,B byte order (BGR was wrong)
- disable_lp=0 so LP windows are available for the bridge writes
|
|
DSI / panel:
- LDO_VO3 acquired at 2500 mV (VDD_MIPI_DPHY) — without this the PHY
PLL busy-waits forever inside esp_lcd_new_dsi_bus.
- PSRAM bumped to 200 MHz (CONFIG_IDF_EXPERIMENTAL_FEATURES) to keep
the DPI fed without underruns.
- Pi-7"-style 800x480 panel init (REG_POWERON, PORTA/PORTB/PWM/PORTC)
+ 16-bit RGB565 framebuffer with portrait-mode rotation buffer in
PSRAM. Currently shows garbled output — known issue, next commit
switches to the TC358762-bridge-aware init sequence.
State + UX:
- viewport_state simplified to AWAKE/ASLEEP only; "unconfigured" is
a flag, not a state. Tap always toggles; content choice (identity
vs frame) is driven by the configured flag.
- BOOT button arms a 15 s identity overlay via local_screens_overlay;
expired callback returns to prior state.
- Boot-done indicator: two backlight flashes (no usable LED GPIO on
the Waveshare board — GPIO 35 BOOT is shared with EMAC TXD1).
- Best-effort subsystem init in app_main; display init deferred to
its own task so a panel hang can't block networking.
Display test pattern: solid R/G/B for 2 s each on boot to characterize
the garble independent of text rendering.
|
|
state_client.{h,c}:
- Worker task drains a depth-1 queue (xQueueOverwrite gives the
replace-on-full semantics from the spec — in-flight POST is never
cancelled; the next queued entry is overwritten by newer state).
- esp_http_client POST to <scrypted>/state with Content-Type application/
json, User-Agent ScryptedViewport/<version>, Connection: close, 1s
timeout. Body: {"viewport":"<name>","state":"wake"|"sleep"}.
- Any non-2xx or transport error increments state_post_failures and is
otherwise ignored. Silently drops if no Scrypted URL is configured.
touch.{h,c}:
- FT5426 capacitive touch on the shared I2C bus at 0x38.
- 30ms polling task; tracks down/up transitions, detects taps as
down-then-up within 500ms with a 150ms debounce.
- On tap, toggles wake/sleep via state_machine_set_local(), which drives
the local transition AND fires state_client_post() at Scrypted.
state_machine adds state_machine_set_local(): runs the same transition
as state_machine_set() then enqueues an outbound POST. Idle-timer
expiry now uses this path so Scrypted sees idle-driven sleeps too
(the spec's "tighten the race with /frame 409" path stays in place as
the fallback).
display.h exposes display_i2c_bus(); touch.c uses it instead of
re-initializing the same I2C port.
app_main starts state_client right after the state machine and starts
touch after display init (touch is skipped if display isn't up since
they share the bus).
CMakeLists.txt: add esp_http_client to REQUIRES.
Build clean against ESP-IDF 5.4 (binary ~860 KB; jumped ~210 KB from
M6 because esp_http_client + cJSON path pulls in tcp_transport, mbedtls,
http_parser).
TESTING.md M7: flask-based test receiver, tap dispatch verification,
failure-path check (kill receiver, confirm counter increments),
queue-coalescing behavior, no-POST-on-Scrypted-initiated, no-POST-
when-unconfigured.
|
|
jpeg_decoder.{h,c} wraps esp_driver_jpeg (ESP32-P4 hardware decoder).
- One-time engine + DMA-aligned PSRAM scratch buffer setup (1 MB input,
768 KB output @ panel native 800x480 RGB565).
- try_lock + unlock so concurrent /frame POSTs get 503 instead of
queueing, per spec.
- jpeg_decoder_get_info() reports the dimensions; the http handler
validates them against the effective resolution before painting.
display.h adds display_present_rgb565(src, w, h):
- Landscape: src is 800x480, memcpy 1:1 into the panel framebuffer.
- Portrait: src is 480x800, software rotate 90° CW into the 800x480
panel framebuffer. (PPA / 2D-DMA hardware rotation is a later
optimization if portrait latency matters.)
http_api.c adds POST /frame:
- Content-Type must be image/jpeg → else 400.
- Empty body → 400. > 1 MB → 413. Display not initialized → 500.
- jpeg_decoder_try_lock(0) for concurrency: second post returns 503.
- Body streamed into the decoder's input buffer in chunks.
- Decode failure or dimension mismatch → 400 + decode_errors++.
- Paint failure → 500.
- Success → frames_received++, last_frame_us = esp_timer_get_time(),
204 No Content.
app_main initializes the JPEG decoder after display_init(). Both are
best-effort: failures log a warning and leave the rest of the firmware
running.
CMakeLists.txt: add esp_driver_jpeg to REQUIRES.
Known gap (M6 closes it): /frame currently paints regardless of
wake/sleep state. The 409-when-asleep rule lands with POST /state
in M6.
Build clean against ESP-IDF 5.4 (binary ~640 KB).
TESTING.md M5 expanded with portrait/landscape test commands,
ImageMagick test-image recipes, the full negative matrix (wrong
Content-Type, oversize, wrong dims, concurrent, garbage), and the
M6 dependency note.
|
|
display.{h,c} brings up the Hosyond 5\" 800x480 panel via the Raspberry
Pi 7\" touchscreen architecture:
- I2C init to the panel-side MCU at 0x45 (Pi 7\" register map ported
from drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c).
- POWERON write + 120ms settle.
- ESP32-P4 MIPI-DSI bring-up in DPI video mode: 2-lane, 480 Mbps,
800x480 @ 60Hz, RGB565, canonical Pi 7\" timings (HSW=18/HBP=20/HFP=62,
VSW=4/VBP=27/VFP=18, 30 MHz pixel clock).
- Gamma-corrected (^2.2) PWM brightness via REG_PWM (0x86).
- display_sleep / display_wake hooks for M6.
- display_fill() and display_test_pattern() (8 vertical color bars)
for M3 acceptance and reuse by M8 local screens.
app_main calls display_init() as best-effort — if the panel isn't
attached or wiring is wrong, the rest of the firmware (Ethernet, mDNS,
/state) keeps running. On success it paints the test pattern.
Pin assignments (PIN_I2C_SDA=7, PIN_I2C_SCL=8) match Waveshare's
bundled-panel BSP convention but are flagged TODO until the user's
specific FPC adapter wiring is confirmed.
main/CMakeLists.txt: add driver, esp_driver_i2c, esp_lcd to REQUIRES.
Build verified clean against ESP-IDF 5.4 for target esp32p4. M3
acceptance (color bars on screen) cannot be verified without a
flashed board + connected panel.
|