Compare commits

...
Author SHA1 Message Date
Scott Powell f83ad03e5e * T114 display fix 2026-07-24 18:35:56 +10:00
Scott Powell 0c04a47355 * fixed for mono ST7789 display driver 2026-07-24 15:31:22 +10:00
Scott Powell 3857f45826 * monochrome displays now do _not_ invert titlebar 2026-07-24 15:14:56 +10:00
Scott Powell 56274db4ad * build fixes for old ESP32 boards 2026-07-22 19:52:23 +10:00
Scott Powell a96a66aa99 * build fixes for NullDisplayDriver 2026-07-22 18:55:59 +10: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
Scott Powell 78240e9477 * optimised: value-init not needed 2026-07-21 19:22:09 +10:00
ripplebizandGitHub 4157e43ace Merge pull request #3005 from axhoff/agent/fix-ble-reconnect
Fix bonded BLE reconnects on ESP32
2026-07-21 19:17:34 +10:00
ripplebizandGitHub 8b048ab19a Merge pull request #3007 from axhoff/agent/synchronize-ble-receive-queue
Synchronize the ESP32 BLE receive queue
2026-07-21 18:10:37 +10:00
ripplebizandGitHub 6a15e1712b Merge pull request #3006 from axhoff/agent/fix-heltec-v4-usb-companion
Enable hardware USB serial on Heltec V4
2026-07-21 17:54:22 +10:00
Liam CottleandGitHub 7124fa1439 Merge pull request #2815 from UltimateCodeWarrior/fix/tbeam-supreme-v3-oled-i2c
Fix blank OLED on T-Beam Supreme V3 (I2C bus init + 0x3D address)
2026-07-21 19:17:32 +12:00
Alexander Hoffer 4d4d7c3746 refactor: use FreeRTOS BLE receive queue 2026-07-21 07:17:01 +01:00
=UltimateCodeWarrior 0de2b4fc5f Move Supreme OLED address to variant config
Keep SH1106Display generic by relying on DISPLAY_ADDRESS while the T-Beam Supreme variant declares its 0x3D OLED address.
2026-07-20 19:24:38 -07:00
fdlamotteandGitHub d2fd1fdaa6 Merge pull request #2887 from spinda/thinknode-m3-dev
Fix GPS and LEDs on ThinkNode M3
2026-07-20 17:42:30 -04:00
fdlamotteandGitHub 427cd7c263 Merge pull request #2987 from entr0p1/fix/nrf52-shutdownperipherals
NRF52Board - ShutdownPeripherals() housekeeping
2026-07-20 11:53:56 -04:00
Alexander Hoffer 9fd6b419e5 fix: preserve bonded BLE reconnects 2026-07-20 15:47:03 +01:00
Alexander Hoffer 60030469c9 fix: enable Heltec V4 USB serial 2026-07-20 15:47:03 +01:00
Alexander Hoffer 3885c67c8e fix: synchronize BLE receive queue 2026-07-20 15:47:03 +01:00
Liam CottleandGitHub da67328017 Merge pull request #2990 from entr0p1/fix/xiao-nrf52-userbtn
Fix Xiao nRF52840 repeater compile failure
2026-07-21 02:09:33 +12:00
entr0p1 d2bbe4d8d3 Fix Xiao nRF52840 repeater compile failure
Compile fails due to user_btn not declared in board target.

Fixes:
- Add MomentaryButton declaration for user_btn in variants/xiao_nrf52/target.cpp
- Add momentary button helper include in variants/xiao_nrf52/target.h
- Add extern linkage for MomentaryButton -> user_btn in variants/xiao_nrf52/target.h
2026-07-20 22:43:25 +10:00
Liam CottleandGitHub a45dae928c Merge pull request #3000 from oltaco/rx-irq-timeout
fix: clear the correct IRQ flag for timeout on preamble detected on SX1262
2026-07-20 23:32:12 +12:00
taco ab5c45f516 fix: clear the correct IRQ flag for timeout on preamble detected 2026-07-20 21:05:12 +10:00
Liam CottleandGitHub c1ee9cad5b Merge pull request #2998 from oltaco/heltec-t1-bodmer
Fix: Heltec t1: add missing bodmer TFT_eSPI library
2026-07-20 22:39:17 +12:00
taco 91e65af9dd fix: heltec t1: add missing bodmer TFT_eSPI library 2026-07-20 20:28:38 +10:00
Liam CottleandGitHub 2834121b6e Merge pull request #2994 from oltaco/rx-irq-timeout
Add setter methods for preambleMillis and maxPayloadMillis on SX1262 and LR1110
2026-07-20 22:07:36 +12:00
taco 121b72712b LR1110: add setter methods for preambleMillis and payloadMillis 2026-07-20 19:52:57 +10:00
taco ae48072ff0 SX1262: add setter methods for preambleMillis and payloadMillis 2026-07-20 19:52:57 +10:00
Liam CottleandGitHub fdd4d8a610 Merge pull request #2989 from rgregg/fix-nrf52-ethernet-build
Fix nRF52 builds broken by unconditional RAK Ethernet include
2026-07-20 20:58:26 +12:00
liamcottle 8c987c8689 Merge remote-tracking branch 'official/main' into dev 2026-07-20 20:42:09 +12:00
ripplebizandGitHub dc7dd5105e Merge pull request #2977 from oltaco/rx-irq-timeout
Add IRQ timeout logic for SX1262 and LR1121
2026-07-20 14:14:02 +10:00
Ryan Gregg cc221330ba Fix nRF52 builds: guard SerialEthernetInterface with ETHERNET_ENABLED
SerialEthernetInterface.cpp is compiled for every nRF52 target because
PlatformIO builds all .cpp files under src/. It includes
SerialEthernetInterface.h, which unconditionally pulls in
<RAK13800_W5100S.h> -- a library only present in the RAK4631 Ethernet
env's lib_deps. As a result any other nRF52 board (e.g. Heltec T114)
fails to build with 'RAK13800_W5100S.h: No such file or directory'.

Wrap the contents of both files in '#ifdef ETHERNET_ENABLED' so they
compile to empty translation units on non-Ethernet builds. RAK4631
Ethernet envs define ETHERNET_ENABLED and are unaffected.

Fixes #2985
2026-07-19 10:17:08 -07:00
entr0p1 aee53a8c3e NRF52Board - ShutdownPeripherals() housekeeping
Improvements:
- Add condition to only turn off display if on (avoids wedging the shutdown on some displays)
- Reset LoRa radio before calling radio_driver.powerOff() to ensure it is in a known good state (and will accept the command)
- Ensure SPI is started so LoRa radio commands can be sent (some shutdowns can happen before this)
- Add gate for P_LORA_NSS pin defined before writing to the pin
2026-07-19 23:59:12 +10:00
taco b28bf00348 fix: setMaxPacketMillis() should return void 2026-07-19 22:06:36 +10:00
Liam CottleandGitHub a3a1aa5e3b Merge pull request #1865 from MeshEnvy/fix/python-json-parse-error
fix: JSON parsing regression in build.sh
2026-07-19 22:58:21 +12:00
ripplebizandGitHub 795989b918 Merge pull request #2966 from Quency-D/heltec-rc32
Add Heltec rc32 board
2026-07-17 23:23:29 +10:00
fdlamotteandGitHub 4aec46b039 Merge pull request #2976 from fdlamotte/UITask_poweroff_radio_display
UI task: poweroff of radio and display moved to board
2026-07-17 08:35:06 -04:00
Florent c644720ea7 uitask: screen and radio poweroff moved to board 2026-07-17 08:29:31 -04:00
fdlamotteandGitHub 8f5a393404 Merge pull request #2975 from fdlamotte/nrf52_boards--use-shutdownPeripherals-on-TInpulse-and-TEchoBoard
nrf52_variants: override shutdownPeripherals instead of powerOff for …
2026-07-17 08:12:35 -04:00
taco 0bd871cde6 add IRQ timeout logic for LR11X0 2026-07-17 21:45:31 +10:00
Florent 84d1e24338 nrf52_variants: override shutdownPeripherals instead of powerOff for TechoLite, TInpulse and TechoBoard (protect the gate) 2026-07-17 07:36:39 -04:00
taco 79ef74ea8d add IRQ timeout logic for SX1262 2026-07-17 21:33:57 +10:00
fdlamotteandGitHub 3a67685597 Merge pull request #2883 from fdlamotte/repeater_btn
repeater: poweroff on long press
2026-07-17 07:18:44 -04:00
taco 975a673efd add calcMaxPacketMillis 2026-07-17 21:15:09 +10:00
fdlamotteandGitHub 76c6e73fb6 Merge branch 'dev' into repeater_btn 2026-07-17 06:55:19 -04:00
fdlamotteandGitHub 3eb5451d3e Merge pull request #2906 from fdlamotte/shutdown_peripherals
Proposal: Introduce shutdownPeripherals in NRF52Board to prepare for shutdown
2026-07-17 06:53:00 -04:00
Quency-D fd7b35f457 Move the rotation input to target.cpp 2026-07-17 15:50:25 +08:00
Quency-D b26f53a263 Avoid waking display from rotary input 2026-07-17 15:35:07 +08:00
ripplebizandGitHub 41f4d6bd91 Merge pull request #2560 from Quency-D/heltec-v4-r8
Add heltec-v4-r8 board
2026-07-17 13:54:45 +10:00
Quency-D 61a8821d3c Optimize battery voltage reading 2026-07-16 17:45:00 +08:00
Quency-D a472cd7956 Delete duplicate pin definitions 2026-07-16 15:14:17 +08:00
Quency-D 3469e92458 Enable patch reception 2026-07-16 11:37:12 +08:00
Quency-D 05d4bd2f7f Add a rotary encoder 2026-07-15 17:45:24 +08:00
ripplebizandGitHub b55d69e57a Merge pull request #1983 from rgregg/rak-ethernet
Support for RAK ethernet module
2026-07-15 18:53:38 +10:00
Liam CottleandGitHub 1831349e5a Merge pull request #2897 from yarda/companion-add-mcu-temp-sensor
Added MCU temp sensor to the companion
2026-07-15 20:11:24 +12:00
Quency-DandGitHub 6c9f0907ee Merge pull request #17 from meshcore-dev/dev
merge Dev
2026-07-15 14:49:40 +08:00
Liam CottleandGitHub f44aede4f3 Merge pull request #2954 from liamcottle/board/thinknode-m7-neighbours
Increase ThinkNode M7 MAX_NEIGHBORS to 50
2026-07-15 16:06:58 +12:00
liamcottle a99f4a0160 increase thinknode m7 max neighbours to 50 2026-07-15 16:05:37 +12:00
Scott Powell 5a14f36e25 * ST7735 color/offset fix for Heltec Tracker V1 2026-07-15 14:00:10 +10:00
Jaroslav Škarvada 15e259c572 Added MCU temp sensor to the companion
Fixes #2896
2026-07-14 17:43:33 +02:00
Liam CottleandGitHub ea4383f90c Merge pull request #2952 from Quency-D/fix-ex-watchdog
fix  repeater watchdog
2026-07-15 02:54:58 +12:00
Quency-D 4d0ba1516c Remove the watchdog feeding operation from each iteration of the loop to reduce power consumption. 2026-07-14 17:23:13 +08:00
Ryan Gregg 16b76bd35a Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts:
#	examples/companion_radio/main.cpp
2026-07-13 23:06:00 -07:00
Huw DuddyandGitHub 64e8e24720 Merge pull request #2951 from meshcore-dev/fast-st7735-display
Fast ST7735 Display Driver
2026-07-14 16:01:14 +10:00
Scott Powell d30d8ed748 * HSPI fix 2026-07-14 15:46:36 +10:00
ripplebizandGitHub 05fdafbb2d Merge pull request #2898 from entr0p1/fix/lilygo-techo-lite-sx1262-ldo
Lilygo T-Echo Lite - Change SX1262 from DC-DC to LDO regulator
2026-07-14 14:53:44 +10:00
ripplebizandGitHub f4ed8c711b Merge pull request #2899 from entr0p1/fix/lilygo-techo-lite-nrf-dcdc
Lilygo T-Echo Lite - nRF52840 DC-DC
2026-07-14 14:52:56 +10:00
ripplebizandGitHub dc0f837451 Merge pull request #2905 from entr0p1/fix/heltec-t114-nrf-dcdc
Heltec T114 - nRF52840 DC-DC Regulator
2026-07-14 14:52:10 +10:00
ripplebizandGitHub 7f4f03d737 Merge pull request #2946 from hpux735/stream-allocation
Add StreamSensor allocation for GroupData
2026-07-14 14:47:57 +10:00
Scott Powell 1bdacb3247 * trying to get Heltec Tracker and Tracker V2 working 2026-07-13 23:27:50 +10:00
Will Dillon 7e95c52972 Add StreamSensor allocation for GroupData
Adds a small allocation for groupdata packets for the Meshcore firmware for the StreamSensor product. It was previously a LoRaWAN platform, and we've moved to Meshcore.

Let me know when and how to demonstrate.
2026-07-13 05:54:31 -07:00
ripplebizandGitHub 219812b9f1 Merge pull request #2943 from meshcore-dev/revert-2915-main
Revert "Add StreamSensor allocation for GroupData"
2026-07-13 19:31:05 +10:00
ripplebizandGitHub d9cd3ea03a Revert "Add StreamSensor allocation for GroupData" 2026-07-13 19:30:11 +10:00
ripplebizandGitHub a517f9c24f Merge pull request #2915 from hpux735/main
Add StreamSensor allocation for GroupData
2026-07-13 18:42:54 +10:00
ripplebizandGitHub b81fb8ebe8 Merge pull request #2917 from jirogit/fix/regionmap-load-eof-handling
fix(RegionMap): load() returns actual read success instead of hardcoded true
2026-07-13 18:28:26 +10:00
ripplebizandGitHub 174bcdfbcc Merge pull request #2942 from liamcottle/board/thinknode-m9
Add support for Elecrow ThinkNode M9
2026-07-13 17:10:54 +10:00
liamcottle 87bf371cd3 initial support for thinknode m9 2026-07-13 19:07:09 +12:00
ripplebizandGitHub 4f12dc02d5 Merge pull request #2937 from benskigomez/fix/gps-timesync-millis-overflow
sensors: fix millis() rollover that stalls GPS time-sync on long-uptime nodes
2026-07-13 17:06:09 +10:00
Liam CottleandGitHub 1c5a8426e8 Merge pull request #2839 from liamcottle/board/thinknode-m7
Add support for Elecrow ThinkNode M7
2026-07-13 15:06:17 +12:00
Liam CottleandGitHub 6bd66d7dc8 Merge pull request #2940 from liamcottle/upgrade/radiolib
Update RadioLib to v7.7.1-6d893483
2026-07-13 15:03:29 +12:00
Liam CottleandGitHub e01a2565b9 Merge pull request #2936 from beala/beala/MeshTowerV2_feed_watchdog_2
Feed external hardware watchdog on Heltec Tower V2 (aka MeshTower V2) Implementation 2
2026-07-13 14:49:04 +12:00
liamcottle 17b6662aa5 update radiolib to v7.7.1-6d893483 2026-07-13 14:39:54 +12:00
Liam CottleandGitHub 55f43e52e2 Merge pull request #2935 from benskigomez/fix/m6-repeater-debug-flags
ThinkNode M6 repeater: disable leftover MESH_DEBUG/GPS_NMEA_DEBUG
2026-07-13 12:55:13 +12:00
Liam CottleandGitHub 8e0c9a241b Merge pull request #2938 from benskigomez/fix/room-server-default-password
ThinkNode M6/M1 room_server: set ROOM_PASSWORD default
2026-07-13 12:54:16 +12:00
Jody Bentley 1429d75f57 ThinkNode M6/M1 room_server: set ROOM_PASSWORD default
Without ROOM_PASSWORD the guest password defaults to empty, and a client
sending a blank password is granted read+write access (can post). Every
other board's room_server env sets ROOM_PASSWORD; the ThinkNode M6 and M1
were the only two shipping an open room. Default them to "hello" to match
the rest of the tree; operators can still change it at runtime with
'set guest.password'.
2026-07-12 18:32:09 -04:00
Jody Bentley 63731d3fbf sensors: fix millis() rollover stall in GPS time-sync + minor cleanups
next_check and next_gps_update stored a future millis() value in a signed
long and compared with a naive '>'. After the ~24.8-day millis() sign flip
the deadline sits above the wrapped millis(), so the block never runs again
and GPS->RTC time-sync (and the location cache refresh) stall permanently
until reboot. Switch to unsigned deadlines with the wrap-safe signed-
difference compare '(long)(millis() - deadline) > 0', matching the idiom in
Dispatcher::millisHasNowPassed.

Also: reorder the MicroNMEALocationProvider ctor init-list to declaration
order (silences -Wreorder) and drop the always-true 'if (_claims > 0)' guard
in claim() (claim() always runs after _claims++, so it is >= 1).
2026-07-12 18:30:40 -04:00
Alex Beal eb97a375d3 Add an instance of ExternalWatchdogManager to Tower V2 2026-07-12 16:17:23 -06:00
Jody Bentley 6343d8d96e ThinkNode M6 repeater: disable leftover MESH_DEBUG/GPS_NMEA_DEBUG
The M6 is the only board whose simple_repeater env ships with MESH_DEBUG=1
and GPS_NMEA_DEBUG=1 enabled. MESH_DEBUG=1 adds a 5-second boot delay
(examples/simple_repeater/main.cpp) plus verbose per-packet serial prints on
the hot RX/TX path; GPS_NMEA_DEBUG=1 echoes every GPS UART character to the
serial console (MicroNMEALocationProvider.h). Every sibling repeater env
(M1, M3, t1000-e, RAK, Heltec) ships these off, and the M6 room_server env
in this same file already has them commented. Comment them out to match.
2026-07-12 17:54:14 -04:00
Alex Beal 47e1ce5bae Update #defines
External watchdog support was added (but never merged) for MeshTower V1. The update's V2's #defines to follow the conventions set there and piggy back off the support already written for V1.
2026-07-12 15:50:43 -06:00
Alex Beal 07b543176b Merge branch 'dev' into pr-1446 2026-07-12 15:44:28 -06:00
jirogit 8ef369058d fix(RegionMap): distinguish clean EOF from partial read in load()
Previously load() returned true unconditionally on file-open success,
masking truncated or corrupt /regions2 files. Additionally, the first
field of each entry record (r->id) used the same success-chaining
pattern as subsequent fields, so a clean EOF at a record boundary set
success=false and would have been indistinguishable from real
corruption once the return value was fixed.

The r->id read is now split out: n==0 is a clean EOF (break, success
retains its prior value from the header read), n!=sizeof(r->id) is a
partial read or corruption (break, success=false). load() now returns
success instead of an unconditional true, so its return value reflects
the actual parse outcome.

Companion fix to #2372, which fixed the same return-true hardcoding
in save(). (#1891 originally reported this on load() but was closed
when #2372 landed — that PR only touched save(); this addresses the
load() side.)
2026-07-09 19:59:05 -07:00
Liam CottleandGitHub 102f1d2a47 Merge pull request #2913 from weebl2000/fix-typos
fix misspelled RF switch pin macros in SX1268/LLCC68/SX1276 radio wrappers
2026-07-10 12:26:28 +12:00
Will DillonandGitHub 754fcb1fae Update number_allocations.md
Adds a small allocation for groupdata packets for the Meshcore firmware for the StreamSensor product.  It was previously a LoRaWAN platform, and we've moved to Meshcore.
2026-07-09 11:43:20 -07:00
Wessel Nieboer 4605d3b6ce fix misspelled RF switch pin macros in SX1268/LLCC68/SX1276 radio wrappers 2026-07-09 16:31:28 +02:00
Quency-D 17d68e328f Add heltec rc32 board 2026-07-08 16:20:12 +08:00
Quency-DandGitHub 10d2d2a1c7 Merge pull request #16 from meshcore-dev/dev
merge Dev
2026-07-08 15:15:19 +08:00
Florent 369016acc9 introduce shutdownPeripherals in NRF52Board to prepare for shutdown 2026-07-07 10:20:03 -04:00
entr0p1 7f8522a362 Heltec T114 - nRF52840 DC-DC Regulator
- Enable DC-DC regulator on nRF52840 for Heltec T114 board (idle current reduced from ~12mA to ~9mA)
2026-07-07 17:12:59 +10:00
Scott Powell 2c9ab2fe5e * timing fixes (courtesy of Taco) 2026-07-07 13:54:14 +10:00
Liam CottleandGitHub bbb58cceb8 Merge pull request #2903 from liamcottle/ci/build-matrix
Refactor builds via GitHub Actions to be super fast
2026-07-07 14:31:28 +12:00
liamcottle 54234b5837 implement matrix builds for faster firmware releases 2026-07-07 13:23:18 +12:00
Florent fec88e1300 fix tenstar c3 repeater build 2026-07-06 12:04:42 -04:00
Florent 3ee2f77877 restore display and radio poweroff in ui 2026-07-06 08:46:57 -04:00
Florent 2efb3af69b repeater: poweroff on long press 2026-07-06 08:40:56 -04:00
fdlamotteandGitHub 9e66a2e246 Merge pull request #2895 from IoTThinks/MCdev-Fixed-poweroff-to-uA-202607
Fixed hibernate / poweroff for BLE companions and repeaters to stay at uA
2026-07-06 08:02:23 -04:00
entr0p1 4fafcd0e23 Lilygo T-Echo Lite
- Enable nRF52840 DC-DC - board idle usage reduced from ~12mA to ~7mA
2026-07-06 15:21:09 +10:00
Scott Powell cea246d0eb * super fast ST7735 display driver 2026-07-06 15:09:18 +10:00
entr0p1 584f9e909b Lilygo T-Echo Lite
- Change SX1262 to use LDO instead of DC-DC (DCC pin on SX1262 not wired in, according to schematic)
2026-07-06 14:52:18 +10:00
Kevin Le 35f654ced3 Fixed hibernate/powerOff for ESP32 boards to stay at uA 2026-07-05 23:47:27 +07:00
Kevin Le 6b205da4e9 Added missing driver/rtc_io.h 2026-07-05 23:33:56 +07:00
Kevin Le eec75eab31 Cleanup Poweroff for NRF52 boards 2026-07-05 22:10:47 +07:00
Kevin Le 7d7de88707 board->powerOff() is sufficient to power off radio, display, GPS and components 2026-07-05 21:22:13 +07:00
Kevin Le 73b7367e45 Put powerOff to NRF52Board 2026-07-05 21:20:39 +07:00
Kevin Le a92046b0eb Put powerOff and enterDeepSleep to ESP32Board 2026-07-05 21:20:13 +07:00
liamcottle 57563ab8f9 update qr code docs 2026-07-06 02:04:56 +12:00
liamcottle b40968a0f0 stop tracking vscode extensions.json 2026-07-05 12:23:22 +12:00
Quency-DandGitHub 2867542cfa Merge branch 'dev' into solar-watchdog 2026-07-04 16:47:37 +08:00
ripplebizandGitHub 61e21462aa Merge pull request #2867 from entr0p1/feature/sx1262-ldo-support
SX1262 LDO Support
2026-07-04 12:35:03 +10:00
Naomi Smith 3af5ffe119 Fix GPS and LEDs on ThinkNode M3
GPS support was implemented incorrectly for the ThinkNode M3. The reset
pin was being driven when it should be left floating for this unit. The
enable pin also wasn't being picked up by `MicroNMEALocationProvider`
because of a mismatch in constant naming conventions. I did a general
cleanup of the GPS and ThinkNode M3 bring-up code so that constant names
line up and "*_ACTIVE" constants are used consistently vs hardcoding
`HIGH`/`LOW`. After making these changes, serial data immediately starts
streaming in from the NMEA on boot and GPS detection just works.

