Commit Graph
23 Commits
Author SHA1 Message Date
agessaman 3e7e0322e5 fix(display): make the USER button click immediate, 3 s hold to power off
Power-off now needs a 3 second hold; any shorter press toggles the display.
MomentaryButton reports a CLICK for any release short of its threshold, so
that single value defines both.

The button also felt unreliable - "a brief press doesn't wake it, more often
than not". MomentaryButton's multi-click detection withholds a CLICK for
MULTI_CLICK_WINDOW_MS (280 ms) after release, and folds a second press
arriving inside that window into a DOUBLE_CLICK. Since the handler only acts
on CLICK, an impatient second press produced nothing at all: press, see
nothing, press again, still nothing. Multi-click is now off for these targets,
so CLICK fires on release.

Both settings are build flags defaulted in variants/heltec_v4_r8/target.cpp
and overridden only on the two TFT observer bases, because the companion
builds share this user_btn and do use double/triple click.

DISPLAY_TOUCH_DEBUG additionally logs which input caused a toggle
("Display: button -> on"), so any remaining flake can be attributed to the
button or to a spurious touch read rather than guessed at.
2026-08-28 15:37:09 -07:00
agessaman f3c81b559b feat(display): add runtime display.flip for panel orientation
`set display.flip 0|1` (also off/on) turns the panel 180 degrees from its
compiled DISPLAY_ROTATION, persisted in MQTTPrefs alongside display.timeout
and applied live without a reboot.

Adding 2 to the compiled rotation rather than setting an absolute value keeps
portrait portrait and landscape landscape, so the DisplayViewport geometry
never changes with it and the setting cannot produce a nonsensical mix.
DisplayDriver gains a defaulted no-op setFlipped(), so no other display
driver is affected.

The compiled rotation was verified identical across `pio run` and `build.sh`
on two machines (movi a11, 2 at the setRotation call site), yet the panel read
upside down for one tester and upright for another - which is what a board
mounted either way up looks like. No single compiled constant satisfies both,
so orientation becomes a setting rather than another rebuild.

Runtime-only, like display_timeout_secs: LegacyV1MQTTPrefs and the frozen
binary payload sizes are untouched, and the JSON group is an append that older
firmware skips.
2026-08-28 14:37:03 -07:00
agessaman 86c4849e55 fix(display): correct R8 touch decode, display wake and power-off
Four defects found by hardware testing of the Expansion Kit V2.

Touch never registered. The panel's controller does not use the point-count
encoding the reference CHSC6X drivers document: byte 0 reads 0x00 idle and
0x1F while a finger is down, so testing for a count of 1 never fired. A
partly-failed read leaves 0xFF, which must not count as a press either, so
the test is now != 0x00 && != 0xFF.

Touch polling could stall the UI loop for ~1 s at a time. The controller
NACKs its address whenever it has nothing to report, and calling requestFrom()
unconditionally logged a bus error on every 50 ms poll and, once the bus
wedged, burned a full ESP_ERR_TIMEOUT inside loop(). Probe the address first,
which reports the same NACK quietly, and bound the read with setTimeOut().

The display could not be woken once it blanked; only RST brought it back.
turnOn() re-ran the whole display.init(), which re-enters SPI setup, spends
~500 ms in Adafruit's reset delays and pulses GPIO 21 - the line shared with
TP_RST, so it reset the touch controller on every wake. Since turnOff() no
longer parks that line low, the panel stays configured while dark and waking
is just the backlight. Toggling also clears the refresh deadline so the
current frame is drawn immediately instead of the stale one.

Power-off rebooted instead of staying off. powerOff() went through
enterDeepSleep(), which always arms an ext1 wake on P_LORA_DIO_1; a deep-sleep
wake is a full reboot, so a node in live traffic restarted within seconds of
showing "Turning OFF". It now disables every wake source, so the node stays
down until RST or a power cycle.

Note that the display off/on cycle had never been exercised on this board
before: observer builds pinned AUTO_OFF_MILLIS=0, so the panel never blanked
until display.timeout made it a runtime setting.
2026-08-28 14:26:52 -07:00
agessaman fcd92e985f feat(display): add R8 observer TFT dashboard, touch toggle and display.timeout
Replace the sparse Heltec V4 R8 observer home screen with a padded dark
analytics dashboard, add manual display control, and make blanking a runtime
setting.

Dashboard (DISPLAY_ACTIVITY_DASHBOARD, the four R8 TFT observer envs):

- RadioActivityWindow: 20 one-minute buckets of valid RX packets, no heap.
  The caller's 32-bit millis() is extended to a monotonic 64-bit clock, so
  nothing downstream has a rollover case; an always-on node passes 2^32 ms
  after ~49.7 days, which would otherwise re-enter warm-up and divide 20
  minutes of traffic by seconds. Rates use 19 whole minutes plus the elapsed
  part of the current one rather than a fixed 1200 s.
- ObserverDashboard: header, radio strip, headline totals, a 20-bar
  packets-per-minute graph and RF/status footers, with separate portrait and
  landscape layouts. A text row is a fixed 16 px, which is 3.2 logical units
  in portrait but 4.27 in landscape, so one shared grid would overlap.
  Text is trimmed by character budget, not measured width: getTextWidth()
  reports an over-long string at the portrait driver's fallback scale, so
  DisplayDriver::drawTextEllipsized() under-trims and the row renders at half
  height.
- Six per-row signatures computed from what is actually drawn, so only the
  rows whose pixels changed repaint. No startFrame(), no whole-screen clear.
  Link state moved out of the full-frame signature, so a DHCP renewal or WiFi
  flap repaints one footer row instead of the panel.
- Dark theme by retuning the UIColor statics at runtime, which needs no
  display-driver edit and carries boot, setup, reboot and power-off with it.

Touch and button (DISPLAY_TOUCH_TOGGLE):

- CHSC6X at I2C 0x2E, polled; TP_INT is unusable (optional R13, and GPIO 43
  is U0TXD). The point-count byte is tested against a valid count, never
  against non-zero: an idle read returns 0xFF, which reads as a finger held
  down forever and latches the tap detector after one event.
- turnOff() no longer parks PIN_TFT_RST low on this board. GPIO 21 is a
  shared LCD_RST/TP_RST net, so doing that held the touch controller in
  reset for as long as the display was off. Verified against Heltec's
  expansion-board and mainboard schematics and the V4-R8 datasheet pinout,
  which also correct the pin comment in HeltecV4R8Board.cpp.
- The USER button click now toggles the display too; it previously did
  nothing whenever the display was already on.

display.timeout:

- `set display.timeout <secs>` / `get display.timeout`, 0 = stay on, 60 s
  default, 3600 max. Read live, so a change applies without a reboot and
  restarts the countdown rather than firing on the old deadline.
- Stored in MQTTPrefs (/mqtt.json), keeping NodePrefs aligned with upstream.
  Runtime-only: LegacyV1MQTTPrefs and the four frozen binary payload sizes
  are unchanged. No JSON format-version bump - the loader skips keys no
  def() claims, so older firmware reads newer files and this firmware reads
  older ones with the default applied. Both directions are covered by tests.
- Joins the observer atomic-setter contract, so a failed save rolls the live
  value back instead of only claiming to.

New periodic work uses a wrap-safe deadline check; `millis() >= deadline`
fires every loop for a whole interval before each rollover.

Adds test_radio_activity_window, test_observer_dashboard (driving the real
renderer against a recording DisplayDriver in both orientation profiles) and
test_touch_tap_detector. 440 native cases pass.
2026-08-28 13:39:16 -07:00
agessaman fc361ca94b feat(display): add R8 portrait observer layouts 2026-08-28 09:21:38 -07:00
agessaman 612c52132c merge: upstream/dev into observer-firmware-dev (2026-07-30, db232808)
Absorbs 106 upstream commits. Seven files conflicted; the substantive one
was upstream's new JSON ConfigSerializer (PR #2982), which replaces the
binary /com_prefs layout with /prefs.json and makes NodePrefs a
ConfigSerializer subclass.

Prefs migration
- Adopt upstream's ConfigSerializer. writeCommonPrefsImage() and its
  documented offsets (0-294) are deleted, along with the now-unreachable
  saveCommonPrefsImageAtomically()/CommonPrefsFileStore atomic rename path.
- Load order is /prefs.json, then /com_prefs, then /node_prefs. Upstream
  dropped the /node_prefs fallback; it is restored here so devices that
  never advanced past that filename keep their config.
- Legacy files are never removed, so migration cannot destroy its own
  source and a deferred or failed save simply retries next boot.
- /com_prefs is treated as a format migration only, not an "upgrade", so
  it does not trip the bridge.source tx->rx flip on existing nodes.
- The MQTTPrefsAtomicStore legacy gate is retained: the observer tail
  recovered from an old-format file still commits to /mqtt_prefs before
  /prefs.json is written.
- MQTTPrefs and /mqtt_prefs are untouched; savePrefs keeps its save_mqtt
  parameter and now returns upstream's bool.

Fixes to upstream code
- RadioPrefs::structure() bound both "rxgain" and "fem_rxgain" to
  rx_boosted_gain, so radio_fem_rxgain was never persisted. Bound to the
  correct field.
- discovery_mod_timestamp was dropped from structure(); it gates
  'since'-filtered DISCOVER replies and is set on every config change, so
  losing it would silently stop discovery responses after a reboot. Added
  as "disc_mod".

Merge artifacts repaired
- Restored bblanchon/ArduinoJson to the native test env; a clean but wrong
  auto-merge at the lib_deps block boundary dropped it and broke all 19
  host test suites.
- Migrated the fork's WebConfig UITask screens off the removed
  DisplayDriver::Color enum to upstream's UIColor element types.
- Removed duplicate getCADEnabled() definitions in companion MyMesh.cpp
  and simple_sensor SensorMesh.cpp that both sides had added.
- Dropped memset(&_prefs, 0, ...) in the four example meshes; NodePrefs now
  has a vtable. guard gains an initializer that memset used to provide.

Other resolutions
- simple_room_server keeps both the fork's discover.* commands and
  upstream's new room.post.