LED handling was also not quite right for the ThinkNode M3. The LoRa TX
LED was being driven high to turn it on when it should actually be
driven low. I changed the code to use the `LED_STATE_ON` constant and
also added a little code to the shutdown path to properly make sure that
all LEDs are turned off.

Tested and confirmed working on real hardware.

Resolves both #1864 and #2879.
2026-07-03 12:07:19 -07:00
Liam CottleandGitHub acdf4e5bdc Merge pull request #2372 from jirogit/fix/region-save-return-value
fix(RegionMap): save() returns actual write success instead of hardcoded true
2026-07-03 12:40:22 +12:00
ripplebizandGitHub 33fdf67e4d Merge pull request #2874 from neilalexander/neil/replyregion
Fix `recv_pkt_region` incorrect usage
2026-07-02 22:20:30 +10:00
Neil Alexander 0d7379520f Fix recv_pkt_region incorrect usage
The `recv_pkt_region` is set when processing a flood packet in `filterRecvFloodPacket`
but direct/non-flood packets would never pass through that function, so the pointer was
not cleared for them.

`sendFloodReply` would then later use it blindly, which meant that the response would
either inherit the region from the last flood packet, or refer to a non-initialised pointer
if no region floods had been received yet.
2026-07-02 09:28:41 +01:00
ripplebizandGitHub affe61dc3b Merge pull request #2880 from ckoehler/push-sonzsorqmwqz
refactor: split MeshTables::hasSeen into pure query + markSeen
2026-07-02 13:58:48 +10:00
Christoph Koehler 4f701b7aec refactor: split MeshTables::hasSeen into pure query + markSeen
hasSeen() was simultaneously a predicate and a mutator — it inserted the
packet hash on every miss, making five call sites that only wanted to mark
a packet as sent call it with the return value discarded.

Split into:
- wasSeen()   — pure predicate, no side effects
- markSeen()  — explicit insert

All query sites now call markSeen() immediately after wasSeen() returns
false, preserving identical runtime behaviour. The five mark-only send
sites (sendFlood, sendDirect, sendZeroHop x2) now call markSeen directly.

Also fixes three bridge sites (BridgeBase, ESPNowBridge, RS232Bridge)
that had the same query+implicit-insert pattern.

Tests: add test/test_mesh_tables/ covering wasSeen purity, markSeen,
dup stats, and clear. Update SHA256 mock to produce deterministic output
(previously finalize() was a no-op). Add Packet.cpp to native build filter.
2026-07-01 21:51:38 -06:00
Liam CottleandGitHub dd87371574 Merge pull request #2873 from ckoehler/fix/nix-devshell
build: add gcc, gtest to nix shell, fix native test framework
2026-07-02 11:32:21 +12:00
Christoph Koehler 4f7c1cb88f build: add gcc, gtest to nix shell, fix native test framework 2026-07-01 08:19:27 -06:00
entr0p1 2cb3a29fa8 SX1262 LDO Support
- Added build flag "SX126X_USE_REGULATOR_LDO" (set to 1 to enable) to use LDO for the SX1262 radio instead of DC-DC when the DCDC pin isn't wired up (e.g. t-echo lite)
- Added additional debug mode diagnostic messages during SX1262 init to better highlight faults
2026-06-30 16:55:16 +10:00
Liam CottleandGitHub 1869b57d62 Merge pull request #2853 from HDDen/dev
Add Data-Type to number_allocations.md
2026-06-29 21:49:19 +12:00
HDDen 58b0f7df9c Update number_allocations.md 2026-06-29 12:41:44 +03:00
HDDen bc35208682 Update number_allocations.md
Added Data-Type range for MeshCore Images codec (range is for small reserve to future versions)
2026-06-28 00:32:22 +03:00
Scott Powell 4f8cb8db78 * ACK packets being 'clipped' (in Dispatcher send). Needed to extend max_airtime timeout calc for short packets 2026-06-27 21:06:01 +10:00
Liam CottleandGitHub 5ff39c6243 Merge pull request #2570 from IoTThinks/MCdev-Fixed-flashmode-XiaoC6-202605
Added flash_mode=dio to avoid boot loop when flashing using merge.bin
2026-06-27 11:32:10 +12:00
Liam CottleandGitHub dc200cf22d Merge pull request #2639 from sefinek/fix/typos-in-comments
fix: fix typos in source code comments
2026-06-27 11:26:54 +12:00
Liam CottleandGitHub 62b95b4e48 Merge pull request #2844 from oltaco/fix-rx-boosted-gain
Fix for Rx boosted gain on LR1110
2026-06-27 11:22:47 +12:00
taco f3d4d8cd5e always save boosted gain setting 2026-06-26 22:36:49 +10:00
Quency-DandGitHub 341aa5e0f4 Merge pull request #14 from meshcore-dev/dev
merge Dev
2026-06-26 17:16:23 +08:00
Liam CottleandGitHub 3d4eec4a6a Merge pull request #1194 from ViezeVingertjes/nibble_zero_connect
Added support for the nibble zero connect
2026-06-26 13:59:59 +12:00
taco b07aba7937 fix: report error when rxgain setting is unsupported or rejected 2026-06-26 08:45:03 +10:00
taco 1f9bb67400 return bool when setting rx boost 2026-06-26 07:44:01 +10:00
ViezeVingertjes b8f1fad654 Add trailing underscore to nibble_screen_connect env names to exclude from automatic builds 2026-06-25 23:27:07 +02:00
ViezeVingertjes eb938a9b78 Address review comments and sync with dev 2026-06-25 23:26:55 +02:00
ViezeVingertjes 69df5c7a95 Remove NeoPixel configuration as its unused. 2026-06-25 23:26:55 +02:00
ViezeVingertjes 9c7d71b9be Added support for the nibble zero connect 2026-06-25 23:26:55 +02:00
taco 5d0ff7fe6e remove guard on MyMesh::setRxBoostedGain() 2026-06-26 07:06:37 +10:00
liamcottle b975180fc8 initial support for elecrow thinknode m7 2026-06-25 16:03:27 +12:00
UltimateCodeWarriorand=UltimateCodeWarrior b8b6598dd3 Fix blank OLED on T-Beam Supreme V3 (I2C bus + 0x3D address)
The SH1106 OLED on the LilyGo T-Beam Supreme V3 never lights up because:

1. The primary I2C bus (Wire) that the OLED, BME280 and magnetometer share
   is never initialized. The PMU/RTC use Wire1 (via XPowersLib); nothing
   brings up Wire on PIN_BOARD_SDA/PIN_BOARD_SCL, so the display driver
   talks on the wrong default pins.
2. The OLED is at I2C address 0x3D on this board (0x3C is taken by the
   QMC6310 magnetometer), but SH1106Display::begin() only tried 0x3C.

Initialize Wire for TBEAM_SUPREME_SX1262, and have SH1106Display::begin()
prefer 0x3D (only ever used by the OLED) before falling back to 0x3C.
Backwards-compatible for boards with the OLED at 0x3C.

Closes #2609
2026-06-22 23:39:34 -07:00
ripplebizandGitHub 3192684582 Merge pull request #2752 from Quency-D/heltec-tower-v2
Add Heltec tower v2
2026-06-23 11:51:03 +10:00
ripplebizandGitHub aa1cb1f663 Merge pull request #2765 from fizzyfuzzle/ESP32-Reset-Reason
Add ESP32 Reset Reason to existing pwrmgt.bootreason cli command
2026-06-23 11:41:57 +10:00
ripplebizandGitHub 6a63dfce1c Merge pull request #2771 from oltaco/nrf52-framework-buildfix
Point Heltec Mesh Solar and Mesh Pocket at our NRF52 framework fork
2026-06-23 11:37:03 +10:00
Liam CottleandGitHub c2cfba4af3 Merge pull request #2810 from IoTThinks/MCDev-Fixed-GPS-Time-for-T114-202606
Fixed GPS time sync for Heltec T114
2026-06-22 14:19:09 +12:00
Kevin Le 62849ef114 Fixed GPS time sync for Heltec T114 2026-06-22 09:13:36 +07:00
Ryan Gregg a206f3e066 Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts:
#	docs/faq.md
#	examples/companion_radio/main.cpp
2026-06-20 14:39:03 -07:00
ripplebizandGitHub 60ea4a91bf Merge pull request #1727 from weebl2000/use-hardware-channel-activity-detection
Use hardware channel activity detection for checking interference
2026-06-15 19:51:59 +10:00
taco dd9b3b32c3 build fix for Heltec Mesh Solar and Mesh Pocket 2026-06-15 00:57:12 +10:00
Wessel Nieboer 7c8e092457 Have CAD be a separate toggle (set cad on/off) 2026-06-14 15:20:58 +02:00
Wessel NieboerandWessel Nieboer 099ef67348 Prevent packet errors from growing 2026-06-14 15:19:20 +02:00
Wessel NieboerandWessel Nieboer 04a6c7009a Just check for not channel free 2026-06-14 15:19:19 +02:00
Wessel NieboerandWessel Nieboer 813d108c7a Also return busy if preamble detected 2026-06-14 15:19:19 +02:00
Wessel Nieboer 4f9a091671 Use hardware channel activity detection for checking interference 2026-06-14 15:19:18 +02:00
Marco e7db7c5a94 Add ESP32 Reset Reason 2026-06-14 11:41:01 +02:00
Huw DuddyandGitHub 29f0a7fc4f Merge pull request #2763 from meshcore-dev/anon-contacts-fix
* fix for anon contacts when full
2026-06-14 19:11:38 +10:00
Scott Powell 2e73fe948e * fix for anon lastmod 2026-06-14 18:54:51 +10:00
Scott Powell 538ac38e18 * fix for counter in RESP_CODE_CONTACTS_START 2026-06-14 18:39:34 +10:00
Scott Powell c2d223ff55 * now handle the case where onAdvertRecv() should _replace_ the anon contact slot 2026-06-14 18:12:08 +10:00
Scott Powell 07648e3344 * fix for anon contacts when full 2026-06-14 17:50:41 +10:00
ripplebizandGitHub a5cb0c25ca Merge pull request #2140 from Quency-D/cli-lna-command
Add CLI control to LoRa's fem LNA.
2026-06-13 22:00:08 +10:00
Quency-D 5300fa18c7 Restore companion NodePrefs file ending 2026-06-13 17:40:11 +08:00
Quency-D 3ee58fd2e3 Remove companion FEM RX gain command IDs 2026-06-13 17:31:54 +08:00
Scott Powell d5f74e93c5 * PAYLOAD_TYPE_PATH bad path_len now rejected 2026-06-13 18:19:41 +10:00
Quency-D 1c4c995a41 Fix low power consumption issues 2026-06-13 16:00:06 +08:00
Liam CottleandGitHub e8d3c53ba1 Merge pull request #2753 from formtapez/cli-docs
Added some missing CLI commands
2026-06-13 00:17:36 +12:00
formtapez d3444e6b0b fix formatting 2026-06-12 12:14:46 +02:00
formtapez 06130dce29 added some missing CLI commands 2026-06-12 12:11:12 +02:00
Quency-D d2d3d44c9b Add maximum power limit 2026-06-12 18:05:27 +08:00
Quency-D fe56d01da2 add tower v2 PA control 2026-06-12 17:58:19 +08:00
Liam CottleandGitHub 55ad7689d5 Merge pull request #2748 from liamcottle/github/stale-bot
Configure GitHub Stale Bot
2026-06-12 03:24:26 +12:00
liamcottle 3b39925399 use single quotes for repo name 2026-06-12 03:20:44 +12:00
liamcottle c94ed29ca3 add github workflow to close stale issues 2026-06-12 03:18:10 +12:00
Quency-DandGitHub 190dbb5293 Merge pull request #13 from meshcore-dev/dev
merge Dev
2026-06-11 15:30:31 +08:00
Liam CottleandGitHub 5a342c7a20 Merge pull request #2691 from NoodlesNZ/security-policy
Add Github Security policy
2026-06-11 19:25:53 +12:00
Quency-DandGitHub 9100a58671 Merge branch 'dev' into cli-lna-command 2026-06-10 11:30:04 +08:00
ripplebizandGitHub 5f3b7f25d0 Merge pull request #2722 from liamcottle/fix/free-packet-on-parse-failure
Fix: CMD_SEND_RAW_PACKET not freeing packet on parse failure
2026-06-08 22:47:06 +10:00
liamcottle ae0bb7ee95 use releasePacket instead of _mgr->free 2026-06-09 00:42:58 +12:00
liamcottle 07bfe90695 free packet on parse failure 2026-06-09 00:31:48 +12:00
Nick Le Mouton dea5ed790f Add SECURITY.md 2026-06-05 21:25:25 +12:00
Sefinek 4bf391f5c3 fix: fix typos in source code comments 2026-05-28 16:38:39 +02:00
Sefinek 00d445a269 fix: fix typos in source code comments 2026-05-28 15:44:56 +02:00
Quency-DandGitHub 8ed7be971a Merge pull request #11 from meshcore-dev/dev
merge Dev
2026-05-27 15:43:22 +08:00
Quency-D e5a3839d64 Optimize ADC readout 2026-05-27 15:12:59 +08:00
Quency-DandGitHub 250f448c3f Merge branch 'dev' into cli-lna-command 2026-05-20 10:13:28 +08:00
Ryan Gregg 40bd7d511b Fix companion disconnect on inbound packet; use server.accept()
EthernetServer::available() returns any socket with data, including the
already-connected client's socket. The companion path then called stop()
on what it thought was a duplicate client, closing the shared socket and
disconnecting the companion after the first packet.

Switch both SerialEthernetInterface and EthernetCLI to server.accept(),
which only returns newly-accepted sockets. Removes the IP/port duplicate
detection in the companion path (no longer reachable) and the same-socket
early-return in the CLI path.

Reported by mcode6726 on PR #1983.
2026-05-18 21:04:09 +00:00
Ryan Gregg 87bed4946c Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts:
#	examples/companion_radio/main.cpp
2026-05-18 20:57:28 +00:00
Quency-D 1ac5f359ca Added touch reset 2026-05-16 10:59:02 +08:00
Kevin Le f29cae602f Added flash_mode=dio to avoid boot loop when flashing using merge.bin 2026-05-16 09:30:49 +07:00
Quency-D c183050935 Add heltec -v4-r8 board 2026-05-15 16:53:18 +08:00
Quency-D 3a546a6395 add heltec tower v2 2026-05-12 14:53:17 +08:00
Quency-DandGitHub 12e6899580 Merge branch 'dev' into cli-lna-command 2026-05-09 18:19:31 +08:00
Quency-DandGitHub ec9fe67afe Merge branch 'dev' into solar-watchdog 2026-05-09 18:08:32 +08:00
jirogit 8e787f6ee4 fix(RegionMap): save() returns actual write success instead of hardcoded true 2026-05-01 00:06:46 -07:00
Quency-D 363ac44461 Fix external watchdog timing around sleep 2026-04-27 15:32:02 +08:00
Quency-DandGitHub 7e0d9f6f39 Merge pull request #10 from meshcore-dev/dev
merge Dev
2026-04-27 14:48:26 +08:00
Quency-DandGitHub 444dcfb8fd Change write to read for radio_fem_rxgain 2026-04-27 14:06:21 +08:00
Ryan Gregg a3354db521 Address PR review feedback from oltaco
- Remove extra blank line in companion_radio main.cpp before NRF52/STM32 block
- Revert unintended STM32_PLATFORM ETHERNET_ENABLED branch (no STM32 ethernet target exists, and it incorrectly included nrf52 headers)
- Drop renovate annotations from rak4631 platformio.ini (no renovate config in this repo)
2026-04-26 20:22:35 +00:00
Quency-DandGitHub 62b0d82682 Restore WiFi operation command scope comments 2026-04-25 16:12:40 +08:00
Quency-DandGitHub 9d26953398 Remove unnecessary blank line in MyMesh.cpp 2026-04-25 16:10:57 +08:00
Quency-DandCopilot c42f6db0eb Revise according to the review comments.
Co-authored-by: Copilot <copilot@github.com>
2026-04-25 16:01:26 +08:00
Quency-D 22f07b3e48 Revert "Merge branch 'meshcore-dev:main' into cli-lna-command"
This reverts commit ca047fe0c0, reversing
changes made to ddedb3c7a7.
2026-04-25 15:33:59 +08:00
Quency-DandGitHub ca047fe0c0 Merge branch 'meshcore-dev:main' into cli-lna-command 2026-04-25 15:29:00 +08:00
Quency-DandGitHub ddedb3c7a7 Merge branch 'dev' into cli-lna-command 2026-04-25 15:28:07 +08:00
Ryan Gregg 2a2c7a171d Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts:
#	docs/faq.md
2026-04-20 15:06:09 +00:00
Quency-D 9664305a87 Adapt LNA CLI control commands for heltec_t096. 2026-03-24 16:13:13 +08:00
Quency-D 2442e9a5bd Adapt LNA CLI control commands for heltec_tracker_v2. 2026-03-24 16:05:28 +08:00
Quency-D 65752fef72 Fix the memory leak issue in the strdup function. 2026-03-24 13:57:11 +08:00
Quency-D 8435464c84 Add v4 FEM LNA CLI control commands. 2026-03-24 10:48:15 +08:00
Ryan Gregg 0ffe1c2cb9 bug fix: ethernet cli dropping connection after each command 2026-03-21 19:28:44 -07:00
Ryan Gregg 3c1a0941aa Fix POE boot failure on RAK4631 Ethernet builds
Drive WB_IO2 (slot power switch) HIGH via early constructor before the
Arduino framework initializes. Without this, the board brownouts on
POE-only power because the slot MOSFET isn't enabled early enough for
the RAK13800 POE module to deliver power to the baseboard.

Also remove the W5100S hardware reset pulse from Ethernet init — the
chip comes out of power-on reset cleanly, and toggling reset kills
the PHY link which breaks POE power delivery.
2026-03-20 17:56:04 -07:00
Ryan GreggandClaude Opus 4.6 eba2446747 Merge upstream/dev into rak-ethernet
Resolve conflicts: keep both Ethernet CLI and SenseCAP power-off
button code in repeater, keep both Ethernet docs and power management
CLI docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:07:09 -07:00
Ryan GreggandClaude Opus 4.6 88892de864 Fix Ethernet init checking hardwareStatus before begin() and deduplicate CLI code
The Ethernet retry loop in repeater and room server checked
hardwareStatus() and linkStatus() before calling Ethernet.begin(),
which always returned EthernetNoHardware since hardware detection
only happens during begin(). Extract shared Ethernet CLI code into
EthernetCLI.h to prevent future divergence. Also fix time_t type
mismatch in companion radio Ethernet init.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:48:42 -07:00
Ryan GreggandClaude Sonnet 4.6 61ba79966b Address PR review feedback from liamcottle (second round)
- Rename eth command to eth.status for consistency with other commands
- Rename generateDeviceMac to generateEthernetMac for clarity
- Refactor ethernet_handle_command to return false by default
- Allow new TCP clients to replace existing connections (repeater, room server, SerialEthernetInterface)
- Boot companion radio without Ethernet on init failure (LoRa-only recovery mode)
- Remove > prompt from ethernet CLI for consistency with serial interface
- Fix variable redeclaration compile error in SerialEthernetInterface when ETHERNET_STATIC_IP is defined
- Fix TCP socket leak when duplicate client detection fires
- Remove dead recv_queue and adv_restart_time members from SerialEthernetInterface
- Fix port numbers in docs (port 23 for repeater/room server CLI, port 5000 for companion radio)
- Clarify eth.status command is only available in repeater and room server firmware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 12:43:08 -07:00
Ryan Gregg 3e9ceba24a Updates based on PR review feedback from liamcottle 2026-03-10 22:35:31 -07:00
Ryan GreggandClaude Opus 4.6 ffe0853d7f Add Ethernet documentation for RAK4631 ETH support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:58:34 -07:00
Ryan GreggandClaude Opus 4.6 aea64e1f19 Address PR review feedback for Ethernet support
- Add #pragma once to SerialEthernetInterface.h
- Rename TCP_PORT to ETH_TCP_PORT with #ifndef guard
- Fix typos: "initalizing" → "initializing"
- Fix #elif without condition → #else for STM32 block
- Replace infinite loop on ETH init failure with halt()
- Remove heartbeat Serial.print(".") output
- Remove dead beginETH() call (ETH_ENABLED, not RAK_ETH_ENABLE)
- Comment out MESH_DEBUG and ETH_DEBUG_LOGGING build flags

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:43:59 -07:00
Ryan GreggandClaude Opus 4.6 70b51bd096 Add native Ethernet support for RAK4631 repeater, room server, and companion
Add W5100S Ethernet adapter support for RAK4631-based firmware, enabling
TCP CLI access on port 23 as an alternative to BLE/Serial connections.