- docs/payloads.md taken from upstream, undoing content earlier merges had
  reverted (Control data section, split login tables).

Verified: 273/273 host tests pass across native and native_kiss_modem;
Heltec v3 repeater, repeater_observer_mqtt, room_server_observer_mqtt,
sensor, and companion_radio_ble all build clean.
2026-07-30 14:56:23 -07:00
Scott Powell b8504e55c6 * refactor of Color mapping in UITasks / DisplayDrivers
* color displays now with a new light theme
2026-07-22 16:03:33 +10:00
agessaman 15e3400a28 merge: upstream/dev into webconfig (v1.16.0 base -> 2026-07-19)
First upstream merge since the 2026-06-06 base (191 upstream commits). 14 files
conflicted; resolutions below.

Fleet-critical check (Constraint 1): upstream reordered NodePrefs members
(rx_boosted_gain / path_hash_mode moved to the struct tail) but did NOT change
/com_prefs. Persistence is written field-by-field at explicit offsets, so member
order is in-memory only. Verified the fork's writeCommonPrefsImage() is
byte-identical to upstream's inline writer at every offset (79 pad, 121, 122,
290-294). No migration needed.

Resolutions:
- CommonCLI.h: kept the fork's NodePrefs (superset) and adopted upstream's
  setRxBoostedGain(bool)->bool signature change, which CommonCLI.cpp now uses to
  report unsupported. Corrected a stale comment claiming rx_boosted_gain lives at
  offset 79 (it is a pad; the field is at 290).
- CommonCLI.cpp: kept the fork's legacy /com_prefs migration and the extracted
  writeCommonPrefsImage() call.
- UITask.cpp: three-way merge - upstream's drawTextCentered + powering-off
  screen, plus the fork's WITH_WEBCONFIG portal/reboot screens.
- ESP32Board.cpp, MeshCore.h, platformio.ini: kept both sides (fork OTA additions
  alongside upstream powerOff/enterDeepSleep and Packet.cpp).
- MicroNMEALocationProvider.h: took upstream's claim/release and added the
  _claims member they depend on.
- MyMesh.cpp/.h (repeater + room server): kept the fork's superset defaults.
- Removed duplicate declarations auto-merge produced: RadioLibWrapper::_cad_enabled
  and MyMesh::getCADEnabled().

Verification: native suite 15/15 (incl. upstream's new test_mesh_tables), both
MQTT smoke builds green, ArduinoJson pin check passes. Hardware validation next.
2026-07-19 12:41:29 -07:00
agessaman dfee21a0c9 feat(webconfig): implement web configuration portal for ESP32 2026-07-16 17:00:22 -07:00
agessamanandCursor b37db66830 Merge upstream/dev into mqtt-bridge-implementation-flex
Merge meshcore-dev changes (kiss_modem targets, preamble SF tuning, CI).
Resolve conflicts: combine RadioLib preamble/watchdog fields; keep kiss_modem and MQTT envs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-09 16:37:11 -07:00
Quency-D 12e6899580 Merge branch 'dev' into cli-lna-command 2026-05-09 18:19:31 +08:00
liamcottle 3cd40902b3 added website to room server splash screen 2026-04-28 20:39:07 +12:00
Quency-D ddedb3c7a7 Merge branch 'dev' into cli-lna-command 2026-04-25 15:28:07 +08:00
agessaman 563d9a7d39 Merge upstream/dev into mqtt-bridge-implementation
Resolve conflicts: devcontainer Node feature; UITask USER_BTN + MQTT WiFi;
room server ctor with RegionMap + MQTTBridge; CommonCLI prefs layout
(rx_boosted_gain after owner_info), refactor get/set into handlers with
MQTT CLI branches and getBoard preserved.

Made-with: Cursor
2026-04-19 10:08:27 -07:00
Wessel Nieboer 0a13ac7fc7 Default button polarity to active-LOW across all firmware types
Nearly all LoRa boards use a boot button that pulls to ground when
pressed.
2026-04-04 13:19:13 +02:00
Quency-D 65752fef72 Fix the memory leak issue in the strdup function. 2026-03-24 13:57:11 +08:00
agessaman 6b0c0e931f Add IP address display for MQTT bridge devices in UITask 2026-01-02 13:36:41 -08:00
Scott Powell af606343a7 * FIX: UI should show "< Room Server >" 2025-05-05 13:11:43 +10:00
JQ 678f36a57b Implement getTextWidth for display classes
- Added getTextWidth method to DisplayDriver interface
- Implemented getTextWidth in all display classes
- Updated examples to use getTextWidth directly
2025-05-04 18:17:18 -07:00
Jacob Quatier c34dd2a40c UI: battery indicator, boot screen, radio settings 2025-04-20 19:17:02 -07:00
liamcottle c4d32eba74 remove unused variables 2025-04-08 23:17:22 +12:00
liamcottle bc820ae93e show firmware version and build date on room server screen 2025-04-08 23:09:55 +12:00
Scott Powell c2ae34314e * basic UITask added to repeater and room server (only HeltecV3 targets enable so far) 2025-03-05 12:52:29 +11:00