- New SerialEthernetInterface for nRF52 with DHCP, reconnection handling,
  and shared WB_IO2 power pin management with GPS module
- Ethernet build targets for repeater, room server, and companion firmware
- Prevent GPS from toggling WB_IO2 when Ethernet module is active
- CI build check for all three ETH firmware targets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:43:59 -07:00
Quency-DandGitHub f9b830b5ea Merge pull request #4 from weebl2000/solar-watchdog
Address review comments
2026-03-04 11:11:08 +08:00
Wessel Nieboer 1ab8a6f6da Address review comments 2026-03-04 03:13:53 +01:00
Ben Allfree 9844b541b6 fix: JSON parsing regression in build 2026-02-26 20:37:27 -08:00
Quency-D 53ff4ed57f Fix watchdog code in repeater 2026-02-09 11:43:41 +08:00
Quency-DandGitHub 9df34e09d0 Merge branch 'dev' into solar-watchdog 2026-02-09 10:15:50 +08:00
Quency-DandGitHub 9f38835543 Merge pull request #1 from meshcore-dev/dev
Merge dev branch
2026-01-22 15:06:44 +08:00
Quency-D c89a0e9929 Add an external watchdog module 2026-01-22 15:03:40 +08:00
253 changed files with 6961 additions and 1290 deletions
@@ -25,5 +25,14 @@ runs:
- name: Extract Version from Git Tag
shell: bash
run: |
GIT_TAG_NAME="${GITHUB_REF#refs/tags/}"
echo "GIT_TAG_VERSION=${GIT_TAG_NAME##*-}" >> $GITHUB_ENV
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
# triggered by a tag push (e.g: refs/tags/companion-v1.2.3)
GIT_TAG_NAME="${GITHUB_REF#refs/tags/}"
VERSION_STRING="${GIT_TAG_NAME##*-}"
else
# triggered by a workflow dispatch (e.g: refs/heads/main)
# strip "refs/heads/" prefix and replace any remaining "/" with "-" to protect file paths
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
VERSION_STRING=$(echo "$BRANCH_NAME" | tr '/' '-')
fi
echo "GIT_TAG_VERSION=${VERSION_STRING}" >> $GITHUB_ENV
@@ -10,33 +10,8 @@ on:
- 'companion-*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Build Firmwares
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-companion-firmwares
- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v7
with:
name: companion-firmwares
path: out
- name: Create Release
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
name: Companion Firmware ${{ env.GIT_TAG_VERSION }}
body: ""
draft: true
files: out/*
build-companion-firmwares:
uses: ./.github/workflows/firmware-builder.yml
with:
firmware_type: 'companion'
release_title_prefix: 'Companion Firmware'
+5 -30
View File
@@ -10,33 +10,8 @@ on:
- 'repeater-*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Build Firmwares
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-repeater-firmwares
- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v7
with:
name: repeater-firmwares
path: out
- name: Create Release
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
name: Repeater Firmware ${{ env.GIT_TAG_VERSION }}
body: ""
draft: true
files: out/*
build-repeater-firmwares:
uses: ./.github/workflows/firmware-builder.yml
with:
firmware_type: 'repeater'
release_title_prefix: 'Repeater Firmware'
@@ -10,33 +10,8 @@ on:
- 'room-server-*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Build Firmwares
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-room-server-firmwares
- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v7
with:
name: room-server-firmwares
path: out
- name: Create Release
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
name: Room Server Firmware ${{ env.GIT_TAG_VERSION }}
body: ""
draft: true
files: out/*
build-room-server-firmwares:
uses: ./.github/workflows/firmware-builder.yml
with:
firmware_type: 'room-server'
release_title_prefix: 'Room Server Firmware'
+90
View File
@@ -0,0 +1,90 @@
name: Firmware Builder
on:
workflow_call:
inputs:
firmware_type:
required: true
type: string
release_title_prefix:
required: true
type: string
jobs:
generate-build-matrix:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.get-build-targets.outputs.targets }}
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Get Build Targets
id: get-build-targets
run: |
# get list of firmwares to build
TARGET_LIST=$(/usr/bin/env bash build.sh get-${{ inputs.firmware_type }}-firmwares-to-build)
# convert targets separated by new line into a json array string
JSON_ARRAY=$(echo "$TARGET_LIST" | jq -R -s -c 'split("\n") | map(select(length > 0))')
# use json array as targets result
echo "targets=$JSON_ARRAY" >> $GITHUB_OUTPUT
build:
needs: generate-build-matrix
runs-on: ubuntu-latest
continue-on-error: true # don't fail entire build if one board fails to build
strategy:
matrix:
target: ${{ fromJson(needs.generate-build-matrix.outputs.targets) }}
fail-fast: false # don't cancel other builds if one board fails to build
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Build Firmware
env:
FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }}
run: /usr/bin/env bash build.sh build-firmware ${{ matrix.target }}
- name: Upload Workflow Artifacts
uses: actions/upload-artifact@v7
with:
name: "${{ matrix.target }}"
path: out
create-release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/') # only create release for tagged builds
steps:
- name: Clone Repo
uses: actions/checkout@v6
- name: Setup Build Environment
uses: ./.github/actions/setup-build-environment
- name: Download All Artifacts
uses: actions/download-artifact@v8
with:
merge-multiple: true
path: out
- name: Create Release
uses: softprops/action-gh-release@v3
with:
name: "${{ inputs.release_title_prefix }} ${{ env.GIT_TAG_VERSION }}"
body: ""
draft: true
files: out/*
+3
View File
@@ -31,8 +31,11 @@ jobs:
- Heltec_v3_room_server
# nRF52
- RAK_4631_companion_radio_ble
- RAK_4631_companion_radio_ethernet
- RAK_4631_repeater
- RAK_4631_repeater_ethernet
- RAK_4631_room_server
- RAK_4631_room_server_ethernet
# RP2040
- PicoW_repeater
# STM32
+32
View File
@@ -0,0 +1,32 @@
name: 'Run Stale Bot'
on:
schedule:
- cron: '30 1 * * *' # daily at 1:30am
workflow_dispatch: {}
permissions:
actions: write
issues: write
pull-requests: write
jobs:
close-issues:
# only run on main repo, not forks
if: github.repository == 'meshcore-dev/MeshCore'
runs-on: ubuntu-latest
steps:
- name: Close Stale Issues
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# auto close issues
days-before-issue-stale: 60
days-before-issue-close: 7
exempt-issue-labels: "keep-open"
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity. Remove the stale label or add a comment if this issue is still relevant, otherwise this issue will automatically close in 7 days."
close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale."
# don't auto close prs
days-before-pr-stale: -1
days-before-pr-close: -1
-9
View File
@@ -1,9 +0,0 @@
{
"recommendations": [
"pioarduino.pioarduino-ide",
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
+57
View File
@@ -0,0 +1,57 @@
# Security Policy
## Supported Versions
Security fixes are applied to the latest release only. We do not backport
fixes to older versions.
| Version | Supported |
|---------|-----------|
| 1.15+ | ✅ |
| <1.15 | ❌ |
## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues.**
Use GitHub's private vulnerability reporting instead:
1. Go to the **Security** tab of this repository
2. Click **Report a vulnerability**
3. Fill in the details and submit
### What to include
A useful report tells us:
- Which component or file is affected
- What an attacker can do (impact) and under what conditions
- A minimal reproduction case or proof-of-concept if you have one
- Whether you believe it is remotely exploitable
You do not need a working exploit to report. An incomplete report is better
than no report.
## What to expect
This is a volunteer-maintained open-source project. We will do our best to
respond in a reasonable timeframe, but cannot commit to specific deadlines.
We ask that you give us a fair opportunity to investigate and address the
issue before any public disclosure. If you have not heard back after
**90 days**, feel free to follow up or proceed with disclosure at your
discretion.
## Scope
In scope:
- Remote code execution, memory corruption, or denial-of-service via crafted
radio packets
- Authentication or encryption bypasses
- Vulnerabilities in the packet routing or path handling logic
Out of scope:
- Physical access attacks (e.g., JTAG, UART extraction of keys)
- Regulatory compliance (duty cycle, frequency restrictions)
- Jamming or other physical-layer radio interference
- Issues in third-party libraries (RadioLib, Crypto, etc.) — report those
upstream
- "Best practice" suggestions without a demonstrated attack path
+43
View File
@@ -0,0 +1,43 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_16MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_rc32"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec RC32 (16 MB FLASH, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/",
"vendor": "Heltec"
}
+61
View File
@@ -0,0 +1,61 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A","0x4405"],
["0x239A","0x0029"],
["0x239A","0x002A"],
["0x239A","0x0071"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_tower_v2",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": [
"bluetooth"
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"
],
"name": "Heltec Tower V2 Board",
"upload": {
"maximum_ram_size": 235520,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://heltec.org/",
"vendor": "Heltec"
}
+2 -2
View File
@@ -9,7 +9,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
@@ -40,4 +40,4 @@
},
"url": "https://heltec.org/",
"vendor": "heltec"
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_16MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_v4_r8"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "heltec_wifi_lora_32 v4 r8 (16 MB FLASH, 8 MB PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org/",
"vendor": "heltec"
}
+42
View File
@@ -0,0 +1,42 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-D BOARD_HAS_PSRAM",
"-D ARDUINO_USB_CDC_ON_BOOT=0",
"-D ARDUINO_USB_MODE=0",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=0"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qio_opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "ELECROW-ThinkNode-M7"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "ELECROW ThinkNode M7",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 524288,
"maximum_size": 8388608,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.elecrow.com",
"vendor": "ELECROW"
}
+57
View File
@@ -0,0 +1,57 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=0",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qio_opi",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "ELECROW-ThinkNode-M9"
},
"connectivity": [
"wifi",
"bluetooth",
"lora"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "elecrow-thinknode-m9",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 524288,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html",
"vendor": "ELECROW"
}
+11 -1
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
# exit when any command fails
set -e
global_usage() {
cat - <<EOF
Usage:
@@ -93,7 +96,7 @@ get_pio_envs_ending_with_string() {
# $1 should be the environment name
get_platform_for_env() {
local env_name=$1
echo "$PIO_CONFIG_JSON" | python3 -c "
printf '%s' "$PIO_CONFIG_JSON" | python3 -c "
import sys, json, re
data = json.load(sys.stdin)
for section, options in data:
@@ -275,4 +278,11 @@ elif [[ $1 == "build-repeater-firmwares" ]]; then
build_repeater_firmwares
elif [[ $1 == "build-room-server-firmwares" ]]; then
build_room_server_firmwares
elif [[ $1 == "get-companion-firmwares-to-build" ]]; then
get_pio_envs_ending_with_string "_companion_radio_usb"
get_pio_envs_ending_with_string "_companion_radio_ble"
elif [[ $1 == "get-repeater-firmwares-to-build" ]]; then
get_pio_envs_ending_with_string "_repeater"
elif [[ $1 == "get-room-server-firmwares-to-build" ]]; then
get_pio_envs_ending_with_string "_room_server"
fi
+4 -3
View File
@@ -1,11 +1,12 @@
{ pkgs ? import <nixpkgs> {} }:
let
{pkgs ? import <nixpkgs> {}}: let
in
pkgs.mkShell {
buildInputs = [
pkgs.platformio
pkgs.python3
pkgs.gcc
pkgs.gtest
# optional: needed as a programmer i.e. for esp32
pkgs.avrdude
];
}
}
+76 -1
View File
@@ -19,6 +19,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- [GPS](#gps-when-gps-support-is-compiled-in)
- [Sensors](#sensors-when-sensor-support-is-compiled-in)
- [Bridge](#bridge-when-bridge-support-is-compiled-in)
- [Ethernet](#ethernet-when-ethernet-support-is-compiled-in)
---
@@ -28,12 +29,25 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Usage:**
- `reboot`
**Note:** No reply is sent.
---
### Power-off the node
**Usage:**
- `poweroff`, or
- `shutdown`
**Note:** No reply is sent.
---
### Reset the clock and reboot
**Usage:**
- `clkreboot`
**Note:** No reply is sent.
---
### Sync the clock with the remote device
@@ -263,6 +277,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### View or change the LoRa FEM receive-path gain state on supported boards
**Usage:**
- `get radio.fem.rxgain`
- `set radio.fem.rxgain <state>`
**Parameters:**
- `state`: `on`|`off`
**Notes:**
- This controls the external LoRa FEM receive-path LNA where the board supports it.
- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode.
---
### System
#### View or change this node's name
@@ -564,6 +592,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### Enable or disable hardware Channel Activity Detection (CAD)
**Usage:**
- `get cad`
- `set cad <on|off>`
**Description:** When enabled, the radio performs a hardware Channel Activity Detection scan before transmitting and defers if the channel is busy. Runs independently of `int.thresh` — either, both, or none may be active.
**Parameters:**
- `on|off`: Enable or disable hardware CAD
**Default:** `off`
---
#### View or change the AGC Reset Interval
**Usage:**
- `get agc.reset.interval`
@@ -632,10 +674,21 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `value`: Maximum flood hop count (0-64) for a packet without a scope (no region set)
**Default:** `0xFF` - indicates it hasn't been set, will track flood.max until it is.
**Default:** `64` - (`0xFF` indicates it hasn't been set, will track flood.max until it is.)
**Note:** An alternative to `region denyf *`, setting `flood.max.unscoped` to a lower value such as `3` would allow for local unscoped messages to propagate, while preventing noisy neighbors from flooding a local region.
---
#### Limit the number of hops for an advert flood message
**Usage:**
- `get flood.max.advert`
- `set flood.max.advert <value>`
**Parameters:**
- `value`: Maximum flood hop count (0-64) for an advert packet
**Default:** `8`
---
@@ -1100,3 +1153,25 @@ region save
**Note:** Returns an error on boards without power management support.
---
### Ethernet (when Ethernet support is compiled in)
Ethernet support is available on RAK4631 boards with a RAK13800 (W5100S) Ethernet module. Use the `_ethernet` firmware variants (e.g. `RAK_4631_repeater_ethernet`) to enable this feature.
---
#### View Ethernet connection status
**Usage:**
- `eth.status`
**Output:**
- `ETH: <ip>:<port>` when connected (e.g. `ETH: 192.168.1.50:23`)
- `ETH: not connected` when Ethernet is not active
**Notes:**
- Available on repeater and room server firmware only. Companion radio ethernet firmware does not expose a CLI.
- The Ethernet interface obtains an IP address via DHCP automatically on boot.
- A TCP server listens on port 23 (default) for CLI connections.
- Connect with any TCP client (e.g. `nc`, PuTTY) to access the same CLI available over serial.
---
+27
View File
@@ -83,6 +83,7 @@ A list of frequently-asked questions and answers for MeshCore
- [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code)
- [7.6. Q: How do I connect to the companion via Wi-Fi, e.g. using a Heltec V3?](#76-q-how-do-i-connect-to-the-companion-via-wi-fi-eg-using-a-heltec-v3)
- [7.7. Q: I have a Station G2, or a Heltec V4, or an Ikoka Stick, or a radio with an EByte E22-900M30S or an EByte E22-900M33S module, what should their transmit power be set to?](#77-q-i-have-a-station-g2-or-a-heltec-v4-or-an-ikoka-stick-or-a-radio-with-an-ebyte-e22-900m30s-or-an-ebyte-e22-900m33s-module-what-should-their-transmit-power-be-set-to)
- [7.8. Q: How do I use Ethernet with a RAK4631?](#78-q-how-do-i-use-ethernet-with-a-rak4631)
## 1. Introduction
@@ -820,3 +821,29 @@ For companion radios, you can set these radios' transmit power in the smartphone
| **Ikoka Stick E22-900M33S** | 2W Model | 9 dBm | 2W | **DO NOT EXCEED** (Risk of burn out) [data sheet](https://www.cdebyte.com/pdf-down.aspx?id=4216) Refer to your local government's requirements |
| **Heltec V4** | Standard Output | 10 dBm | 22 dBm (~0.15W) | |
| | High Output | 22 dBm | 28 dBm (~0.5W to 0.6W) | |
---
### 7.8. Q: How do I use Ethernet with a RAK4631?
**A:**
MeshCore supports Ethernet on RAK4631 boards using the [RAK13800](https://docs.rakwireless.com/product-categories/wisblock/rak13800/datasheet/) WisBlock Ethernet module (based on the W5100S chip).
**Hardware required:**
- RAK4631 WisBlock Core
- RAK19007 or RAK19018 WisBlock Base Board (with an available IO slot)
- RAK13800 WisBlock Ethernet module
- Ethernet cable connected to a network with a DHCP server
**Firmware:**
Flash one of the Ethernet-enabled firmware variants:
- `RAK_4631_repeater_ethernet` - Repeater with Ethernet CLI access
- `RAK_4631_room_server_ethernet` - Room server with Ethernet CLI access
- `RAK_4631_companion_radio_ethernet` - Companion radio over Ethernet (replaces BLE)
**Connecting:**
- The device obtains an IP address via DHCP automatically on boot.
- For repeaters and room servers, connect to the device on TCP port 23 using any TCP client (e.g. `nc <ip> 23` or PuTTY in raw mode). This gives you the same CLI available over serial/USB.
- For companion radio firmware, the Ethernet interface replaces BLE as the transport to companion apps. Connect on TCP port 5000 (same as the WiFi companion radio).
- Use the `eth.status` CLI command to check connection status and see the assigned IP address.
---
+2
View File
@@ -17,6 +17,8 @@ Once you have a working app/project, you need to be able to demonstrate it exist
| 0000 - 00FF | -reserved for internal use- | |
| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open |
| 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz |
| 0120 | MCO Advanced | most.original.address@gmail.com — https://hdden.ru/MCOa/ |
| 0130 - 013F | StreamSensor | william@housedillon.com - https://housedillon.com/blog/lora-e5-with-seeed-fusion |
| FF00 - FFFF | -reserved for testing/dev- | |
(add rows, inside the range 0100 - FEFF for custom apps)
+3 -1
View File
@@ -12,8 +12,10 @@ meshcore://channel/add?name=Public&secret=8b3387e9c5cdea6ac9e5edbaa115cd72
**Parameters**:
- `name`: Channel name (URL-encoded if needed)
- `name`: Channel name (URL-encoded)
- `secret`: 16-byte secret represented as 32 hex characters
- `region_scope`: Region Scope (optional, URL-encoded if provided)
- Supported by MeshCore App v1.47.0+
## Add Contact
+1 -1
View File
@@ -545,7 +545,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
uint32_t pos = 0, found_pos = 0;
uint32_t min_timestamp = 0xFFFFFFFF;
// search for matching key OR evict by oldest timestmap
// search for matching key OR evict by oldest timestamp
BlobRec tmp;
file.seek(0);
while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) {
+17 -10
View File
@@ -261,6 +261,9 @@ float MyMesh::getAirtimeBudgetFactor() const {
int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved
}
bool MyMesh::getCADEnabled() const {
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
}
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
if (_prefs.rx_delay_base <= 0.0f) return 0;
@@ -654,6 +657,11 @@ uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_tim
// query other sensors -- target specific
sensors.querySensors(permissions, telemetry);
float temperature = board.getMCUTemperature();
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
}
memcpy(reply, &sender_timestamp,
4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
@@ -854,7 +862,7 @@ void MyMesh::onSendTimeout() {}
MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui)
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables),
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) {
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
_iter_started = false;
_cli_rescue = false;
offline_queue_len = 0;
@@ -1542,6 +1550,7 @@ void MyMesh::handleCmdFrame(size_t len) {
memcpy(anon.id.pub_key, pub_key, PUB_KEY_SIZE);
anon.out_path_len = 0; // default to zero-hop direct
anon.type = ADV_TYPE_NONE; // unknown
anon.lastmod = getRTCClock()->getCurrentTime();
if (addContact(anon)) recipient = &anon;
}
@@ -1636,6 +1645,11 @@ void MyMesh::handleCmdFrame(size_t len) {
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len == 4) { // 'self' telemetry request
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
float temperature = board.getMCUTemperature();
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
}
// query other sensors -- target specific
sensors.querySensors(0xFF, telemetry);
@@ -1981,6 +1995,7 @@ void MyMesh::handleCmdFrame(size_t len) {
sendPacket(pkt, priority, 0);
writeOKFrame();
} else {
releasePacket(pkt);
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else {
@@ -2186,15 +2201,7 @@ void MyMesh::checkSerialInterface() {
&& !_serial->isWriteBusy() // don't spam the Serial Interface too quickly!
) {
ContactInfo contact;
bool found = false;
while (_iter.hasNext(this, contact)) {
if (contact.type != ADV_TYPE_NONE) {
found = true;
break;
}
}
if (found) {
if (_iter.hasNext(this, contact)) {
if (contact.lastmod > _iter_filter_since) { // apply the 'since' filter
writeContactRespFrame(RESP_CODE_CONTACT, contact);
if (contact.lastmod > _most_recent_lastmod) {
+1
View File
@@ -105,6 +105,7 @@ public:
protected:
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
+35 -9
View File
@@ -18,13 +18,13 @@ static uint32_t _atoi(const char* sp) {
#include <CustomLFS_QSPIFlash.h>
DataStore store(InternalFS, QSPIFlash, rtc_clock);
#else
#if defined(EXTRAFS)
#include <CustomLFS.h>
CustomLFS ExtraFS(0xD4000, 0x19000, 128);
DataStore store(InternalFS, ExtraFS, rtc_clock);
#else
DataStore store(InternalFS, rtc_clock);
#endif
#if defined(EXTRAFS)
#include <CustomLFS.h>
CustomLFS ExtraFS(0xD4000, 0x19000, 128);
DataStore store(InternalFS, ExtraFS, rtc_clock);
#else
DataStore store(InternalFS, rtc_clock);
#endif
#endif
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
@@ -74,6 +74,9 @@ static uint32_t _atoi(const char* sp) {
#ifdef BLE_PIN_CODE
#include <helpers/nrf52/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#elif defined(ETHERNET_ENABLED)
#include <helpers/nrf52/SerialEthernetInterface.h>
SerialEthernetInterface serial_interface;
#else
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
@@ -113,9 +116,12 @@ void halt() {
void setup() {
Serial.begin(115200);
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS
DisplayDriver* disp = NULL;
if (display.begin()) {
@@ -158,10 +164,23 @@ void setup() {
#ifdef BLE_PIN_CODE
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
the_mesh.startInterface(serial_interface);
#elif defined(ETHERNET_ENABLED)
Serial.print("Waiting for serial to connect...\n");
unsigned long timeout = millis();
while (!Serial) {
if ((millis() - timeout) < 5000) { delay(100); } else { break; }
}
Serial.println("Initializing Ethernet adapter...");
if (serial_interface.begin()) {
the_mesh.startInterface(serial_interface);
} else {
Serial.println("ETH: Init failed, continuing without Ethernet (mesh only)");
}
#else
serial_interface.begin(Serial);
#endif
the_mesh.startInterface(serial_interface);
#endif
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
store.begin();
@@ -249,6 +268,13 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
#ifdef ETHERNET_ENABLED
serial_interface.loop();
#endif
if (!the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
+58 -26
View File
@@ -53,23 +53,24 @@ public:
int render(DisplayDriver& display) override {
// meshcore logo
display.setColor(DisplayDriver::BLUE);
display.setColor(UIColor::corp_blue);
int logoWidth = 128;
display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// meshcore website
const char* website = "https://meshcore.io";
display.setColor(DisplayDriver::LIGHT);
display.setColor(UIColor::primary_txt);
display.setTextSize(1);
uint16_t websiteWidth = display.getTextWidth(website);
display.setCursor((display.width() - websiteWidth) / 2, 22);
display.print(website);
// version info
display.setColor(DisplayDriver::LIGHT);
display.setColor(UIColor::primary_txt);
display.setTextSize(1);
display.drawTextCentered(display.width()/2, 35, _version_info);
display.setColor(UIColor::secondary_txt);
display.setTextSize(1);
display.drawTextCentered(display.width()/2, 48, FIRMWARE_BUILD_DATE);
@@ -128,7 +129,7 @@ class HomeScreen : public UIScreen {
int iconHeight = 10;
int iconX = display.width() - iconWidth - 5; // Position the icon near the top-right corner
int iconY = 0;
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::title_txt);
// battery outline
display.drawRect(iconX, iconY, iconWidth, iconHeight);
@@ -143,7 +144,7 @@ class HomeScreen : public UIScreen {
// show muted icon if buzzer is muted
#ifdef PIN_BUZZER
if (_task->isBuzzerQuiet()) {
display.setColor(DisplayDriver::RED);
display.setColor(UIColor::warning_txt);
display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8);
}
#endif
@@ -188,34 +189,41 @@ public:
}
int render(DisplayDriver& display) override {
display.setColor(UIColor::title_bkg);
display.fillRect(0, 0, display.width(), 12);
char tmp[80];
// node name
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::title_txt);
char filtered_name[sizeof(_node_prefs->node_name)];
display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
display.setCursor(0, 0);
display.setCursor(0, 2);
display.print(filtered_name);
// battery voltage
renderBatteryIndicator(display, _task->getBattMilliVolts());
// curr page indicator
if (UIColor::title_bkg == UIColor::window_bkg) {
display.setColor(UIColor::title_txt);
} else {
display.setColor(UIColor::title_bkg);
}
int y = 14;
int x = display.width() / 2 - 5 * (HomePage::Count-1);
for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) {
if (i == _page) {
display.fillRect(x-1, y-1, 3, 3);
display.fillRect(x-1, y-1, 4, 4);
} else {
display.fillRect(x, y, 1, 1);
display.fillRect(x, y, 2, 2);
}
}
if (_page == HomePage::FIRST) {
display.setColor(DisplayDriver::YELLOW);
display.setColor(UIColor::primary_txt);
display.setTextSize(2);
sprintf(tmp, "MSG: %d", _task->getMsgCount());
display.drawTextCentered(display.width() / 2, 20, tmp);
display.drawTextCentered(display.width() / 2, 22, tmp);
#ifdef WIFI_SSID
IPAddress ip = WiFi.localIP();
@@ -224,19 +232,19 @@ public:
display.drawTextCentered(display.width() / 2, 54, tmp);
#endif
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::warning_txt);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 43, "< Connected >");
} else if (the_mesh.getBLEPin() != 0) { // BT pin
display.setColor(DisplayDriver::RED);
display.setColor(UIColor::warning_txt);
display.setTextSize(2);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
display.drawTextCentered(display.width() / 2, 43, tmp);
}
} else if (_page == HomePage::RECENT) {
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::primary_txt);
int y = 20;
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
auto a = &recent[i];
@@ -260,7 +268,7 @@ public:
display.print(tmp);
}
} else if (_page == HomePage::RADIO) {
display.setColor(DisplayDriver::YELLOW);
display.setColor(UIColor::primary_txt);
display.setTextSize(1);
// freq / sf
display.setCursor(0, 20);
@@ -279,15 +287,17 @@ public:
sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor());
display.print(tmp);
} else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
display.drawXbm((display.width() - 32) / 2, 18,
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
32, 32);
display.setColor(UIColor::secondary_txt);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL);
} else if (_page == HomePage::ADVERT) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
display.setColor(UIColor::secondary_txt);
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
#if ENV_INCLUDE_GPS == 1
} else if (_page == HomePage::GPS) {
@@ -305,24 +315,33 @@ public:
#else
strcpy(buf, gps_state ? "gps on" : "gps off");
#endif
display.setColor(UIColor::primary_txt);
display.drawTextLeftAlign(0, y, buf);
if (nmea == NULL) {
y = y + 12;
display.setColor(UIColor::secondary_txt);
display.drawTextLeftAlign(0, y, "Can't access GPS");
} else {
display.setColor(UIColor::primary_txt);
strcpy(buf, nmea->isValid()?"fix":"no fix");
display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12;
display.setColor(UIColor::secondary_txt);
display.drawTextLeftAlign(0, y, "sat");
display.setColor(UIColor::primary_txt);
sprintf(buf, "%d", nmea->satellitesCount());
display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12;
display.setColor(UIColor::secondary_txt);
display.drawTextLeftAlign(0, y, "pos");
display.setColor(UIColor::primary_txt);
sprintf(buf, "%.4f %.4f",
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12;
display.setColor(UIColor::secondary_txt);
display.drawTextLeftAlign(0, y, "alt");
display.setColor(UIColor::primary_txt);
sprintf(buf, "%.2f", nmea->getAltitude()/1000.);
display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12;
@@ -390,7 +409,9 @@ public:
strcpy(name, "unk"); sprintf(buf, "");
}
display.setCursor(0, y);
display.setColor(UIColor::secondary_txt);
display.print(name);
display.setColor(UIColor::primary_txt);
display.setCursor(
display.width()-display.getTextWidth(buf)-1, y
);
@@ -401,11 +422,13 @@ public:
else sensors_scroll_offset = 0;
#endif
} else if (_page == HomePage::SHUTDOWN) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
display.setTextSize(1);
if (_shutdown_init) {
display.setColor(UIColor::warning_txt);
display.drawTextCentered(display.width() / 2, 34, "hibernating...");
} else {
display.setColor(UIColor::secondary_txt);
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
}
@@ -498,7 +521,7 @@ public:
char tmp[16];
display.setCursor(0, 0);
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
sprintf(tmp, "Unread: %d", num_unread);
display.print(tmp);
@@ -518,13 +541,13 @@ public:
display.drawRect(0, 11, display.width(), 1); // horiz line
display.setCursor(0, 14);
display.setColor(DisplayDriver::YELLOW);
display.setColor(UIColor::secondary_txt);
char filtered_origin[sizeof(p->origin)];
display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin));
display.print(filtered_origin);
display.setCursor(0, 25);
display.setColor(DisplayDriver::LIGHT);
display.setColor(UIColor::primary_txt);
char filtered_msg[sizeof(p->msg)];
display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg));
display.printWordWrap(filtered_msg, display.width());
@@ -697,8 +720,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
_display->turnOff();
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
@@ -748,6 +770,16 @@ void UITask::loop() {
c = handleTripleClick(KEY_SELECT);
}
#endif
#if defined(UI_HAS_ROTARY_INPUT)
RotaryInputEvent rotaryEv = rotary_input.poll();
if (c == 0 && _display != NULL && _display->isOn()) {
if (rotaryEv == RotaryInputEvent::Next) {
c = KEY_NEXT;
} else if (rotaryEv == RotaryInputEvent::Prev) {
c = KEY_PREV;
}
}
#endif
#if defined(PIN_USER_BTN_ANA)
if (abs(millis() - _analogue_pin_read_millis) > 10) {
int ev = analog_btn.check();
@@ -797,9 +829,9 @@ void UITask::loop() {
_display->setTextSize(1);
int y = _display->height() / 3;
int p = _display->height() / 32;
_display->setColor(DisplayDriver::DARK);
_display->setColor(UIColor::popup_bkg);
_display->fillRect(p, y, _display->width() - p*2, y);
_display->setColor(DisplayDriver::LIGHT); // draw box border
_display->setColor(UIColor::popup_txt); // draw box border
_display->drawRect(p, y, _display->width() - p*2, y);
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
@@ -836,7 +868,7 @@ void UITask::loop() {
if (_display != NULL) {
_display->startFrame();
_display->setTextSize(2);
_display->setColor(DisplayDriver::RED);
_display->setColor(UIColor::warning_txt);
_display->drawTextCentered(_display->width() / 2, 20, "Low Battery.");
_display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!");
_display->endFrame();
+14 -15
View File
@@ -167,7 +167,7 @@ void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) {
int iconHeight = 12;
int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner
int iconY = 0;
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
// battery outline
_display->drawRect(iconX, iconY, iconWidth, iconHeight);
@@ -188,7 +188,7 @@ void UITask::renderCurrScreen() {
_display->setTextSize(1.4);
uint16_t textWidth = _display->getTextWidth(_alert);
_display->setCursor((_display->width() - textWidth) / 2, 22);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::warning_txt);
_display->print(_alert);
_alert[0] = 0;
_need_refresh = true;
@@ -197,30 +197,29 @@ void UITask::renderCurrScreen() {
// render message preview
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
_display->print(_node_prefs->node_name);
_display->setCursor(0, 12);
_display->setColor(DisplayDriver::YELLOW);
_display->setColor(UIColor::secondary_txt);
_display->print(_origin);
_display->setCursor(0, 24);
_display->setColor(DisplayDriver::LIGHT);
_display->print(_msg);
_display->setCursor(_display->width() - 28, 9);
_display->setTextSize(2);
_display->setColor(DisplayDriver::ORANGE);
_display->setColor(UIColor::primary_txt);
sprintf(tmp, "%d", _msgcount);
_display->print(tmp);
_display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114
_display->setColor(UIColor::secondary_txt); // last color will be kept on T114
} else if ((millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
_display->setColor(UIColor::corp_blue);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setColor(UIColor::primary_txt);
_display->setTextSize(1);
uint16_t textWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - textWidth) / 2, 22);
@@ -229,7 +228,7 @@ void UITask::renderCurrScreen() {
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
_display->print(_node_prefs->node_name);
// battery voltage
@@ -237,7 +236,7 @@ void UITask::renderCurrScreen() {
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
_display->setColor(UIColor::secondary_txt);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
@@ -248,14 +247,14 @@ void UITask::renderCurrScreen() {
// BT pin
if (!_connected && the_mesh.getBLEPin() != 0) {
_display->setColor(DisplayDriver::RED);
_display->setColor(UIColor::warning_txt);
_display->setTextSize(2);
_display->setCursor(0, 43);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
_display->print(tmp);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
} else {
_display->setColor(DisplayDriver::LIGHT);
_display->setColor(UIColor::primary_txt);
}
}
_need_refresh = false;
@@ -307,7 +306,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
@@ -104,7 +104,7 @@ public:
if (_status[0] == 0) return;
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::primary_txt);
// if (_needs_redraw) {
// _text_width = display.getTextWidth(_status);
+19 -15
View File
@@ -56,24 +56,24 @@ public:
int render(DisplayDriver& display) override {
if (millis() < version_after) {
// meshcore logo
display.setColor(DisplayDriver::BLUE);
display.setColor(UIColor::corp_blue);
int logoWidth = 72;
display.drawXbm(0, 0, meshcore_logo, 72, 36);
} else {
// meshcore website
const char* website = "meshcore.io";
display.setColor(DisplayDriver::LIGHT);
display.setColor(UIColor::primary_txt);
display.setTextSize(1);
uint16_t websiteWidth = display.getTextWidth(website);
display.setCursor((display.width() - websiteWidth) / 2, 9);
display.print(website);
// version info
display.setColor(DisplayDriver::LIGHT);
display.setTextSize(1);
display.drawTextCentered(display.width()/2, 18, _version_info);
display.setColor(UIColor::secondary_txt);
display.setTextSize(1);
display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE);
}
@@ -163,7 +163,7 @@ public:
// display.print(filtered_name);
display.setColor(DisplayDriver::YELLOW);
display.setColor(UIColor::primary_txt);
display.setTextSize(2);
sprintf(tmp, "MSG: %d", _task->getMsgCount());
display.setCursor(0, 10);
@@ -180,19 +180,19 @@ public:
display.drawTextCentered(display.width() / 2, 54, tmp);
#endif
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::warning_txt);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >");
} else if (the_mesh.getBLEPin() != 0) { // BT pin
display.setColor(DisplayDriver::RED);
display.setColor(UIColor::warning_txt);
display.setTextSize(2);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
display.drawTextCentered(display.width() / 2, display.height()-8, tmp);
}
} else if (_page == HomePage::RECENT) {
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::primary_txt);
int y = 8;
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
auto a = &recent[i];
@@ -216,7 +216,7 @@ public:
display.print(tmp);
}
} else if (_page == HomePage::RADIO) {
display.setColor(DisplayDriver::YELLOW);
display.setColor(UIColor::primary_txt);
display.setTextSize(1);
// frequency and spreading factor
display.setCursor(0, 8);
@@ -238,14 +238,14 @@ public:
display.drawTextRightAlign(display.width(), 26, tmp);
} else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
display.drawXbm((display.width() - 32) / 2, 8,
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
32, 32);
display.setTextSize(1);
// display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL);
} else if (_page == HomePage::ADVERT) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::corp_blue);
display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32);
// display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL);
#if ENV_INCLUDE_GPS == 1
@@ -264,9 +264,11 @@ public:
#else
strcpy(buf, gps_state ? "gps on" : "gps off");
#endif
display.setColor(UIColor::primary_txt);
display.drawTextLeftAlign(0, y, buf);
if (nmea == NULL) {
// y = y + 8;
display.setColor(UIColor::warning_txt);
display.drawTextLeftAlign(0, y, "Can't access GPS");
} else {
if (!gps_state || !nmea->isValid()) {
@@ -274,6 +276,7 @@ public:
} else {
sprintf(buf, "%d sat", nmea->satellitesCount());
}
display.setColor(UIColor::primary_txt);
display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 8;
sprintf(buf, "lat %.4f",
@@ -349,8 +352,10 @@ public:
r.skipData(type);
strcpy(name, "unk"); sprintf(buf, "");
}
display.setColor(UIColor::secondary_txt);
display.setCursor(0, y);
display.print(name);
display.setColor(UIColor::primary_txt);
display.setCursor(
display.width()-display.getTextWidth(buf)-1, y
);
@@ -361,7 +366,7 @@ public:
else sensors_scroll_offset = 0;
#endif
} else if (_page == HomePage::SHUTDOWN) {
display.setColor(DisplayDriver::GREEN);
display.setColor(UIColor::secondary_txt);
display.setTextSize(1);
if (_shutdown_init) {
display.drawTextCentered(display.width() / 2, 20, "hibernating...");
@@ -566,8 +571,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
_display->turnOff();
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
@@ -693,9 +697,9 @@ void UITask::loop() {
_display->setTextSize(1);
int y = _display->height() / 3;
int p = _display->height() / 32;
_display->setColor(DisplayDriver::DARK);
_display->setColor(UIColor::popup_bkg);
_display->fillRect(p, y, _display->width() - p*2, y);
_display->setColor(DisplayDriver::LIGHT); // draw box border
_display->setColor(UIColor::popup_txt); // draw box border
_display->drawRect(p, y, _display->width() - p*2, y);
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
+8 -8
View File
@@ -549,8 +549,7 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
return getRNG()->nextInt(0, 5*t + 1);
}
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
@@ -562,8 +561,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
} else {
recv_pkt_region = NULL;
}
// do normal processing
return false;
return Mesh::onRecvPacket(pkt);
}
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
@@ -867,6 +865,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
set_radio_at = revert_radio_at = 0;
_logging = false;
region_load_active = false;
recv_pkt_region = NULL;
#if MAX_NEIGHBOURS
memset(neighbours, 0, sizeof(neighbours));
@@ -893,6 +892,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_max_unscoped = 64;
_prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// bridge defaults
_prefs.bridge_enabled = 1; // enabled
@@ -917,6 +917,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_boosted_gain = 1; // enabled by default;
#endif
#endif
_prefs.radio_fem_rxgain = 1;
pending_discover_tag = 0;
pending_discover_until = 0;
@@ -965,6 +966,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
@@ -1059,11 +1061,9 @@ void MyMesh::setTxPower(int8_t power_dbm) {
radio_driver.setTxPower(power_dbm);
}
#if defined(USE_SX1262) || defined(USE_SX1268)
void MyMesh::setRxBoostedGain(bool enable) {
radio_driver.setRxBoostedGainMode(enable);
bool MyMesh::setRxBoostedGain(bool enable) {
return radio_driver.setRxBoostedGainMode(enable);
}
#endif
void MyMesh::formatNeighborsReply(char *reply) {
char *dp = reply;
+7 -4
View File
@@ -11,6 +11,7 @@
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
using File = fs::File;
#endif
#ifdef WITH_RS232_BRIDGE
@@ -150,6 +151,9 @@ protected:
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -163,7 +167,7 @@ protected:
}
#endif
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
int searchPeersByHash(const uint8_t* hash) override;
@@ -249,7 +253,6 @@ public:
// To check if there is pending work
bool hasPendingWork() const;
#if defined(USE_SX1262) || defined(USE_SX1268)
void setRxBoostedGain(bool enable) override;
#endif
bool setRxBoostedGain(bool enable) override;
};
+56 -32
View File
@@ -1,4 +1,5 @@
#include "UITask.h"
#include "target.h"
#include <Arduino.h>
#include <helpers/CommonCLI.h>
@@ -9,6 +10,8 @@
#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
#define POWEROFF_DELAY 3000
// 'meshcore', 128x13px
static const uint8_t meshcore_logo [] PROGMEM = {
0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe,
@@ -29,9 +32,14 @@ static const uint8_t meshcore_logo [] PROGMEM = {
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
_prevBtnState = HIGH;
_auto_off = millis() + AUTO_OFF_MILLIS;
_started_at = millis();
_node_prefs = node_prefs;
_display->turnOn();
#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
user_btn.begin();
#endif
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(firmware_version);
@@ -41,47 +49,56 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
char tmp[80];
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
if (millis() < _started_at + BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
_display->setColor(UIColor::corp_blue);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// meshcore website
const char* website = "https://meshcore.io";
_display->setColor(DisplayDriver::LIGHT);
_display->setColor(UIColor::primary_txt);
_display->setTextSize(1);
uint16_t websiteWidth = _display->getTextWidth(website);
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
_display->print(website);
_display->drawTextCentered(_display->width() / 2, 22, website);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 35);
_display->print(_version_info);
_display->drawTextCentered(_display->width() / 2, 35, _version_info);
// node type
const char* node_type = "< Repeater >";
uint16_t typeWidth = _display->getTextWidth(node_type);
_display->setCursor((_display->width() - typeWidth) / 2, 48);
_display->print(node_type);
} else { // home screen
// node name
_display->drawTextCentered(_display->width() / 2, 48, node_type);
} else if (_powering_off_at > 0) {
// meshcore logo
_display->setColor(UIColor::corp_blue);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// meshcore website
const char* website = "https://meshcore.io";
_display->setColor(UIColor::primary_txt);
_display->setTextSize(1);
_display->drawTextCentered(_display->width()/ 2, 22, website);
// Powering off
const char* poweroff_string = "Turning OFF";
uint16_t poffWidth = _display->getTextWidth(poweroff_string);
_display->setCursor((_display->width() - poffWidth) / 2, 48);
_display->drawTextCentered(_display->width()/2, 48, poweroff_string);
} else {
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
_display->print(_node_prefs->node_name);
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
@@ -93,21 +110,19 @@ void UITask::renderCurrScreen() {
}
void UITask::loop() {
#ifdef PIN_USER_BTN
if (millis() >= _next_read) {
int btnState = digitalRead(PIN_USER_BTN);
if (btnState != _prevBtnState) {
if (btnState == USER_BTN_PRESSED) { // pressed?
if (_display->isOn()) {
// TODO: any action ?
} else {
_display->turnOn();
}
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
}
_prevBtnState = btnState;
#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
int ev = user_btn.check();
if (ev == BUTTON_EVENT_CLICK) {
if (_display->isOn()) {
// TODO: any action ?
} else {
_display->turnOn();
}
_next_read = millis() + 200; // 5 reads per second
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
_display->turnOn();
Serial.println("Powering Off");
_powering_off_at = millis() + POWEROFF_DELAY;
}
#endif
@@ -123,4 +138,13 @@ void UITask::loop() {
_display->turnOff();
}
}
if (_powering_off_at > 0) { // power off timer armed
#ifdef LED_PIN
digitalWrite(LED_PIN, LED_STATE_ON); // switch on the led until poweroff
#endif
if (millis() > _powering_off_at) {
_board->powerOff(); // should not return
}
}
}
+4 -1
View File
@@ -4,15 +4,18 @@
#include <helpers/CommonCLI.h>
class UITask {
mesh::MainBoard* _board;
DisplayDriver* _display;
unsigned long _next_read, _next_refresh, _auto_off;
int _prevBtnState;
NodePrefs* _node_prefs;
char _version_info[32];
unsigned long _powering_off_at = 0;
unsigned long _started_at = 0;
void renderCurrScreen();
public:
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
UITask(mesh::MainBoard& board, DisplayDriver& display) : _board(&board), _display(&display) { _next_read = _next_refresh = 0; }
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop();
+45 -2
View File
@@ -5,7 +5,12 @@
#ifdef DISPLAY_CLASS
#include "UITask.h"
static UITask ui_task(display);
static UITask ui_task(board, display);
#endif
#ifdef ETHERNET_ENABLED
#define ETHERNET_CLI_BANNER "MeshCore Repeater CLI"
#include <helpers/nrf52/EthernetCLI.h>
#endif
StdRNG fast_rng;
@@ -18,6 +23,9 @@ void halt() {
}
static char command[160];
#ifdef ETHERNET_ENABLED
static char ethernet_command[160];
#endif
// For power saving
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
@@ -33,6 +41,10 @@ void setup() {
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
// give some extra time for serial to settle so
// boot debug messages can be seen on terminal
@@ -86,6 +98,9 @@ void setup() {
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
command[0] = 0;
#ifdef ETHERNET_ENABLED
ethernet_command[0] = 0;
#endif
sensors.begin();
@@ -95,6 +110,10 @@ void setup() {
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
#ifdef ETHERNET_ENABLED
ethernet_start_task();
#endif
// send out initial zero hop Advertisement to the mesh
#if ENABLE_ADVERT_ON_BOOT == 1
the_mesh.sendSelfAdvertisement(16000, false);
@@ -104,6 +123,7 @@ void setup() {
}
void loop() {
// Handle Serial CLI
int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read();
@@ -122,7 +142,14 @@ void loop() {
Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
reply[0] = 0;
#ifdef ETHERNET_ENABLED
if (!ethernet_handle_command(command, reply)) {
the_mesh.handleCommand(0, command, reply);
}
#else
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
#endif
if (reply[0]) {
Serial.print(" -> "); Serial.println(reply);
}
@@ -130,7 +157,20 @@ void loop() {
command[0] = 0; // reset command buffer
}
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
#ifdef ETHERNET_ENABLED
ethernet_loop_maintain();
if (ethernet_read_line(ethernet_command, sizeof(ethernet_command))) {
char reply[160];
reply[0] = 0;
if (!ethernet_handle_command(ethernet_command, reply)) {
the_mesh.handleCommand(0, ethernet_command, reply);
}
ethernet_send_reply(reply);
ethernet_command[0] = 0;
}
#endif
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) && !defined(DISPLAY_CLASS)
// Hold the user button to power off the SenseCAP Solar repeater.
int btnState = digitalRead(PIN_USER_BTN);
if (btnState == LOW) {
@@ -152,6 +192,9 @@ void loop() {
#endif
rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
+6 -4
View File
@@ -290,8 +290,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
return true;
}
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
@@ -303,8 +302,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
} else {
recv_pkt_region = NULL;
}
// do normal processing
return false;
return Mesh::onRecvPacket(pkt);
}
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
@@ -627,6 +625,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_logging = false;
region_load_active = false;
set_radio_at = revert_radio_at = 0;
recv_pkt_region = NULL;
// defaults
memset(&_prefs, 0, sizeof(_prefs));
@@ -650,6 +649,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_max_unscoped = 64;
_prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
#ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif
@@ -658,6 +658,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.gps_enabled = 0;
_prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_prefs.radio_fem_rxgain = 1;
next_post_idx = 0;
next_client_idx = 0;
@@ -699,6 +700,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+4 -1
View File
@@ -144,6 +144,9 @@ protected:
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -151,7 +154,7 @@ protected:
return _prefs.multi_acks;
}
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
bool allowPacketForward(const mesh::Packet* packet) override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
+5 -6
View File
@@ -41,27 +41,27 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
char tmp[80];
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
_display->setColor(UIColor::corp_blue);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// meshcore website
const char* website = "https://meshcore.io";
_display->setColor(DisplayDriver::LIGHT);
_display->setColor(UIColor::primary_txt);
_display->setTextSize(1);
uint16_t websiteWidth = _display->getTextWidth(website);
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
_display->print(website);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 35);
@@ -76,12 +76,11 @@ void UITask::renderCurrScreen() {
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
_display->print(_node_prefs->node_name);
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
+42
View File
@@ -3,6 +3,11 @@
#include "MyMesh.h"
#ifdef ETHERNET_ENABLED
#define ETHERNET_CLI_BANNER "MeshCore Room Server CLI"
#include <helpers/nrf52/EthernetCLI.h>
#endif
#ifdef DISPLAY_CLASS
#include "UITask.h"
static UITask ui_task(display);
@@ -17,6 +22,9 @@ void halt() {
}
static char command[MAX_POST_TEXT_LEN+1];
#ifdef ETHERNET_ENABLED
static char ethernet_command[MAX_POST_TEXT_LEN+1];
#endif
void setup() {
Serial.begin(115200);
@@ -24,6 +32,10 @@ void setup() {
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS
if (display.begin()) {
display.startFrame();
@@ -67,6 +79,9 @@ void setup() {
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
command[0] = 0;
#ifdef ETHERNET_ENABLED
ethernet_command[0] = 0;
#endif
sensors.begin();
@@ -76,6 +91,10 @@ void setup() {
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
#ifdef ETHERNET_ENABLED
ethernet_start_task();
#endif
// send out initial zero hop Advertisement to the mesh
#if ENABLE_ADVERT_ON_BOOT == 1
the_mesh.sendSelfAdvertisement(16000, false);
@@ -101,7 +120,14 @@ void loop() {
if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
reply[0] = 0;
#ifdef ETHERNET_ENABLED
if (!ethernet_handle_command(command, reply)) {
the_mesh.handleCommand(0, command, reply);
}
#else
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
#endif
if (reply[0]) {
Serial.print(" -> "); Serial.println(reply);
}
@@ -109,10 +135,26 @@ void loop() {
command[0] = 0; // reset command buffer
}
#ifdef ETHERNET_ENABLED
ethernet_loop_maintain();
if (ethernet_read_line(ethernet_command, sizeof(ethernet_command))) {
char reply[160];
reply[0] = 0;
if (!ethernet_handle_command(ethernet_command, reply)) {
the_mesh.handleCommand(0, ethernet_command, reply);
}
ethernet_send_reply(reply);
ethernet_command[0] = 0;
}
#endif
the_mesh.loop();
sensors.loop();
#ifdef DISPLAY_CLASS
ui_task.loop();
#endif
rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
}
+8 -1
View File
@@ -135,7 +135,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
File file = _fs->open("/contacts", "w", true);
#endif
if (file) {
ContactsIterator iter;
ContactsIterator iter = startContactsIterator();
ContactInfo c;
uint8_t unused = 0;
uint32_t reserved = 0;
@@ -560,6 +560,10 @@ void setup() {
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
if (!radio_init()) { halt(); }
fast_rng.begin(radio_driver.getRngSeed());
@@ -591,4 +595,7 @@ void setup() {
void loop() {
the_mesh.loop();
rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
}
+6
View File
@@ -323,6 +323,9 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
int SensorMesh::getInterferenceThreshold() const {
return _prefs.interference_threshold;
}
bool SensorMesh::getCADEnabled() const {
return _prefs.cad_enabled;
}
int SensorMesh::getAGCResetInterval() const {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -726,11 +729,13 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.disable_fwd = true;
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// GPS defaults
_prefs.gps_enabled = 0;
_prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_prefs.radio_fem_rxgain = 1;
memset(default_scope.key, 0, sizeof(default_scope.key));
}
@@ -766,6 +771,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+1
View File
@@ -120,6 +120,7 @@ protected:
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() const override;
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
int searchPeersByHash(const uint8_t* hash) override;
+5 -6
View File
@@ -41,27 +41,27 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
char tmp[80];
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
_display->setColor(UIColor::corp_blue);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// meshcore website
const char* website = "https://meshcore.io";
_display->setColor(DisplayDriver::LIGHT);
_display->setColor(UIColor::primary_txt);
_display->setTextSize(1);
uint16_t websiteWidth = _display->getTextWidth(website);
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
_display->print(website);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 35);
@@ -76,12 +76,11 @@ void UITask::renderCurrScreen() {
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->setColor(UIColor::primary_txt);
_display->print(_node_prefs->node_name);
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
+7
View File
@@ -58,6 +58,10 @@ void setup() {
board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS
if (display.begin()) {
display.startFrame();
@@ -147,4 +151,7 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
}
+4 -1
View File
@@ -19,7 +19,8 @@ monitor_speed = 115200
lib_deps =
SPI
Wire
jgromes/RadioLib @ ^7.6.0
;jgromes/RadioLib @ ^7.7.1
https://github.com/jgromes/RadioLib.git#6d8934836678d8894e3d556550475b37dce3e2b6
rweather/Crypto @ ^0.4.0
adafruit/RTClib @ ^2.1.3
melopero/Melopero RV3028 @ ^1.1.0
@@ -157,6 +158,7 @@ lib_deps =
[env:native]
platform = native
test_framework = googletest
build_flags = -std=c++17
-I src
-I test/mocks
@@ -164,5 +166,6 @@ test_build_src = yes
build_src_filter =
-<*>
+<../src/Utils.cpp>
+<../src/Packet.cpp>
lib_deps =
google/googletest @ 1.17.0
+1
View File
@@ -66,6 +66,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
void Dispatcher::loop() {
if (millisHasNowPassed(next_floor_calib_time)) {
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
_radio->setCADEnabled(getCADEnabled());
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
}
_radio->loop();
+3
View File
@@ -65,6 +65,8 @@ public:
virtual void triggerNoiseFloorCalibrate(int threshold) { }
virtual void setCADEnabled(bool enable) { }
virtual void resetAGC() { }
virtual bool isInRecvMode() const = 0;
@@ -166,6 +168,7 @@ protected:
virtual uint32_t getCADFailRetryDelay() const;
virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual bool getCADEnabled() const { return false; } // hardware CAD disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
+31 -16
View File
@@ -55,7 +55,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint16_t offset = (uint16_t)pkt->path_len << path_sz;
if (offset >= len) { // TRACE has reached end of given path
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// append SNR (Not hash!)
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
@@ -89,14 +90,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
return forwardMultipartDirect(pkt);
} else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
if (!_tables->hasSeen(pkt)) { // don't retransmit!
if (!_tables->wasSeen(pkt)) { // don't retransmit!
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
routeDirectRecvAcks(pkt, 0);
}
return ACTION_RELEASE;
}
if (!_tables->hasSeen(pkt)) {
if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
uint32_t d = getDirectRetransmitDelay(pkt);
@@ -117,7 +120,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
if (i > pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onAckRecv(pkt, ack_crc);
action = routeRecvPacket(pkt);
}
@@ -134,7 +138,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + CIPHER_MAC_SIZE >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins.
// For flood mode, the path may not be the 'best' in terms of hops.
// FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?)
@@ -155,6 +160,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
int k = 0;
uint8_t path_len = data[k++];
if (!Packet::isValidPathLen(path_len)) {
MESH_DEBUG_PRINTLN("%s PAYLOAD_TYPE_PATH, bad path_len: %u", getLogDateTime(), (uint32_t)path_len);
break; // reject bad encoding
}
uint8_t hash_size = (path_len >> 6) + 1;
uint8_t hash_count = path_len & 63;
uint8_t* path = &data[k]; k += hash_size*hash_count;
@@ -193,7 +202,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
if (self_id.isHashMatch(&dest_hash)) {
Identity sender(sender_pub_key);
@@ -220,7 +230,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
GroupChannel channels[4];
int num = searchChannelsByHash(&channel_hash, channels, 4);
@@ -251,7 +262,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime());
} else if (self_id.matches(id.pub_key)) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
uint8_t* app_data = &pkt->payload[i];
int app_data_len = pkt->payload_len - i;
if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; }
@@ -278,7 +290,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
break;
}
case PAYLOAD_TYPE_RAW_CUSTOM: {
if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) {
if (pkt->isRouteDirect() && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onRawDataRecv(pkt);
//action = routeRecvPacket(pkt); don't flood route these (yet)
}
@@ -296,7 +309,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) {
if (!_tables->wasSeen(&tmp)) {
_tables->markSeen(&tmp);
uint32_t ack_crc;
memcpy(&ack_crc, tmp.payload, 4);
@@ -353,7 +367,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) { // don't retransmit!
if (!_tables->wasSeen(&tmp)) { // don't retransmit!
_tables->markSeen(&tmp);
removeSelfFromPath(&tmp);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
}
@@ -633,7 +648,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
packet->header |= ROUTE_TYPE_FLOOD;
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -662,7 +677,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m
packet->transport_codes[1] = transport_codes[1];
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -695,7 +710,7 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
pri = 0;
}
}
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, pri, delay_millis);
}
@@ -705,7 +720,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
packet->path_len = 0; // path_len of zero means Zero Hop
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
@@ -718,7 +733,7 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay
packet->path_len = 0; // path_len of zero means Zero Hop
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
+4 -3
View File
@@ -15,8 +15,9 @@ public:
*/
class MeshTables {
public:
virtual bool hasSeen(const Packet* packet) = 0;
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
virtual bool wasSeen(const Packet* packet) = 0;
virtual void markSeen(const Packet* packet) = 0;
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
};
/**
@@ -100,7 +101,7 @@ protected:
* \param auth_code a code to authenticate the packet
* \param flags zero for now
* \param path_snrs single byte SNR*4 for each hop in the path
* \param path_hashes hashes if each repeater in the path
* \param path_hashes hashes of each repeater in the path
* \param path_len length of the path_snrs[] and path_hashes[] arrays
*/
virtual void onTraceRecv(Packet* packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t* path_snrs, const uint8_t* path_hashes, uint8_t path_len) { }
+3
View File
@@ -64,6 +64,9 @@ public:
virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
+1 -1
View File
@@ -25,7 +25,7 @@ namespace mesh {
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type(uint16), data_len, blob)
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNR for each hop
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet
//...
+1
View File
@@ -42,6 +42,7 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
}
if (i2c_probe(wire, PCF8563_ADDRESS)) {
MESH_DEBUG_PRINTLN("PCF8563: Found");
rtc_8563_success = rtc_8563.begin(&wire);
}
+32 -20
View File
@@ -68,29 +68,36 @@ void BaseChatMesh::bootstrapRTCfromContacts() {
}
ContactInfo* BaseChatMesh::allocateContactSlot(bool transient_only) {
if (num_contacts < MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (transient_only || shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
for (int i = 0; i < num_contacts; i++) {
if (transient_only) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
} else {
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
if (transient_only) {
// only allocate from first N
for (int i = 0; i < MAX_ANON_CONTACTS; i++) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
if (oldest_idx >= 0) {
// NOTE: do NOT call onContactOverwrite()
return &contacts[oldest_idx];
}
} else {
if (num_contacts < MAX_ANON_CONTACTS+MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
for (int i = MAX_ANON_CONTACTS; i < num_contacts; i++) {
bool is_favourite = (contacts[i].flags & 0x01) != 0;
if (!is_favourite && contacts[i].lastmod < oldest_lastmod && contacts[i].type != ADV_TYPE_NONE) {
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
}
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
return &contacts[oldest_idx];
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
return &contacts[oldest_idx];
}
}
}
return NULL; // no space, no overwrite or all contacts are all favourites
@@ -139,6 +146,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
packet->header = save;
}
if (from && from->type == ADV_TYPE_NONE) { // already in contacts, but from a temporary ANON_REQ ?
memset(from, 0, sizeof(*from)); // clear the anon/temp slot
from = NULL; // do normal 'add' flow
}
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
if (from == NULL) {
if (!shouldAutoAddContactType(parser.getType())) {
@@ -930,11 +942,11 @@ bool BaseChatMesh::getContactByIdx(uint32_t idx, ContactInfo& contact) {
}
ContactsIterator BaseChatMesh::startContactsIterator() {
return ContactsIterator();
return ContactsIterator(MAX_ANON_CONTACTS); // start at offset, skip the anon entries
}
bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) {
if (next_idx >= mesh->getNumContacts()) return false;
if (next_idx >= mesh->getTotalContactSlots()) return false;
dest = mesh->contacts[next_idx++];
return true;
+12 -5
View File
@@ -28,8 +28,9 @@ public:
class BaseChatMesh;
class ContactsIterator {
int next_idx = 0;
int next_idx;
public:
ContactsIterator(int start) { next_idx = start; }
bool hasNext(const BaseChatMesh* mesh, ContactInfo& dest);
};
@@ -79,8 +80,9 @@ class BaseChatMesh : public mesh::Mesh {
protected:
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, mgr, tables)
{
num_contacts = 0;
{
resetContacts();
#ifdef MAX_GROUP_CHANNELS
memset(channels, 0, sizeof(channels));
num_channels = 0;
@@ -91,7 +93,11 @@ protected:
}
void bootstrapRTCfromContacts();
void resetContacts() { num_contacts = 0; }
void resetContacts() {
memset(contacts, 0, sizeof(contacts[0])*MAX_ANON_CONTACTS); // set all to have type = ADV_TYPE_NONE(0)
num_contacts = MAX_ANON_CONTACTS; // seed the first contacts for anon requests
}
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
ContactInfo* allocateContactSlot(bool transient_only=false); // helper to find slot for new contact
@@ -166,7 +172,8 @@ public:
ContactInfo* lookupContactByPubKey(const uint8_t* pub_key, int prefix_len);
bool removeContact(ContactInfo& contact);
bool addContact(const ContactInfo& contact);
int getNumContacts() const { return num_contacts; }
int getTotalContactSlots() const { return num_contacts; }
int getNumContacts() const { return num_contacts - MAX_ANON_CONTACTS; } // don't include the reserved slots at start
bool getContactByIdx(uint32_t idx, ContactInfo& contact);
ContactsIterator startContactsIterator();
ChannelDetails* addChannel(const char* name, const char* psk_base64);
+58 -22
View File
@@ -88,10 +88,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
// next: 293
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -121,6 +123,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close();
}
@@ -181,10 +185,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
// next: 293
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
file.close();
}
@@ -500,6 +506,10 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->interference_threshold = atoi(&config[11]);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "cad ", 4) == 0) {
_prefs->cad_enabled = memcmp(&config[4], "on", 2) == 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs();
@@ -561,13 +571,37 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
savePrefs();
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
} else if (memcmp(config, "radio.rxgain ", 13) == 0) {
_prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0;
strcpy(reply, "OK");
bool enabled = memcmp(&config[13], "on", 2) == 0;
_prefs->rx_boosted_gain = enabled;
savePrefs();
_callbacks->setRxBoostedGain(_prefs->rx_boosted_gain);
#endif
if (_callbacks->setRxBoostedGain(enabled)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: unsupported");
}
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemLnaEnabled(true)) {
_prefs->radio_fem_rxgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemLnaEnabled(false)) {
_prefs->radio_fem_rxgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
@@ -757,7 +791,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
}
} else {
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
};
} else {
strcpy(reply, "unknown config: ");
@@ -776,6 +810,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
} else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "cad", 3) == 0) {
sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off");
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) {
@@ -801,10 +837,14 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
} else if (memcmp(config, "lon", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
} else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif
} else if (memcmp(config, "radio.fem.rxgain", 16) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
@@ -890,12 +930,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
strcpy(reply, "Error: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %.3f", adc_mult);
}
@@ -913,13 +953,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
+4 -2
View File
@@ -61,8 +61,10 @@ struct NodePrefs { // persisted to file
float adc_multiplier;
char owner_info[120];
uint8_t rx_boosted_gain; // power settings
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
};
class CommonCLICallbacks {
@@ -109,8 +111,8 @@ public:
// no op by default
};
virtual void setRxBoostedGain(bool enable) {
// no op by default
virtual bool setRxBoostedGain(bool enable) {
return false; // CommonCLI reports unsupported if not overridden by wrapper
};
};
+42
View File
@@ -1,6 +1,7 @@
#ifdef ESP_PLATFORM
#include "ESP32Board.h"
#include <target.h>
#if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
#include <WiFi.h>
@@ -44,4 +45,45 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
}
#endif
void ESP32Board::powerOff() {
enterDeepSleep(0); // Do not wakeup
}
void ESP32Board::enterDeepSleep(uint32_t secs) {
// Power off the display if any
#ifdef DISPLAY_CLASS
display.turnOff();
#endif
// Power off LoRa
radio_driver.powerOff();
// Keep LoRa inactive during deepsleep
digitalWrite(P_LORA_NSS, HIGH);
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
gpio_hold_en((gpio_num_t)P_LORA_NSS);
#else
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
#endif
// Power off GPS if any
if (sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000ULL);
}
// Finally set ESP32 into deepsleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
#endif
+40
View File
@@ -14,6 +14,7 @@
#include <Wire.h>
#include "soc/rtc.h"
#include "esp_system.h"
#include <driver/rtc_io.h>
class ESP32Board : public mesh::MainBoard {
protected:
@@ -62,6 +63,9 @@ public:
return raw / 4;
}
virtual void powerOff() override;
void enterDeepSleep(uint32_t secs);
uint32_t getIRQGpio() override {
return P_LORA_DIO_1; // default for SX1262
}
@@ -155,6 +159,42 @@ public:
void setInhibitSleep(bool inhibit) {
inhibit_sleep = inhibit;
}
uint32_t getResetReason() const override {
return esp_reset_reason();
}
// https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html
const char* getResetReasonString(uint32_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN:
return "Unknown or first boot";
case ESP_RST_POWERON:
return "Power-on reset";
case ESP_RST_EXT:
return "External reset";
case ESP_RST_SW:
return "Software reset";
case ESP_RST_PANIC:
return "Panic / exception reset";
case ESP_RST_INT_WDT:
return "Interrupt watchdog reset";
case ESP_RST_TASK_WDT:
return "Task watchdog reset";
case ESP_RST_WDT:
return "Other watchdog reset";
case ESP_RST_DEEPSLEEP:
return "Wake from deep sleep";
case ESP_RST_BROWNOUT:
return "Brownout (low voltage)";
case ESP_RST_SDIO:
return "SDIO reset";
default:
static char buf[40];
snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason);
return buf;
}
}
};
class ESP32RTCClock : public mesh::RTCClock {
+12
View File
@@ -0,0 +1,12 @@
#pragma once
class ExternalWatchdogManager {
protected:
unsigned long last_feed_watchdog;
public:
ExternalWatchdogManager() { last_feed_watchdog = 0; }
virtual bool begin() { return false; }
virtual void loop() { }
virtual unsigned long getIntervalMs() const { return 0; }
virtual void feed() { }
};
-30
View File
@@ -15,8 +15,6 @@
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class MeshadventurerBoard : public ESP32Board {
public:
@@ -35,34 +33,6 @@ public:
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void powerOff() override {
// TODO: re-enable this when there is a definite wake-up source pin:
// enterDeepSleep(0);
}
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
+80 -7
View File
@@ -1,5 +1,6 @@
#if defined(NRF52_PLATFORM)
#include "NRF52Board.h"
#include <target.h>
#include <bluefruit.h>
#include <nrf_soc.h>
@@ -279,16 +280,29 @@ void NRF52Board::sleep(uint32_t secs) {
// Temperature from NRF52 MCU
float NRF52Board::getMCUTemperature() {
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis();
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
if(millis() - startTime > 5) { // To wait 5ms just in case
NRF_TEMP->TASKS_STOP = 1;
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t err_code;
int32_t temp;
err_code = sd_temp_get(&temp);
if (err_code == NRF_SUCCESS) {
return (float)temp * 0.25f;
} else {
return NAN;
}
} else {
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis();
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
if(millis() - startTime > 5) { // To wait 5ms just in case
NRF_TEMP->TASKS_STOP = 1;
return NAN;
}
}
}
NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag
int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units
@@ -297,6 +311,65 @@ float NRF52Board::getMCUTemperature() {
return temp * 0.25f; // Convert to *C
}
void NRF52Board::shutdownPeripherals() {
// Power off the display if any
#ifdef DISPLAY_CLASS
if (display.isOn()) {
display.turnOff();
}
#endif
// Prep LoRa radio for power down
#ifdef P_LORA_RESET
digitalWrite(P_LORA_RESET, HIGH); // preload OUT latch so pinMode can't glitch NRESET low
pinMode(P_LORA_RESET, OUTPUT);
digitalWrite(P_LORA_RESET, LOW); // deliberate hardware reset (datasheet: >=100us)
delayMicroseconds(200);
digitalWrite(P_LORA_RESET, HIGH);
#endif
#if defined(P_LORA_SCLK) && defined(P_LORA_MISO) && defined(P_LORA_MOSI)
SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
SPI.begin(); // SPI may not be started on some shutdown paths, need it to shut down radio
#endif
#ifdef P_LORA_BUSY
pinMode(P_LORA_BUSY, INPUT);
uint32_t started_at = millis();
while (digitalRead(P_LORA_BUSY) && millis() - started_at < 10) {} //wait for radio to be ready
#endif
#ifdef P_LORA_NSS
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off LoRa
radio_driver.powerOff();
// Keep LoRa inactive during deepsleep
#ifdef P_LORA_NSS
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off GPS if any
if(sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
}
void NRF52Board::powerOff() {
shutdownPeripherals();
// Enter SYSTEMOFF
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) { // SoftDevice is enabled
sd_power_system_off();
} else { // SoftDevice is not enable
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
}
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader ";
const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B
+2
View File
@@ -50,6 +50,8 @@ public:
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); }
virtual void shutdownPeripherals();
virtual void powerOff() override;
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
+6 -4
View File
@@ -93,13 +93,15 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
while (num_regions < MAX_REGION_ENTRIES) {
auto r = &regions[num_regions];
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
int n = file.read((uint8_t *) &r->id, sizeof(r->id));
if (n == 0) break; // clean EOF
success = (n == sizeof(r->id));
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
if (!success) break; // EOF
if (!success) break; // partial read or corruption
if (r->id >= next_id) { // make sure next_id is valid
next_id = r->id + 1;
@@ -108,7 +110,7 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
}
}
file.close();
return true;
return success;
}
}
return false; // failed
@@ -139,7 +141,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
}
}
file.close();
return true;
return success;
}
return false; // failed
}
+10 -6
View File
@@ -31,27 +31,31 @@ public:
}
#endif
bool hasSeen(const mesh::Packet* packet) override {
bool wasSeen(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
const uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (packet->isRouteDirect()) {
_direct_dups++; // keep some stats
_direct_dups++;
} else {
_flood_dups++;
}
return true;
}
}
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
return false;
}
void markSeen(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
memcpy(&_hashes[_next_idx * MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
}
void clear(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
+2 -1
View File
@@ -39,7 +39,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
// bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
} else {
+1 -1
View File
@@ -110,7 +110,7 @@ protected:
* @brief Common packet handling for received packets
*
* Implements the standard pattern used by all bridges:
* - Check if packet was seen before using _seen_packets.hasSeen()
* - Check if packet was seen before using _seen_packets.wasSeen()
* - Queue packet for mesh processing if not seen before
* - Free packet if already seen to prevent duplicates
*
+3 -2
View File
@@ -32,7 +32,7 @@ void ESPNowBridge::begin() {
// Initialize WiFi in station mode
WiFi.mode(WIFI_STA);
// Set wifi channel
// Set Wi-Fi channel
if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel);
return;
@@ -167,7 +167,8 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
// Create a temporary buffer just for size calculation and reuse for actual writing
uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
+2 -1
View File
@@ -115,7 +115,8 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
uint16_t len = packet->writeTo(buffer + 4);
+1 -1
View File
@@ -38,7 +38,7 @@ public:
* These two functions do nothing for ESP-NOW, but are needed for the
* Radio interface.
*/
virtual void setRxBoostedGainMode(bool) { }
virtual bool setRxBoostedGainMode(bool) { }
virtual bool getRxBoostedGainMode() const { return false; }
uint32_t intID();
+18 -20
View File
@@ -103,10 +103,9 @@ void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
BLE_DEBUG_PRINTLN("onDisconnect()");
deviceConnected = false;
if (_isEnabled) {
adv_restart_time = millis() + ADVERT_RESTART_DELAY;
// loop() will detect this on next loop, and set deviceConnected to false
}
}
@@ -118,17 +117,24 @@ void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gat
if (len > MAX_FRAME_SIZE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), frame too big, len=%d", len);
} else if (recv_queue_len >= FRAME_QUEUE_SIZE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!");
} else {
recv_queue[recv_queue_len].len = len;
memcpy(recv_queue[recv_queue_len].buf, rxValue, len);
recv_queue_len++;
Frame frame = {};
frame.len = len;
memcpy(frame.buf, rxValue, len);
if (xQueueSend(recv_queue, &frame, 0) != pdTRUE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!");
}
}
}
// ---------- public methods
void SerialBLEInterface::clearBuffers() {
xQueueReset(recv_queue);
send_queue_len = 0;
}
void SerialBLEInterface::enable() {
if (_isEnabled) return;
@@ -202,21 +208,13 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
}
}
if (recv_queue_len > 0) { // check recv queue
size_t len = recv_queue[0].len; // take from top of queue
memcpy(dest, recv_queue[0].buf, len);
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]);
recv_queue_len--;
for (int i = 0; i < recv_queue_len; i++) { // delete top item from queue
recv_queue[i] = recv_queue[i + 1];
}
return len;
Frame frame;
if (xQueueReceive(recv_queue, &frame, 0) == pdTRUE) {
memcpy(dest, frame.buf, frame.len);
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", (uint32_t) frame.len, (uint32_t) dest[0]);
return frame.len;
}
if (pServer->getConnectedCount() == 0) deviceConnected = false;
if (deviceConnected != oldDeviceConnected) {
if (!deviceConnected) { // disconnecting
clearBuffers();
+10 -4
View File
@@ -5,6 +5,8 @@
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLEServerCallbacks, BLECharacteristicCallbacks {
BLEServer *pServer;
@@ -24,12 +26,13 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
};
#define FRAME_QUEUE_SIZE 4
int recv_queue_len;
Frame recv_queue[FRAME_QUEUE_SIZE];
StaticQueue_t recv_queue_state;
uint8_t recv_queue_storage[FRAME_QUEUE_SIZE * sizeof(Frame)];
QueueHandle_t recv_queue;
int send_queue_len;
Frame send_queue[FRAME_QUEUE_SIZE];
void clearBuffers() { recv_queue_len = 0; send_queue_len = 0; }
void clearBuffers();
protected:
// BLESecurityCallbacks methods
@@ -58,7 +61,10 @@ public:
_isEnabled = false;
_last_write = 0;
last_conn_id = 0;
send_queue_len = recv_queue_len = 0;
recv_queue = xQueueCreateStatic(
FRAME_QUEUE_SIZE, sizeof(Frame), recv_queue_storage, &recv_queue_state
);
send_queue_len = 0;
}
/**
+10
View File
@@ -16,6 +16,16 @@ void TBeamBoard::begin() {
ESP32Board::begin();
#ifdef TBEAM_SUPREME_SX1262
// On the T-Beam S3 Supreme the PMU + RTC sit on Wire1 (GPIO 42/41, brought
// up by XPowersLib), while the SH1106 OLED and the BME280/QMC6310 sensors
// sit on the primary bus, Wire (GPIO PIN_BOARD_SDA/PIN_BOARD_SCL). Nothing
// else initialises Wire on this board, so the display driver would
// otherwise talk on the wrong (default) pins and display.begin() fails,
// leaving the screen blank. Bring the OLED bus up here.
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
power_init();
//Configure user button
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#ifdef ETHERNET_ENABLED
#include <Arduino.h>
#include <SPI.h>
#include <RAK13800_W5100S.h>
#include <helpers/nrf52/EthernetMac.h>
#define PIN_SPI1_MISO (29)
#define PIN_SPI1_MOSI (30)
#define PIN_SPI1_SCK (3)
static SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
#ifndef ETHERNET_TCP_PORT
#define ETHERNET_TCP_PORT 23 // telnet port for CLI access
#endif
#ifndef ETHERNET_CLI_BANNER
#define ETHERNET_CLI_BANNER "MeshCore CLI"
#endif
#define ETHERNET_RETRY_INTERVAL_MS 30000
static EthernetServer ethernet_server(ETHERNET_TCP_PORT);
static EthernetClient ethernet_client;
static volatile bool ethernet_running = false;
// FreeRTOS task: handles hw init, DHCP, and retries in the background
static void ethernet_task(void* param) {
(void)param;
Serial.println("ETH: Initializing hardware");
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
uint8_t mac[6];
generateEthernetMac(mac);
Serial.printf("ETH: MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Retry loop: keep trying until we get an IP
while (!ethernet_running) {
Serial.println("ETH: Attempting DHCP...");
if (Ethernet.begin(mac, 10000, 2000) == 0) {
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("ETH: Hardware not found, giving up");
vTaskDelete(NULL);
return;
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("ETH: Cable not connected, will retry");
} else {
Serial.println("ETH: DHCP failed, will retry");
}
vTaskDelay(pdMS_TO_TICKS(ETHERNET_RETRY_INTERVAL_MS));
continue;
}
IPAddress ip = Ethernet.localIP();
Serial.printf("ETH: IP: %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
Serial.printf("ETH: Listening on TCP port %d\n", ETHERNET_TCP_PORT);
ethernet_server.begin();
ethernet_running = true;
}
// DHCP succeeded, task is done
vTaskDelete(NULL);
}
static void ethernet_start_task() {
xTaskCreate(ethernet_task, "eth_init", 1024, NULL, 1, NULL);
}
// Format ethernet status into reply buffer. Returns true if command was handled.
static bool ethernet_handle_command(const char* command, char* reply) {
if (strcmp(command, "eth.status") == 0) {
if (!ethernet_running) {
strcpy(reply, "ETH: not connected");
} else {
IPAddress ip = Ethernet.localIP();
sprintf(reply, "ETH: %u.%u.%u.%u:%d", ip[0], ip[1], ip[2], ip[3], ETHERNET_TCP_PORT);
}
return true;
}
return false;
}
// Check for new TCP client connections, replacing any existing connection.
// Use accept() (not available()) so we only see newly-accepted sockets;
// available() also returns existing connected sockets that have data, which
// would force us to disambiguate every inbound packet from a real new client.
static void ethernet_check_client() {
auto newClient = ethernet_server.accept();
if (newClient) {
if (ethernet_client) ethernet_client.stop();
ethernet_client = newClient;
IPAddress ip = ethernet_client.remoteIP();
Serial.printf("ETH: Client connected from %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
ethernet_client.println(ETHERNET_CLI_BANNER);
}
}
// Call from loop() to maintain DHCP and check for new clients
static void ethernet_loop_maintain() {
if (ethernet_running) {
ethernet_check_client();
Ethernet.maintain();
}
}
// Read a line from the Ethernet client into the command buffer.
// Returns true when a complete line is ready to process (command is null-terminated).
// The caller should process the command and then reset ethernet_command[0] = 0.
static bool ethernet_read_line(char* ethernet_command, size_t buf_size) {
if (!ethernet_running || !ethernet_client || !ethernet_client.connected()) return false;
int elen = strlen(ethernet_command);
while (ethernet_client.available() && elen < (int)buf_size - 1) {
char c = ethernet_client.read();
if (c == '\n' && elen == 0) continue; // ignore leading LF (from CR+LF)
if (c == '\r' || c == '\n') { ethernet_command[elen++] = '\r'; break; }
ethernet_command[elen++] = c;
ethernet_command[elen] = 0;
}
if (elen == (int)buf_size - 1) {
ethernet_command[buf_size - 1] = '\r';
}
if (elen > 0 && ethernet_command[elen - 1] == '\r') {
ethernet_command[elen - 1] = 0;
ethernet_client.println();
return true;
}
return false;
}
// Send a reply to the Ethernet client
static void ethernet_send_reply(const char* reply) {
if (reply[0]) {
ethernet_client.print(" -> "); ethernet_client.println(reply);
}
}
#endif // ETHERNET_ENABLED
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
static inline void generateEthernetMac(uint8_t mac[6]) {
uint32_t device_id = NRF_FICR->DEVICEID[0];
mac[0] = 0x02;
mac[1] = 0x92;
mac[2] = 0x1F;
mac[3] = (device_id >> 16) & 0xFF;
mac[4] = (device_id >> 8) & 0xFF;
mac[5] = device_id & 0xFF;
}
@@ -0,0 +1,268 @@
#ifdef ETHERNET_ENABLED
#include "SerialEthernetInterface.h"
#include "EthernetMac.h"
#include <SPI.h>
#include <EthernetUdp.h>
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
#define RECV_STATE_IDLE 0
#define RECV_STATE_HDR_FOUND 1
#define RECV_STATE_LEN1_FOUND 2
#define RECV_STATE_LEN2_FOUND 3
bool SerialEthernetInterface::begin() {
ETHERNET_DEBUG_PRINTLN("Ethernet initializing");
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
#ifdef PIN_ETHERNET_RESET
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
#endif
uint8_t mac[6];
generateEthernetMac(mac);
ETHERNET_DEBUG_PRINTLN(
"Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]);
ETHERNET_DEBUG_PRINTLN("Init");
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
// Use static IP if build flags are defined, otherwise DHCP
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gateway(ETHERNET_STATIC_GATEWAY);
IPAddress subnet(ETHERNET_STATIC_SUBNET);
IPAddress dns(ETHERNET_STATIC_DNS);
Ethernet.begin(mac, ip, dns, gateway, subnet);
#else
ETHERNET_DEBUG_PRINTLN("Begin");
if (Ethernet.begin(mac) == 0) {
ETHERNET_DEBUG_PRINTLN("Begin failed.");
// DHCP failed -- let's figure out why
if (Ethernet.hardwareStatus() == EthernetNoHardware) // Check for Ethernet hardware present.
{
ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found.");
return false;
}
if (Ethernet.linkStatus() == LinkOFF) // No physical connection
{
ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected.");
return false;
}
ETHERNET_DEBUG_PRINTLN("Ethernet: DHCP failed for unknown reason.");
return false;
}
#endif
ETHERNET_DEBUG_PRINTLN("Ethernet begin complete");
ETHERNET_DEBUG_PRINT_IP("IP", Ethernet.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet", Ethernet.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP());
server.begin(); // start listening for clients
ETHERNET_DEBUG_PRINTLN("Ethernet: listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
void SerialEthernetInterface::enable() {
if (_isEnabled) return;
_isEnabled = true;
clearBuffers();
}
void SerialEthernetInterface::disable() {
_isEnabled = false;
}
size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
if (len > MAX_FRAME_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len);
return 0;
}
if (deviceConnected && len > 0) {
if (send_queue_len >= FRAME_QUEUE_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
return 0;
}
send_queue[send_queue_len].len = len; // add to send queue
memcpy(send_queue[send_queue_len].buf, src, len);
send_queue_len++;
return len;
}
return 0;
}
bool SerialEthernetInterface::isWriteBusy() const {
return false;
}
size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
// Use accept() (not available()) so we only see newly-accepted sockets.
// available() also returns existing connected sockets that have data,
// which would cause us to treat each inbound packet as a "new client"
// and stop() the underlying socket — disconnecting the companion.
auto newClient = server.accept();
if (newClient) {
IPAddress new_ip = newClient.remoteIP();
uint16_t new_port = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN(
"New client accepted %u.%u.%u.%u:%u",
new_ip[0],
new_ip[1],
new_ip[2],
new_ip[3],
new_port);
deviceConnected = false;
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
_state = RECV_STATE_IDLE;
_frame_len = 0;
_rx_len = 0;
client = newClient;
ETHERNET_DEBUG_PRINTLN("Switched to new client");
}
if (client.connected()) {
if (!deviceConnected) {
ETHERNET_DEBUG_PRINTLN(
"Got connection %u.%u.%u.%u:%u",
client.remoteIP()[0],
client.remoteIP()[1],
client.remoteIP()[2],
client.remoteIP()[3],
client.remotePort());
deviceConnected = true;
}
} else {
if (deviceConnected) {
deviceConnected = false;
ETHERNET_DEBUG_PRINTLN("Disconnected");
}
}
if (deviceConnected) {
if (send_queue_len > 0) { // first, check send queue
_last_write = millis();
int len = send_queue[0].len;
#if ETHERNET_RAW_LINE
ETHERNET_DEBUG_PRINTLN("TX line len=%d", len);
client.write(send_queue[0].buf, len);
client.write("\r\n", 2);
#else
uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames
pkt[0] = '>';
pkt[1] = (len & 0xFF); // LSB
pkt[2] = (len >> 8); // MSB
memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len);
ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len);
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len);
#endif
client.write(pkt, 3 + len);
#endif
send_queue_len--;
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
send_queue[i] = send_queue[i + 1];
}
} else {
while (client.available()) {
int c = client.read();
if (c < 0) break;
#if ETHERNET_RAW_LINE
if (c == '\r' || c == '\n') {
if (_rx_len == 0) {
continue;
}
uint16_t out_len = _rx_len;
if (out_len > MAX_FRAME_SIZE) {
out_len = MAX_FRAME_SIZE;
}
memcpy(dest, _rx_buf, out_len);
_rx_len = 0;
return out_len;
}
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
_rx_len++;
}
#else
switch (_state) {
case RECV_STATE_IDLE:
if (c == '<') {
_state = RECV_STATE_HDR_FOUND;
}
break;
case RECV_STATE_HDR_FOUND:
_frame_len = (uint8_t)c;
_state = RECV_STATE_LEN1_FOUND;
break;
case RECV_STATE_LEN1_FOUND:
_frame_len |= ((uint16_t)c) << 8;
_rx_len = 0;
_state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE;
break;
default:
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
}
_rx_len++;
if (_rx_len >= _frame_len) {
if (_frame_len > MAX_FRAME_SIZE) {
_frame_len = MAX_FRAME_SIZE;
}
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len);
#endif
memcpy(dest, _rx_buf, _frame_len);
_state = RECV_STATE_IDLE;
return _frame_len;
}
}
#endif
}
}
}
return 0;
}
bool SerialEthernetInterface::isConnected() const {
return deviceConnected;
}
void SerialEthernetInterface::loop() {
Ethernet.maintain();
}
#endif // ETHERNET_ENABLED
@@ -0,0 +1,82 @@
#pragma once
#ifdef ETHERNET_ENABLED
#include "helpers/BaseSerialInterface.h"
#include <SPI.h>
#include <RAK13800_W5100S.h>
#ifndef ETHERNET_TCP_PORT
#define ETHERNET_TCP_PORT 5000
#endif
// define ETHERNET_RAW_LINE=1 to use raw line-based CLI instead of framed packets
class SerialEthernetInterface : public BaseSerialInterface {
bool deviceConnected;
bool _isEnabled;
unsigned long _last_write;
uint8_t _state;
uint16_t _frame_len;
uint16_t _rx_len;
uint8_t _rx_buf[MAX_FRAME_SIZE];
EthernetServer server;
EthernetClient client;
struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
};
#define FRAME_QUEUE_SIZE 4
int send_queue_len;
Frame send_queue[FRAME_QUEUE_SIZE];
void clearBuffers() {
send_queue_len = 0;
_state = 0;
_frame_len = 0;
_rx_len = 0;
}
protected:
public:
SerialEthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) {
deviceConnected = false;
_isEnabled = false;
_last_write = 0;
send_queue_len = 0;
_state = 0;
_frame_len = 0;
_rx_len = 0;
}
bool begin();
// BaseSerialInterface methods
void enable() override;
void disable() override;
bool isEnabled() const override { return _isEnabled; }
bool isConnected() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
void loop();
};
#if ETHERNET_DEBUG_LOGGING && ARDUINO
#include <Arduino.h>
#define ETHERNET_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf(name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3])
#else
#define ETHERNET_DEBUG_PRINT(...) {}
#define ETHERNET_DEBUG_PRINTLN(...) {}
#define ETHERNET_DEBUG_PRINT_IP(...) {}
#endif
#endif // ETHERNET_ENABLED
+4 -4
View File
@@ -66,11 +66,11 @@ class CustomLLCC68 : public LLCC68 {
setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN
#define SX1262X_RXEN RADIOLIB_NC
#ifndef SX126X_RXEN
#define SX126X_RXEN RADIOLIB_NC
#endif
#ifndef SX1262X_TXEN
#define SX1262X_TXEN RADIOLIB_NC
#ifndef SX126X_TXEN
#define SX126X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif
+2 -2
View File
@@ -33,8 +33,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override {
((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode();
+47 -3
View File
@@ -4,6 +4,10 @@
#include "MeshCore.h"
class CustomLR1110 : public LR1110 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false;
public:
@@ -32,9 +36,49 @@ class CustomLR1110 : public LR1110 {
bool getRxBoostedGainMode() const { return _rx_boosted; }
bool isReceiving() {
uint16_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
return detected;
uint32_t irq = getIrqStatus();
bool preamble = irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; // bit 4
bool header = irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; // bit 5
bool hdrErr = irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR; // bit 6
uint32_t now = millis();
if (hdrErr) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
+10 -2
View File
@@ -14,6 +14,9 @@ public:
((CustomLR1110 *)_radio)->setBandwidth(bw);
((CustomLR1110 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLR1110 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLR1110 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
@@ -26,6 +29,11 @@ public:
return rssi;
}
uint32_t getEstAirtimeFor(int len_bytes) override {
auto airtime = RadioLibWrapper::getEstAirtimeFor(len_bytes);
return airtime < 200 ? 200 : airtime; // at least 200 millis
}
void onSendFinished() override {
RadioLibWrapper::onSendFinished();
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
@@ -36,8 +44,8 @@ public:
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
void setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
+62 -8
View File
@@ -1,11 +1,14 @@
#pragma once
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
#include "MeshCore.h"
class CustomSX1262 : public SX1262 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public:
CustomSX1262(Module *mod) : SX1262(mod) { }
@@ -27,6 +30,14 @@ class CustomSX1262 : public SX1262 {
uint8_t cr = 5;
#endif
#ifdef SX126X_USE_REGULATOR_LDO
constexpr bool useRegulatorLDO = SX126X_USE_REGULATOR_LDO;
#else
constexpr bool useRegulatorLDO = false;
#endif
MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", useRegulatorLDO ? "LDO" : "DC-DC");
#if defined(P_LORA_SCLK)
#ifdef NRF52_PLATFORM
if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); }
@@ -42,11 +53,12 @@ class CustomSX1262 : public SX1262 {
if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
#endif
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO);
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
MESH_DEBUG_PRINTLN("SX1262 init failed with error %d, retrying with TCXO at 0.0V", status);
tcxo = 0.0f;
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO);
}
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
@@ -83,13 +95,55 @@ class CustomSX1262 : public SX1262 {
writeRegister(0x8B5, &r_data, 1);
#endif
MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors());
return true; // success
}
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
uint32_t irq = getIrqFlags();
bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4
bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
bool getRxBoostedGainMode() {
+5 -2
View File
@@ -18,6 +18,9 @@ public:
((CustomSX1262 *)_radio)->setBandwidth(bw);
((CustomSX1262 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomSX1262 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomSX1262 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
bool isReceivingPacket() override {
@@ -40,8 +43,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override {
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
+4 -4
View File
@@ -66,11 +66,11 @@ class CustomSX1268 : public SX1268 {
setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN
#define SX1262X_RXEN RADIOLIB_NC
#ifndef SX126X_RXEN
#define SX126X_RXEN RADIOLIB_NC
#endif
#ifndef SX1262X_TXEN
#define SX1262X_TXEN RADIOLIB_NC
#ifndef SX126X_TXEN
#define SX126X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif
+2 -2
View File
@@ -37,8 +37,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override {
((CustomSX1268 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
+6 -6
View File
@@ -50,14 +50,14 @@ class CustomSX1276 : public SX1276 {
setCurrentLimit(SX127X_CURRENT_LIMIT);
#endif
#if defined(SX176X_RXEN) || defined(SX176X_TXEN)
#ifndef SX176X_RXEN
#define SX176X_RXEN RADIOLIB_NC
#if defined(SX127X_RXEN) || defined(SX127X_TXEN)
#ifndef SX127X_RXEN
#define SX127X_RXEN RADIOLIB_NC
#endif
#ifndef SX176X_TXEN
#define SX176X_TXEN RADIOLIB_NC
#ifndef SX127X_TXEN
#define SX127X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX176X_RXEN, SX176X_TXEN);
setRfSwitchPins(SX127X_RXEN, SX127X_TXEN);
#endif
setCRC(1);
+38 -3
View File
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
// start average out some samples
_num_floor_samples = 0;
@@ -178,10 +179,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
@@ -211,3 +228,21 @@ float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) {
return max(0.0, min(1.0, success_rate_based_on_snr * collision_penalty));
}
PacketMillis RadioLibWrapper::calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols) {
// based on RadioLib's calculateTimeOnAir()
uint32_t tsym_us = ((uint32_t)10000 << sf) / (bw * 10);
uint32_t sfCoeff1_x4 = (sf == 5 || sf == 6) ? 25 : 17; // 6.25 : 4.25, semtech magic numbers to account for sync word + sfd
// preamble + syncword + sfd + header
uint32_t preamble_us = (((preambleSymbols + 8) * 4 + sfCoeff1_x4) * tsym_us) / 4;
// airtime for max packet at current radio settings
uint32_t total_us = _radio->getTimeOnAir(MAX_TRANS_UNIT);
// airtime for payload only (no preamble, header or SOF)
uint32_t payload_us = total_us > preamble_us ? total_us - preamble_us : 4000 - preamble_us; // fallback to 4 secs at worst case
// rescale payload_us for max possible CR
if (cr >= 5 && cr < 8) { payload_us = (payload_us * 8) / cr; }
return PacketMillis {(preamble_us + 999) / 1000, (payload_us + 999) / 1000};
}
+11 -2
View File
@@ -3,12 +3,18 @@
#include <Mesh.h>
#include <RadioLib.h>
struct PacketMillis {
uint32_t preambleMillis; // preamble-detect -> header-valid deadline
uint32_t payloadMillis; // header-valid -> rx-done deadline
};
class RadioLibWrapper : public mesh::Radio {
protected:
PhysicalLayer* _radio;
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
@@ -32,7 +38,7 @@ public:
bool isInRecvMode() const override;
bool isChannelActive();
bool isReceiving() override {
bool isReceiving() override {
if (isReceivingPacket()) return true;
return isChannelActive();
@@ -46,9 +52,12 @@ public:
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols);
virtual int16_t performChannelScan();
int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override;
void loop() override;
@@ -63,7 +72,7 @@ public:
float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10
virtual void setRxBoostedGainMode(bool) { }
virtual bool setRxBoostedGainMode(bool) { return false; }
virtual bool getRxBoostedGainMode() const { return false; }
};
@@ -807,6 +807,13 @@ void EnvironmentSensorManager::rakGPSInit(){
bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
#if defined(ETHERNET_ENABLED) && defined(RAK_BOARD)
if (ioPin == WB_IO2) {
// WB_IO2 powers the Ethernet module on RAK baseboards.
return false;
}
#endif
//set initial waking state
pinMode(ioPin,OUTPUT);
digitalWrite(ioPin,LOW);
@@ -889,11 +896,11 @@ void EnvironmentSensorManager::stop_gps() {
void EnvironmentSensorManager::loop() {
#if ENV_INCLUDE_GPS
static long next_gps_update = 0;
static unsigned long next_gps_update = 0;
if (gps_active) {
_location->loop();
}
if (millis() > next_gps_update) {
if ((long)(millis() - next_gps_update) > 0) {
if(gps_active){
#ifdef RAK_WISBLOCK_GPS
+1 -1
View File
@@ -142,7 +142,7 @@ public:
case LPP_GPS:
_pos += 9; break;
case LPP_POLYLINE:
_pos += 8; break; // TODO: this is MINIMIUM
_pos += 8; break; // TODO: this is MINIMUM
case LPP_GYROMETER:
case LPP_ACCELEROMETER:
_pos += 6; break;
+22 -20
View File
@@ -13,8 +13,12 @@
#endif
#endif
#ifndef PIN_GPS_EN_ACTIVE
#define PIN_GPS_EN_ACTIVE HIGH
#ifndef GPS_EN_ACTIVE
#ifdef PIN_GPS_EN_ACTIVE
#define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE
#else
#define GPS_EN_ACTIVE HIGH
#endif
#endif
#ifndef GPS_RESET
@@ -25,11 +29,11 @@
#endif
#endif
#ifndef GPS_RESET_FORCE
#ifndef GPS_RESET_ACTIVE
#ifdef PIN_GPS_RESET_ACTIVE
#define GPS_RESET_FORCE PIN_GPS_RESET_ACTIVE
#define GPS_RESET_ACTIVE PIN_GPS_RESET_ACTIVE
#else
#define GPS_RESET_FORCE LOW
#define GPS_RESET_ACTIVE LOW
#endif
#endif
@@ -42,29 +46,27 @@ class MicroNMEALocationProvider : public LocationProvider {
int8_t _claims = 0;
int _pin_reset;
int _pin_en;
long next_check = 0;
unsigned long next_check = 0;
long time_valid = 0;
unsigned long _last_time_sync = 0;
static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes
public :
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) :
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) {
nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) {
if (_pin_reset != -1) {
pinMode(_pin_reset, OUTPUT);
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
}
if (_pin_en != -1) {
pinMode(_pin_en, OUTPUT);
digitalWrite(_pin_en, LOW);
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
}
}
void claim() {
_claims++;
if (_claims > 0) {
if (_peripher_power) _peripher_power->claim();
}
if (_peripher_power) _peripher_power->claim();
}
void release() {
@@ -76,27 +78,27 @@ public :
void begin() override {
claim();
if (_pin_en != -1) {
digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE);
digitalWrite(_pin_en, GPS_EN_ACTIVE);
}
if (_pin_reset != -1) {
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
}
}
void reset() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
delay(10);
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
}
}
void stop() override {
if (_pin_en != -1) {
digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE);
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
}
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
}
release();
}
@@ -105,7 +107,7 @@ public :
// directly read the enable pin if present as gps can be
// activated/deactivated outside of here ...
if (_pin_en != -1) {
return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE;
return digitalRead(_pin_en) == GPS_EN_ACTIVE;
} else {
return true; // no enable so must be active
}
@@ -143,7 +145,7 @@ public :
if (!isValid()) time_valid = 0;
if (millis() > next_check) {
if ((long)(millis() - next_check) > 0) {
next_check = millis() + 1000;
// Re-enable time sync periodically when GPS has valid fix
if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) {
+3 -3
View File
@@ -37,7 +37,7 @@ static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, l
}
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// been erased. Negative error codes are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
@@ -62,7 +62,7 @@ static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, l
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block)
{
@@ -87,7 +87,7 @@ static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block)
}
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
// are propagated to the user.
static int _internal_flash_sync(const struct lfs_config *c)
{
return LFS_ERR_OK; // don't need sync
+11 -3
View File
@@ -3,12 +3,20 @@
#include <stdint.h>
#include <string.h>
using ColorVal = uint16_t;
class UIColor {
public:
// color definitions (by element _type_)
static ColorVal window_bkg, title_bkg, title_txt, primary_txt, secondary_txt, warning_txt, popup_bkg, popup_txt, corp_blue;
};
class DisplayDriver {
int _w, _h;
protected:
DisplayDriver(int w, int h) { _w = w; _h = h; }
public:
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
//enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
int width() const { return _w; }
int height() const { return _h; }
@@ -18,9 +26,9 @@ public:
virtual void turnOn() = 0;
virtual void turnOff() = 0;
virtual void clear() = 0;
virtual void startFrame(Color bkg = DARK) = 0;
virtual void startFrame(ColorVal bkg = UIColor::window_bkg) = 0;
virtual void setTextSize(int sz) = 0;
virtual void setColor(Color c) = 0;
virtual void setColor(ColorVal c) = 0;
virtual void setCursor(int x, int y) = 0;
virtual void print(const char* str) = 0;
virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override
+22 -8
View File
@@ -2,6 +2,17 @@
#include "../../MeshCore.h"
// Color scheme
ColorVal UIColor::window_bkg = WHITE;
ColorVal UIColor::title_bkg = WHITE;
ColorVal UIColor::title_txt = BLACK;
ColorVal UIColor::primary_txt = BLACK;
ColorVal UIColor::secondary_txt = BLACK;
ColorVal UIColor::warning_txt = BLACK;
ColorVal UIColor::popup_bkg = WHITE;
ColorVal UIColor::popup_txt = BLACK;
ColorVal UIColor::corp_blue = BLACK;
BaseDisplay* E213Display::detectEInk()
{
// Test 1: Logic of BUSY pin
@@ -108,16 +119,18 @@ void E213Display::clear() {
display->clear();
}
void E213Display::startFrame(Color bkg) {
void E213Display::startFrame(ColorVal bkg) {
display_crc.reset();
// Fill screen with white first to ensure clean background
display->fillRect(0, 0, width(), height(), WHITE);
if (bkg == LIGHT) {
if (bkg == 0) {
// Fill with black if light background requested (inverted for e-ink)
display->fillRect(0, 0, width(), height(), BLACK);
}
_color = UIColor::primary_txt;
display->setTextColor(_color);
}
void E213Display::setTextSize(int sz) {
@@ -126,9 +139,10 @@ void E213Display::setTextSize(int sz) {
display->setTextSize(sz);
}
void E213Display::setColor(Color c) {
display_crc.update<Color>(c);
// implemented in individual display methods
void E213Display::setColor(ColorVal c) {
_color = c;
display_crc.update<ColorVal>(c);
display->setTextColor(_color);
}
void E213Display::setCursor(int x, int y) {
@@ -147,7 +161,7 @@ void E213Display::fillRect(int x, int y, int w, int h) {
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display->fillRect(x, y, w, h, BLACK);
display->fillRect(x, y, w, h, _color);
}
void E213Display::drawRect(int x, int y, int w, int h) {
@@ -155,7 +169,7 @@ void E213Display::drawRect(int x, int y, int w, int h) {
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display->drawRect(x, y, w, h, BLACK);
display->drawRect(x, y, w, h, _color);
}
void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
@@ -179,7 +193,7 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
// If the bit is set, draw the pixel
if (bitSet) {
display->drawPixel(x + bx, y + by, BLACK);
display->drawPixel(x + bx, y + by, _color);
}
}
}
+3 -2
View File
@@ -16,6 +16,7 @@ class E213Display : public DisplayDriver {
RefCountedDigitalPin* _periph_power;
CRC32 display_crc;
uint32_t last_display_crc_value = 0;
uint16_t _color;
public:
E213Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(250, 122), _periph_power(periph_power) {}
@@ -30,9 +31,9 @@ public:
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char *str) override;
void fillRect(int x, int y, int w, int h) override;
+22 -8
View File
@@ -2,6 +2,17 @@
#include "../../MeshCore.h"
// Color scheme
ColorVal UIColor::window_bkg = WHITE;
ColorVal UIColor::title_bkg = WHITE;
ColorVal UIColor::title_txt = BLACK;
ColorVal UIColor::primary_txt = BLACK;
ColorVal UIColor::secondary_txt = BLACK;
ColorVal UIColor::warning_txt = BLACK;
ColorVal UIColor::popup_bkg = WHITE;
ColorVal UIColor::popup_txt = BLACK;
ColorVal UIColor::corp_blue = BLACK;
bool E290Display::begin() {
if (_init) return true;
@@ -62,15 +73,17 @@ void E290Display::clear() {
display.clear();
}
void E290Display::startFrame(Color bkg) {
void E290Display::startFrame(ColorVal bkg) {
display_crc.reset();
// Fill screen with white first to ensure clean background
display.fillRect(0, 0, width(), height(), WHITE);
if (bkg == LIGHT) {
if (bkg == 0) {
// Fill with black if light background requested (inverted for e-ink)
display.fillRect(0, 0, width(), height(), BLACK);
}
_color = UIColor::primary_txt;
display.setTextColor(_color);
}
void E290Display::setTextSize(int sz) {
@@ -79,9 +92,10 @@ void E290Display::setTextSize(int sz) {
display.setTextSize(sz);
}
void E290Display::setColor(Color c) {
display_crc.update<Color>(c);
// implemented in individual display methods
void E290Display::setColor(ColorVal c) {
_color = c;
display_crc.update<ColorVal>(c);
display.setTextColor(_color);
}
void E290Display::setCursor(int x, int y) {
@@ -100,7 +114,7 @@ void E290Display::fillRect(int x, int y, int w, int h) {
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display.fillRect(x, y, w, h, BLACK);
display.fillRect(x, y, w, h, _color);
}
void E290Display::drawRect(int x, int y, int w, int h) {
@@ -108,7 +122,7 @@ void E290Display::drawRect(int x, int y, int w, int h) {
display_crc.update<int>(y);
display_crc.update<int>(w);
display_crc.update<int>(h);
display.drawRect(x, y, w, h, BLACK);
display.drawRect(x, y, w, h, _color);
}
void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
@@ -132,7 +146,7 @@ void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
// If the bit is set, draw the pixel
if (bitSet) {
display.drawPixel(x + bx, y + by, BLACK);
display.drawPixel(x + bx, y + by, _color);
}
}
}
+3 -2
View File
@@ -16,6 +16,7 @@ class E290Display : public DisplayDriver {
RefCountedDigitalPin* _periph_power;
CRC32 display_crc;
uint32_t last_display_crc_value = 0;
uint16_t _color;
public:
E290Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(296, 128), _periph_power(periph_power) {}
@@ -26,9 +27,9 @@ public:
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char *str) override;
void fillRect(int x, int y, int w, int h) override;
+18 -11
View File
@@ -14,6 +14,18 @@
SPIClass SPI1 = SPIClass(FSPI);
#endif
// Color scheme
ColorVal UIColor::window_bkg = GxEPD_WHITE;
ColorVal UIColor::title_bkg = GxEPD_WHITE;
ColorVal UIColor::title_txt = GxEPD_BLACK;
ColorVal UIColor::primary_txt = GxEPD_BLACK;
ColorVal UIColor::secondary_txt = GxEPD_BLACK;
ColorVal UIColor::warning_txt = GxEPD_BLACK;
ColorVal UIColor::popup_bkg = GxEPD_WHITE;
ColorVal UIColor::popup_txt = GxEPD_BLACK;
ColorVal UIColor::corp_blue = GxEPD_BLACK;
bool GxEPDDisplay::begin() {
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
#ifdef ESP32
@@ -61,9 +73,9 @@ void GxEPDDisplay::clear() {
display_crc.reset();
}
void GxEPDDisplay::startFrame(Color bkg) {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(_curr_color = GxEPD_BLACK);
void GxEPDDisplay::startFrame(ColorVal bkg) {
display.fillScreen(bkg);
display.setTextColor(_curr_color = UIColor::primary_txt);
display_crc.reset();
}
@@ -85,14 +97,9 @@ void GxEPDDisplay::setTextSize(int sz) {
}
}
void GxEPDDisplay::setColor(Color c) {
display_crc.update<Color> (c);
// colours need to be inverted for epaper displays
if (c == DARK) {
display.setTextColor(_curr_color = GxEPD_WHITE);
} else {
display.setTextColor(_curr_color = GxEPD_BLACK);
}
void GxEPDDisplay::setColor(ColorVal c) {
display_crc.update<ColorVal> (c);
display.setTextColor(_curr_color = c);
}
void GxEPDDisplay::setCursor(int x, int y) {
+2 -2
View File
@@ -51,9 +51,9 @@ public:
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
+17 -31
View File
@@ -1,5 +1,16 @@
#include "LGFXDisplay.h"
// Color scheme
ColorVal UIColor::window_bkg = 0xFFFF;
ColorVal UIColor::title_bkg = 0x001F;
ColorVal UIColor::title_txt = 0xFFFF;
ColorVal UIColor::primary_txt = 0x0000;
ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray
ColorVal UIColor::warning_txt = 0xFD20;
ColorVal UIColor::popup_bkg = 0x07FF; // CYAN
ColorVal UIColor::popup_txt = 0x0000;
ColorVal UIColor::corp_blue = 0x001A;
bool LGFXDisplay::begin() {
turnOn();
display->init();
@@ -35,45 +46,20 @@ void LGFXDisplay::clear() {
buffer.clearDisplay();
}
void LGFXDisplay::startFrame(Color bkg) {
void LGFXDisplay::startFrame(ColorVal bkg) {
// display->startWrite();
// display->getScanLine();
buffer.clearDisplay();
buffer.setTextColor(TFT_WHITE);
_color = bkg;
buffer.fillScreen(_color);
buffer.setTextColor(_color = UIColor::primary_txt);
}
void LGFXDisplay::setTextSize(int sz) {
buffer.setTextSize(sz);
}
void LGFXDisplay::setColor(Color c) {
// _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK;
switch (c) {
case DARK:
_color = TFT_BLACK;
break;
case LIGHT:
_color = TFT_WHITE;
break;
case RED:
_color = TFT_RED;
break;
case GREEN:
_color = TFT_GREEN;
break;
case BLUE:
_color = TFT_BLUE;
break;
case YELLOW:
_color = TFT_YELLOW;
break;
case ORANGE:
_color = TFT_ORANGE;
break;
default:
_color = TFT_WHITE;
}
buffer.setTextColor(_color);
void LGFXDisplay::setColor(ColorVal c) {
buffer.setTextColor(_color = c);
}
void LGFXDisplay::setCursor(int x, int y) {
+2 -2
View File
@@ -25,9 +25,9 @@ public:
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
+551
View File
@@ -0,0 +1,551 @@
#include "NV3001BDisplay.h"
#include <Arduino.h>
#include <string.h>
#ifndef SPI_FREQUENCY
#define SPI_FREQUENCY 8000000
#endif
#ifndef PIN_TFT_SCL
#error "PIN_TFT_SCL must be defined"
#endif
#ifndef PIN_TFT_SDA
#error "PIN_TFT_SDA must be defined"
#endif
#ifndef PIN_TFT_CS
#error "PIN_TFT_CS must be defined"
#endif
#ifndef PIN_TFT_DC
#error "PIN_TFT_DC must be defined"
#endif
#ifndef PIN_TFT_MISO
#define PIN_TFT_MISO -1
#endif
#ifndef PIN_TFT_RST
#define PIN_TFT_RST -1
#endif
#ifndef PIN_TFT_EN
#define PIN_TFT_EN -1
#endif
#ifndef PIN_TFT_BL
#define PIN_TFT_BL -1
#endif
#ifndef PIN_TFT_EN_ACTIVE
#define PIN_TFT_EN_ACTIVE LOW
#endif
#ifndef PIN_TFT_BL_ACTIVE
#define PIN_TFT_BL_ACTIVE HIGH
#endif
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 0
#endif
#ifndef NV3001B_SCREEN_WIDTH
#define NV3001B_SCREEN_WIDTH 220
#endif
#ifndef NV3001B_SCREEN_HEIGHT
#define NV3001B_SCREEN_HEIGHT 128
#endif
#ifndef DISPLAY_SCALE_X
#define DISPLAY_SCALE_X ((float)NV3001B_SCREEN_WIDTH / NV3001B_LOGICAL_WIDTH)
#endif
#ifndef DISPLAY_SCALE_Y
#define DISPLAY_SCALE_Y ((float)NV3001B_SCREEN_HEIGHT / NV3001B_LOGICAL_HEIGHT)
#endif
#define NV3001B_SWRESET 0x01
#define NV3001B_SLPOUT 0x11
#define NV3001B_DISPON 0x29
#define NV3001B_CASET 0x2A
#define NV3001B_RASET 0x2B
#define NV3001B_RAMWR 0x2C
#define NV3001B_MADCTL 0x36
#define NV3001B_COLMOD 0x3A
#define NV3001B_MADCTL_MY 0x80
#define NV3001B_MADCTL_MX 0x40
#define NV3001B_MADCTL_MV 0x20
#define NV3001B_MADCTL_RGB 0x00
#ifndef NV3001B_TEXT_SIZE1_SCALE_X
#define NV3001B_TEXT_SIZE1_SCALE_X 1
#endif
#ifndef NV3001B_TEXT_SIZE1_SCALE_Y
#define NV3001B_TEXT_SIZE1_SCALE_Y 2
#endif
#ifndef NV3001B_TEXT_SIZE2_SCALE_X
#define NV3001B_TEXT_SIZE2_SCALE_X 2
#endif
#ifndef NV3001B_TEXT_SIZE2_SCALE_Y
#define NV3001B_TEXT_SIZE2_SCALE_Y 3
#endif
// Color scheme
ColorVal UIColor::window_bkg = 0xFFFF;
ColorVal UIColor::title_bkg = 0x001F;
ColorVal UIColor::title_txt = 0xFFFF;
ColorVal UIColor::primary_txt = 0x0000;
ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray
ColorVal UIColor::warning_txt = 0xFD20;
ColorVal UIColor::popup_bkg = 0x07FF; // CYAN
ColorVal UIColor::popup_txt = 0x0000;
ColorVal UIColor::corp_blue = 0x001A;
static int scaleX(int x) {
return (int)(x * DISPLAY_SCALE_X);
}
static int scaleY(int y) {
return (int)(y * DISPLAY_SCALE_Y);
}
static int scaleWidth(int x, int w) {
if (w <= 0) return 0;
int scaled = scaleX(x + w) - scaleX(x);
return scaled > 0 ? scaled : 1;
}
static int scaleHeight(int y, int h) {
if (h <= 0) return 0;
int scaled = scaleY(y + h) - scaleY(y);
return scaled > 0 ? scaled : 1;
}
static uint8_t nv3001bMADCTL(uint8_t rotation) {
uint8_t madctl;
switch (rotation & 3) {
case 0:
madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB;
break;
case 1:
madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MX | NV3001B_MADCTL_RGB;
break;
case 2:
madctl = NV3001B_MADCTL_RGB;
break;
default:
madctl = NV3001B_MADCTL_MX | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB;
break;
}
return madctl;
}
static const uint8_t font5x7[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14,
0x7f, 0x14, 0x7f, 0x14, 0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49,
0x55, 0x22, 0x50, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22,
0x1c, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x50, 0x30, 0x00,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x60, 0x60, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02,
0x3e, 0x51, 0x49, 0x45, 0x3e, 0x00, 0x42, 0x7f, 0x40, 0x00, 0x42, 0x61, 0x51, 0x49, 0x46, 0x21,
0x41, 0x45, 0x4b, 0x31, 0x18, 0x14, 0x12, 0x7f, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3c, 0x4a,
0x49, 0x49, 0x30, 0x01, 0x71, 0x09, 0x05, 0x03, 0x36, 0x49, 0x49, 0x49, 0x36, 0x06, 0x49, 0x49,
0x29, 0x1e, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x56, 0x36, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41,
0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x51, 0x09, 0x06,
0x32, 0x49, 0x79, 0x41, 0x3e, 0x7e, 0x11, 0x11, 0x11, 0x7e, 0x7f, 0x49, 0x49, 0x49, 0x36, 0x3e,
0x41, 0x41, 0x41, 0x22, 0x7f, 0x41, 0x41, 0x22, 0x1c, 0x7f, 0x49, 0x49, 0x49, 0x41, 0x7f, 0x09,
0x09, 0x09, 0x01, 0x3e, 0x41, 0x49, 0x49, 0x7a, 0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00, 0x41, 0x7f,
0x41, 0x00, 0x20, 0x40, 0x41, 0x3f, 0x01, 0x7f, 0x08, 0x14, 0x22, 0x41, 0x7f, 0x40, 0x40, 0x40,
0x40, 0x7f, 0x02, 0x0c, 0x02, 0x7f, 0x7f, 0x04, 0x08, 0x10, 0x7f, 0x3e, 0x41, 0x41, 0x41, 0x3e,
0x7f, 0x09, 0x09, 0x09, 0x06, 0x3e, 0x41, 0x51, 0x21, 0x5e, 0x7f, 0x09, 0x19, 0x29, 0x46, 0x46,
0x49, 0x49, 0x49, 0x31, 0x01, 0x01, 0x7f, 0x01, 0x01, 0x3f, 0x40, 0x40, 0x40, 0x3f, 0x1f, 0x20,
0x40, 0x20, 0x1f, 0x3f, 0x40, 0x38, 0x40, 0x3f, 0x63, 0x14, 0x08, 0x14, 0x63, 0x07, 0x08, 0x70,
0x08, 0x07, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x7f, 0x41, 0x41, 0x00, 0x02, 0x04, 0x08, 0x10,
0x20, 0x00, 0x41, 0x41, 0x7f, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x01, 0x02, 0x04, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x7f, 0x48, 0x44, 0x44, 0x38, 0x38,
0x44, 0x44, 0x44, 0x20, 0x38, 0x44, 0x44, 0x48, 0x7f, 0x38, 0x54, 0x54, 0x54, 0x18, 0x08, 0x7e,
0x09, 0x01, 0x02, 0x0c, 0x52, 0x52, 0x52, 0x3e, 0x7f, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7d,
0x40, 0x00, 0x20, 0x40, 0x44, 0x3d, 0x00, 0x7f, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7f, 0x40,
0x00, 0x7c, 0x04, 0x18, 0x04, 0x78, 0x7c, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38,
0x7c, 0x14, 0x14, 0x14, 0x08, 0x08, 0x14, 0x14, 0x18, 0x7c, 0x7c, 0x08, 0x04, 0x04, 0x08, 0x48,
0x54, 0x54, 0x54, 0x20, 0x04, 0x3f, 0x44, 0x40, 0x20, 0x3c, 0x40, 0x40, 0x20, 0x7c, 0x1c, 0x20,
0x40, 0x20, 0x1c, 0x3c, 0x40, 0x30, 0x40, 0x3c, 0x44, 0x28, 0x10, 0x28, 0x44, 0x0c, 0x50, 0x50,
0x50, 0x3c, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x7f, 0x00,
0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00, 0x06, 0x09, 0x09, 0x06
};
static int textPixelScaleX(uint8_t size) {
return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_X : NV3001B_TEXT_SIZE2_SCALE_X;
}
static int textPixelScaleY(uint8_t size) {
return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_Y : NV3001B_TEXT_SIZE2_SCALE_Y;
}
static void setupOptionalOutput(int pin, int level) {
if (pin < 0) return;
pinMode(pin, OUTPUT);
digitalWrite(pin, level);
}
static void writeOptionalPin(int pin, int level) {
if (pin < 0) return;
digitalWrite(pin, level);
}
void NV3001BDisplay::writeCommand(uint8_t cmd) {
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, LOW);
digitalWrite(PIN_TFT_CS, LOW);
spi.transfer(cmd);
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::writeBytes(const uint8_t* data, size_t len) {
if (!data || len == 0) return;
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, HIGH);
digitalWrite(PIN_TFT_CS, LOW);
for (size_t i = 0; i < len; i++) {
spi.transfer(data[i]);
}
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::writeCommandData(uint8_t cmd, const uint8_t* data, size_t len) {
writeCommand(cmd);
writeBytes(data, len);
}
void NV3001BDisplay::setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
uint16_t x2 = x + w - 1;
uint16_t y2 = y + h - 1;
uint8_t data[4];
data[0] = x >> 8;
data[1] = x & 0xff;
data[2] = x2 >> 8;
data[3] = x2 & 0xff;
writeCommandData(NV3001B_CASET, data, sizeof(data));
data[0] = y >> 8;
data[1] = y & 0xff;
data[2] = y2 >> 8;
data[3] = y2 & 0xff;
writeCommandData(NV3001B_RASET, data, sizeof(data));
writeCommand(NV3001B_RAMWR);
}
void NV3001BDisplay::writeColor(uint16_t rgb, uint32_t count) {
uint8_t hi = rgb >> 8;
uint8_t lo = rgb & 0xff;
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, HIGH);
digitalWrite(PIN_TFT_CS, LOW);
while (count--) {
spi.transfer(hi);
spi.transfer(lo);
}
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::initPanel() {
#define CMD0(C) do { writeCommand(C); } while (0)
#define CMD1(C, A) do { const uint8_t d[] = { A }; writeCommandData(C, d, sizeof(d)); } while (0)
#define CMD2(C, A, B) do { const uint8_t d[] = { A, B }; writeCommandData(C, d, sizeof(d)); } while (0)
CMD0(NV3001B_SWRESET);
delay(120);
CMD1(0xFF, 0xA5);
CMD1(0x41, 0x00);
CMD1(0x50, 0x02);
CMD1(0x52, 0x6E);
CMD1(0x57, 0x02);
CMD1(0x46, 0x11);
CMD2(0x47, 0x00, 0x01);
CMD2(0x8F, 0x22, 0x03);
CMD1(0x9A, 0x78);
CMD1(0x9B, 0x78);
CMD1(0x9C, 0xA0);
CMD1(0x9D, 0x17);
CMD1(0x9E, 0xC1);
CMD1(0x83, 0x5A);
CMD1(0x84, 0xB6);
CMD1(0xFF, 0xA5);
CMD1(0x85, 0x5F);
CMD1(0x6E, 0x0F);
CMD1(0x7E, 0x0F);
CMD1(0x60, 0x00);
CMD1(0x70, 0x00);
CMD1(0x6D, 0x33);
CMD1(0x7D, 0x37);
CMD1(0x61, 0x09);
CMD1(0x71, 0x0A);
CMD1(0x6C, 0x2A);
CMD1(0x7C, 0x36);
CMD1(0x62, 0x11);
CMD1(0x72, 0x10);
CMD1(0x68, 0x4E);
CMD1(0x78, 0x4E);
CMD1(0x66, 0x36);
CMD1(0x76, 0x3C);
CMD1(0x1A, 0x1C);
CMD1(0x7B, 0x14);
CMD1(0x63, 0x0D);
CMD1(0x73, 0x0A);
CMD1(0x6A, 0x16);
CMD1(0x7A, 0x12);
CMD1(0x64, 0x0B);
CMD1(0x74, 0x0A);
CMD1(0x69, 0x08);
CMD1(0x79, 0x0A);
CMD1(0x65, 0x06);
CMD1(0x75, 0x07);
CMD1(0x67, 0x23);
CMD1(0x77, 0x44);
CMD1(0xE0, 0x00);
CMD1(0xE9, 0x30);
CMD1(0xEB, 0xB7);
CMD1(0xEC, 0x00);
CMD1(0xED, 0x11);
CMD1(0xF0, 0xB7);
CMD1(0x53, 0x04);
CMD1(0x54, 0x04);
CMD1(0x55, 0x40);
CMD1(0x56, 0x40);
CMD2(0xA0, 0x60, 0x01);
CMD1(0xA1, 0x84);
CMD1(0xA2, 0x85);
CMD2(0xAB, 0x00, 0x02);
CMD2(0xAC, 0x00, 0x06);
CMD2(0xAD, 0x00, 0x03);
CMD2(0xAE, 0x00, 0x07);
CMD1(0xC7, 0x01);
CMD1(0xB9, 0x82);
CMD1(0xBA, 0x83);
CMD1(0xBB, 0x00);
CMD1(0xBC, 0x81);
CMD1(0xBD, 0x02);
CMD1(0xBE, 0x01);
CMD1(0xBF, 0x04);
CMD1(0xC0, 0x03);
CMD1(0xC8, 0x55);
CMD1(0xC9, 0xC9);
CMD1(0xCA, 0xC8);
CMD1(0xCB, 0xCB);
CMD1(0xCC, 0xCA);
CMD1(0xCD, 0x55);
CMD1(0xCE, 0xCE);
CMD1(0xCF, 0xCD);
CMD1(0xD0, 0xD0);
CMD1(0xD1, 0xCF);
CMD1(0xF2, 0x46);
CMD1(0xA8, 0x04);
CMD1(0xA9, 0xB0);
CMD1(0xAA, 0xA3);
CMD1(0xB6, 0x00);
CMD1(0xB7, 0xB0);
CMD1(0xB8, 0xA3);
CMD1(0xC4, 0x03);
CMD1(0xC5, 0xB0);
CMD1(0xC6, 0xA3);
CMD1(0x80, 0x10);
CMD1(0xFF, 0x00);
CMD1(0x35, 0x00);
CMD0(NV3001B_SLPOUT);
delay(120);
CMD1(NV3001B_COLMOD, 0x05);
CMD1(NV3001B_MADCTL, nv3001bMADCTL(DISPLAY_ROTATION));
CMD0(NV3001B_DISPON);
delay(10);
#undef CMD0
#undef CMD1
#undef CMD2
}
void NV3001BDisplay::fillPhysicalRect(int x, int y, int w, int h) {
if (!is_on || w <= 0 || h <= 0) return;
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x + w > NV3001B_SCREEN_WIDTH) w = NV3001B_SCREEN_WIDTH - x;
if (y + h > NV3001B_SCREEN_HEIGHT) h = NV3001B_SCREEN_HEIGHT - y;
if (w <= 0 || h <= 0) return;
setAddrWindow(x, y, w, h);
writeColor(color, (uint32_t)w * h);
}
void NV3001BDisplay::drawChar(int x, int y, char ch) {
if (ch < 32 || ch > 127) ch = '?';
uint16_t index = (uint16_t)(ch - 32) * 5;
int scale_x = textPixelScaleX(text_size);
int scale_y = textPixelScaleY(text_size);
for (int col = 0; col < 5; col++) {
uint8_t line = pgm_read_byte(font5x7 + index + col);
for (int row = 0; row < 7; row++) {
if (line & (1 << row)) {
fillPhysicalRect(x + col * scale_x, y + row * scale_y, scale_x, scale_y);
}
}
}
}
bool NV3001BDisplay::begin() {
if (is_on) return true;
if (periph_power) periph_power->claim();
setupOptionalOutput(PIN_TFT_EN, PIN_TFT_EN_ACTIVE);
setupOptionalOutput(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE);
pinMode(PIN_TFT_CS, OUTPUT);
pinMode(PIN_TFT_DC, OUTPUT);
digitalWrite(PIN_TFT_CS, HIGH);
digitalWrite(PIN_TFT_DC, HIGH);
delay(20);
spi.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS);
if (PIN_TFT_RST >= 0) {
pinMode(PIN_TFT_RST, OUTPUT);
digitalWrite(PIN_TFT_RST, HIGH);
delay(10);
digitalWrite(PIN_TFT_RST, LOW);
delay(20);
digitalWrite(PIN_TFT_RST, HIGH);
delay(120);
}
initPanel();
is_on = true;
color = 0x0000;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = 0xffff;
text_size = 1;
cursor_x = 0;
cursor_y = 0;
writeOptionalPin(PIN_TFT_BL, PIN_TFT_BL_ACTIVE);
return true;
}
void NV3001BDisplay::turnOn() {
begin();
}
void NV3001BDisplay::turnOff() {
if (!is_on) return;
writeOptionalPin(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE);
writeOptionalPin(PIN_TFT_EN, !PIN_TFT_EN_ACTIVE);
is_on = false;
if (periph_power) periph_power->release();
}
void NV3001BDisplay::clear() {
uint16_t saved = color;
color = UIColor::window_bkg;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = saved;
}
void NV3001BDisplay::startFrame(ColorVal bkg) {
color = bkg;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = UIColor::primary_txt;
text_size = 1;
cursor_x = 0;
cursor_y = 0;
}
void NV3001BDisplay::setTextSize(int sz) {
text_size = sz < 1 ? 1 : sz;
}
void NV3001BDisplay::setColor(ColorVal c) {
color = c;
}
void NV3001BDisplay::setCursor(int x, int y) {
cursor_x = scaleX(x);
cursor_y = scaleY(y);
}
void NV3001BDisplay::print(const char* str) {
if (!str || !is_on) return;
int scale_x = textPixelScaleX(text_size);
int scale_y = textPixelScaleY(text_size);
while (*str) {
if (*str == '\n') {
cursor_x = 0;
cursor_y += 8 * scale_y;
} else if (*str == '\r') {
cursor_x = 0;
} else {
drawChar(cursor_x, cursor_y, *str);
cursor_x += 6 * scale_x;
}
str++;
}
}
void NV3001BDisplay::fillRect(int x, int y, int w, int h) {
fillPhysicalRect(scaleX(x), scaleY(y), scaleWidth(x, w), scaleHeight(y, h));
}
void NV3001BDisplay::drawRect(int x, int y, int w, int h) {
int x1 = scaleX(x);
int y1 = scaleY(y);
int sw = scaleWidth(x, w);
int sh = scaleHeight(y, h);
fillPhysicalRect(x1, y1, sw, 1);
fillPhysicalRect(x1, y1 + sh - 1, sw, 1);
fillPhysicalRect(x1, y1, 1, sh);
fillPhysicalRect(x1 + sw - 1, y1, 1, sh);
}
void NV3001BDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
if (!bits || !is_on) return;
int byte_width = (w + 7) / 8;
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
uint8_t byte = pgm_read_byte(bits + j * byte_width + i / 8);
if (byte & (0x80 >> (i & 7))) {
fillPhysicalRect(scaleX(x + i), scaleY(y + j), scaleWidth(x + i, 1), scaleHeight(y + j, 1));
}
}
}
}
uint16_t NV3001BDisplay::getTextWidth(const char* str) {
if (!str) return 0;
uint16_t len = 0;
while (str[len] && str[len] != '\n' && str[len] != '\r') len++;
return (uint16_t)((len * 6 * textPixelScaleX(text_size)) / DISPLAY_SCALE_X);
}
void NV3001BDisplay::endFrame() {
}

Some files were not shown because too many files have changed in this diff Show More