Compare commits

...
185 Commits
Author SHA1 Message Date
fdlamotteandGitHub 427cd7c263 Merge pull request #2987 from entr0p1/fix/nrf52-shutdownperipherals
NRF52Board - ShutdownPeripherals() housekeeping
2026-07-20 11:53:56 -04: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
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
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
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
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
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 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
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
Ryan Gregg 2a2c7a171d Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts:
#	docs/faq.md
2026-04-20 15:06:09 +00: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
208 changed files with 6367 additions and 875 deletions
@@ -25,5 +25,14 @@ runs:
- name: Extract Version from Git Tag - name: Extract Version from Git Tag
shell: bash shell: bash
run: | run: |
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/}" GIT_TAG_NAME="${GITHUB_REF#refs/tags/}"
echo "GIT_TAG_VERSION=${GIT_TAG_NAME##*-}" >> $GITHUB_ENV 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-*' - 'companion-*'
jobs: jobs:
build-companion-firmwares:
build: uses: ./.github/workflows/firmware-builder.yml
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: with:
name: companion-firmwares firmware_type: 'companion'
path: out release_title_prefix: 'Companion Firmware'
- 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/*
+4 -29
View File
@@ -10,33 +10,8 @@ on:
- 'repeater-*' - 'repeater-*'
jobs: jobs:
build-repeater-firmwares:
build: uses: ./.github/workflows/firmware-builder.yml
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: with:
name: repeater-firmwares firmware_type: 'repeater'
path: out release_title_prefix: 'Repeater Firmware'
- 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/*
@@ -10,33 +10,8 @@ on:
- 'room-server-*' - 'room-server-*'
jobs: jobs:
build-room-server-firmwares:
build: uses: ./.github/workflows/firmware-builder.yml
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: with:
name: room-server-firmwares firmware_type: 'room-server'
path: out release_title_prefix: 'Room Server Firmware'
- 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/*
+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 - Heltec_v3_room_server
# nRF52 # nRF52
- RAK_4631_companion_radio_ble - RAK_4631_companion_radio_ble
- RAK_4631_companion_radio_ethernet
- RAK_4631_repeater - RAK_4631_repeater
- RAK_4631_repeater_ethernet
- RAK_4631_room_server - RAK_4631_room_server
- RAK_4631_room_server_ethernet
# RP2040 # RP2040
- PicoW_repeater - PicoW_repeater
# STM32 # 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"
}
+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 #!/usr/bin/env bash
# exit when any command fails
set -e
global_usage() { global_usage() {
cat - <<EOF cat - <<EOF
Usage: Usage:
@@ -93,7 +96,7 @@ get_pio_envs_ending_with_string() {
# $1 should be the environment name # $1 should be the environment name
get_platform_for_env() { get_platform_for_env() {
local env_name=$1 local env_name=$1
echo "$PIO_CONFIG_JSON" | python3 -c " printf '%s' "$PIO_CONFIG_JSON" | python3 -c "
import sys, json, re import sys, json, re
data = json.load(sys.stdin) data = json.load(sys.stdin)
for section, options in data: for section, options in data:
@@ -275,4 +278,11 @@ elif [[ $1 == "build-repeater-firmwares" ]]; then
build_repeater_firmwares build_repeater_firmwares
elif [[ $1 == "build-room-server-firmwares" ]]; then elif [[ $1 == "build-room-server-firmwares" ]]; then
build_room_server_firmwares 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 fi
+3 -2
View File
@@ -1,10 +1,11 @@
{ pkgs ? import <nixpkgs> {} }: {pkgs ? import <nixpkgs> {}}: let
let
in in
pkgs.mkShell { pkgs.mkShell {
buildInputs = [ buildInputs = [
pkgs.platformio pkgs.platformio
pkgs.python3 pkgs.python3
pkgs.gcc
pkgs.gtest
# optional: needed as a programmer i.e. for esp32 # optional: needed as a programmer i.e. for esp32
pkgs.avrdude pkgs.avrdude
]; ];
+62 -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) - [GPS](#gps-when-gps-support-is-compiled-in)
- [Sensors](#sensors-when-sensor-support-is-compiled-in) - [Sensors](#sensors-when-sensor-support-is-compiled-in)
- [Bridge](#bridge-when-bridge-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:** **Usage:**
- `reboot` - `reboot`
**Note:** No reply is sent.
---
### Power-off the node
**Usage:**
- `poweroff`, or
- `shutdown`
**Note:** No reply is sent.
--- ---
### Reset the clock and reboot ### Reset the clock and reboot
**Usage:** **Usage:**
- `clkreboot` - `clkreboot`
**Note:** No reply is sent.
--- ---
### Sync the clock with the remote device ### Sync the clock with the remote device
@@ -578,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 #### View or change the AGC Reset Interval
**Usage:** **Usage:**
- `get agc.reset.interval` - `get agc.reset.interval`
@@ -646,10 +674,21 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:** **Parameters:**
- `value`: Maximum flood hop count (0-64) for a packet without a scope (no region set) - `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. **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`
--- ---
@@ -1114,3 +1153,25 @@ region save
**Note:** Returns an error on boards without power management support. **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.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.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.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 ## 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 | | **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) | | | **Heltec V4** | Standard Output | 10 dBm | 22 dBm (~0.15W) | |
| | High Output | 22 dBm | 28 dBm (~0.5W to 0.6W) | | | | 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- | | | 0000 - 00FF | -reserved for internal use- | |
| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open |
| 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz | | 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- | | | FF00 - FFFF | -reserved for testing/dev- | |
(add rows, inside the range 0100 - FEFF for custom apps) (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**: **Parameters**:
- `name`: Channel name (URL-encoded if needed) - `name`: Channel name (URL-encoded)
- `secret`: 16-byte secret represented as 32 hex characters - `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 ## 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 pos = 0, found_pos = 0;
uint32_t min_timestamp = 0xFFFFFFFF; 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; BlobRec tmp;
file.seek(0); file.seek(0);
while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) {
+13
View File
@@ -261,6 +261,9 @@ float MyMesh::getAirtimeBudgetFactor() const {
int MyMesh::getInterferenceThreshold() const { int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved 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 { int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
if (_prefs.rx_delay_base <= 0.0f) return 0; 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 // query other sensors -- target specific
sensors.querySensors(permissions, telemetry); 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, memcpy(reply, &sender_timestamp,
4); // reflect sender_timestamp back in response packet (kind of like a 'tag') 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
@@ -1637,6 +1645,11 @@ void MyMesh::handleCmdFrame(size_t len) {
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len == 4) { // 'self' telemetry request } else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len == 4) { // 'self' telemetry request
telemetry.reset(); telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); 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 // query other sensors -- target specific
sensors.querySensors(0xFF, telemetry); sensors.querySensors(0xFF, telemetry);
+1
View File
@@ -105,6 +105,7 @@ public:
protected: protected:
float getAirtimeBudgetFactor() const override; float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override; int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int calcRxDelay(float score, uint32_t air_time) const override; int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override; uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
+28 -2
View File
@@ -74,6 +74,9 @@ static uint32_t _atoi(const char* sp) {
#ifdef BLE_PIN_CODE #ifdef BLE_PIN_CODE
#include <helpers/nrf52/SerialBLEInterface.h> #include <helpers/nrf52/SerialBLEInterface.h>
SerialBLEInterface serial_interface; SerialBLEInterface serial_interface;
#elif defined(ETHERNET_ENABLED)
#include <helpers/nrf52/SerialEthernetInterface.h>
SerialEthernetInterface serial_interface;
#else #else
#include <helpers/ArduinoSerialInterface.h> #include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface; ArduinoSerialInterface serial_interface;
@@ -113,9 +116,12 @@ void halt() {
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
board.begin(); board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
DisplayDriver* disp = NULL; DisplayDriver* disp = NULL;
if (display.begin()) { if (display.begin()) {
@@ -158,10 +164,23 @@ void setup() {
#ifdef BLE_PIN_CODE #ifdef BLE_PIN_CODE
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); 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 #else
serial_interface.begin(Serial); serial_interface.begin(Serial);
#endif
the_mesh.startInterface(serial_interface); the_mesh.startInterface(serial_interface);
#endif
#elif defined(RP2040_PLATFORM) #elif defined(RP2040_PLATFORM)
LittleFS.begin(); LittleFS.begin();
store.begin(); store.begin();
@@ -249,6 +268,13 @@ void loop() {
ui_task.loop(); ui_task.loop();
#endif #endif
rtc_clock.tick(); rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
#ifdef ETHERNET_ENABLED
serial_interface.loop();
#endif
if (!the_mesh.hasPendingWork()) { if (!the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
+11 -2
View File
@@ -697,8 +697,7 @@ void UITask::shutdown(bool restart){
if (restart) { if (restart) {
_board->reboot(); _board->reboot();
} else { } else {
_display->turnOff(); // Power off board including radio, display, GPS and components
radio_driver.powerOff();
_board->powerOff(); _board->powerOff();
} }
} }
@@ -748,6 +747,16 @@ void UITask::loop() {
c = handleTripleClick(KEY_SELECT); c = handleTripleClick(KEY_SELECT);
} }
#endif #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 defined(PIN_USER_BTN_ANA)
if (abs(millis() - _analogue_pin_read_millis) > 10) { if (abs(millis() - _analogue_pin_read_millis) > 10) {
int ev = analog_btn.check(); int ev = analog_btn.check();
+1 -1
View File
@@ -307,7 +307,7 @@ void UITask::shutdown(bool restart){
if (restart) { if (restart) {
_board->reboot(); _board->reboot();
} else { } else {
radio_driver.powerOff(); // Power off board including radio, display, GPS and components
_board->powerOff(); _board->powerOff();
} }
} }
+1 -2
View File
@@ -566,8 +566,7 @@ void UITask::shutdown(bool restart){
if (restart) { if (restart) {
_board->reboot(); _board->reboot();
} else { } else {
_display->turnOff(); // Power off board including radio, display, GPS and components
radio_driver.powerOff();
_board->powerOff(); _board->powerOff();
} }
} }
+6 -8
View File
@@ -549,8 +549,7 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
return getRNG()->nextInt(0, 5*t + 1); return getRNG()->nextInt(0, 5*t + 1);
} }
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
@@ -562,8 +561,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
} else { } else {
recv_pkt_region = NULL; recv_pkt_region = NULL;
} }
// do normal processing return Mesh::onRecvPacket(pkt);
return false;
} }
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, 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; set_radio_at = revert_radio_at = 0;
_logging = false; _logging = false;
region_load_active = false; region_load_active = false;
recv_pkt_region = NULL;
#if MAX_NEIGHBOURS #if MAX_NEIGHBOURS
memset(neighbours, 0, sizeof(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_unscoped = 64;
_prefs.flood_max_advert = 8; _prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled _prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// bridge defaults // bridge defaults
_prefs.bridge_enabled = 1; // enabled _prefs.bridge_enabled = 1; // enabled
@@ -1061,11 +1061,9 @@ void MyMesh::setTxPower(int8_t power_dbm) {
radio_driver.setTxPower(power_dbm); radio_driver.setTxPower(power_dbm);
} }
#if defined(USE_SX1262) || defined(USE_SX1268) bool MyMesh::setRxBoostedGain(bool enable) {
void MyMesh::setRxBoostedGain(bool enable) { return radio_driver.setRxBoostedGainMode(enable);
radio_driver.setRxBoostedGainMode(enable);
} }
#endif
void MyMesh::formatNeighborsReply(char *reply) { void MyMesh::formatNeighborsReply(char *reply) {
char *dp = reply; char *dp = reply;
+6 -4
View File
@@ -150,6 +150,9 @@ protected:
int getInterferenceThreshold() const override { int getInterferenceThreshold() const override {
return _prefs.interference_threshold; return _prefs.interference_threshold;
} }
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override { int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
} }
@@ -163,7 +166,7 @@ protected:
} }
#endif #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; 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; int searchPeersByHash(const uint8_t* hash) override;
@@ -249,7 +252,6 @@ public:
// To check if there is pending work // To check if there is pending work
bool hasPendingWork() const; bool hasPendingWork() const;
#if defined(USE_SX1262) || defined(USE_SX1268) bool setRxBoostedGain(bool enable) override;
void setRxBoostedGain(bool enable) override;
#endif
}; };
+46 -21
View File
@@ -1,4 +1,5 @@
#include "UITask.h" #include "UITask.h"
#include "target.h"
#include <Arduino.h> #include <Arduino.h>
#include <helpers/CommonCLI.h> #include <helpers/CommonCLI.h>
@@ -9,6 +10,8 @@
#define AUTO_OFF_MILLIS 20000 // 20 seconds #define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds
#define POWEROFF_DELAY 3000
// 'meshcore', 128x13px // 'meshcore', 128x13px
static const uint8_t meshcore_logo [] PROGMEM = { static const uint8_t meshcore_logo [] PROGMEM = {
0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 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) { void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
_prevBtnState = HIGH; _prevBtnState = HIGH;
_auto_off = millis() + AUTO_OFF_MILLIS; _auto_off = millis() + AUTO_OFF_MILLIS;
_started_at = millis();
_node_prefs = node_prefs; _node_prefs = node_prefs;
_display->turnOn(); _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 // strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3 // e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(firmware_version); char *version = strdup(firmware_version);
@@ -47,7 +55,7 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
void UITask::renderCurrScreen() { void UITask::renderCurrScreen() {
char tmp[80]; char tmp[80];
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen if (millis() < _started_at + BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo // meshcore logo
_display->setColor(DisplayDriver::BLUE); _display->setColor(DisplayDriver::BLUE);
int logoWidth = 128; int logoWidth = 128;
@@ -57,24 +65,34 @@ void UITask::renderCurrScreen() {
const char* website = "https://meshcore.io"; const char* website = "https://meshcore.io";
_display->setColor(DisplayDriver::LIGHT); _display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1); _display->setTextSize(1);
uint16_t websiteWidth = _display->getTextWidth(website); _display->drawTextCentered(_display->width() / 2, 22, website);
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
_display->print(website);
// version info // version info
_display->setColor(DisplayDriver::LIGHT); _display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1); _display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info); _display->drawTextCentered(_display->width() / 2, 35, _version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 35);
_display->print(_version_info);
// node type // node type
const char* node_type = "< Repeater >"; const char* node_type = "< Repeater >";
uint16_t typeWidth = _display->getTextWidth(node_type); _display->drawTextCentered(_display->width() / 2, 48, node_type);
_display->setCursor((_display->width() - typeWidth) / 2, 48); } else if (_powering_off_at > 0) {
_display->print(node_type); // meshcore logo
} else { // home screen _display->setColor(DisplayDriver::BLUE);
// node name 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->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->setCursor(0, 0);
_display->setTextSize(1); _display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN); _display->setColor(DisplayDriver::GREEN);
@@ -94,21 +112,19 @@ void UITask::renderCurrScreen() {
} }
void UITask::loop() { void UITask::loop() {
#ifdef PIN_USER_BTN #if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS)
if (millis() >= _next_read) { int ev = user_btn.check();
int btnState = digitalRead(PIN_USER_BTN); if (ev == BUTTON_EVENT_CLICK) {
if (btnState != _prevBtnState) {
if (btnState == USER_BTN_PRESSED) { // pressed?
if (_display->isOn()) { if (_display->isOn()) {
// TODO: any action ? // TODO: any action ?
} else { } else {
_display->turnOn(); _display->turnOn();
} }
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
} } else if (ev == BUTTON_EVENT_LONG_PRESS) {
_prevBtnState = btnState; _display->turnOn();
} Serial.println("Powering Off");
_next_read = millis() + 200; // 5 reads per second _powering_off_at = millis() + POWEROFF_DELAY;
} }
#endif #endif
@@ -124,4 +140,13 @@ void UITask::loop() {
_display->turnOff(); _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> #include <helpers/CommonCLI.h>
class UITask { class UITask {
mesh::MainBoard* _board;
DisplayDriver* _display; DisplayDriver* _display;
unsigned long _next_read, _next_refresh, _auto_off; unsigned long _next_read, _next_refresh, _auto_off;
int _prevBtnState; int _prevBtnState;
NodePrefs* _node_prefs; NodePrefs* _node_prefs;
char _version_info[32]; char _version_info[32];
unsigned long _powering_off_at = 0;
unsigned long _started_at = 0;
void renderCurrScreen(); void renderCurrScreen();
public: 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 begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop(); void loop();
+45 -2
View File
@@ -5,7 +5,12 @@
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
#include "UITask.h" #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 #endif
StdRNG fast_rng; StdRNG fast_rng;
@@ -18,6 +23,9 @@ void halt() {
} }
static char command[160]; static char command[160];
#ifdef ETHERNET_ENABLED
static char ethernet_command[160];
#endif
// For power saving // For power saving
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
@@ -33,6 +41,10 @@ void setup() {
board.begin(); board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) #if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
// give some extra time for serial to settle so // give some extra time for serial to settle so
// boot debug messages can be seen on terminal // 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(); mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
command[0] = 0; command[0] = 0;
#ifdef ETHERNET_ENABLED
ethernet_command[0] = 0;
#endif
sensors.begin(); sensors.begin();
@@ -95,6 +110,10 @@ void setup() {
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif #endif
#ifdef ETHERNET_ENABLED
ethernet_start_task();
#endif
// send out initial zero hop Advertisement to the mesh // send out initial zero hop Advertisement to the mesh
#if ENABLE_ADVERT_ON_BOOT == 1 #if ENABLE_ADVERT_ON_BOOT == 1
the_mesh.sendSelfAdvertisement(16000, false); the_mesh.sendSelfAdvertisement(16000, false);
@@ -104,6 +123,7 @@ void setup() {
} }
void loop() { void loop() {
// Handle Serial CLI
int len = strlen(command); int len = strlen(command);
while (Serial.available() && len < sizeof(command)-1) { while (Serial.available() && len < sizeof(command)-1) {
char c = Serial.read(); char c = Serial.read();
@@ -122,7 +142,14 @@ void loop() {
Serial.print('\n'); Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator command[len - 1] = 0; // replace newline with C string null terminator
char reply[160]; 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! the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
#endif
if (reply[0]) { if (reply[0]) {
Serial.print(" -> "); Serial.println(reply); Serial.print(" -> "); Serial.println(reply);
} }
@@ -130,7 +157,20 @@ void loop() {
command[0] = 0; // reset command buffer 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. // Hold the user button to power off the SenseCAP Solar repeater.
int btnState = digitalRead(PIN_USER_BTN); int btnState = digitalRead(PIN_USER_BTN);
if (btnState == LOW) { if (btnState == LOW) {
@@ -152,6 +192,9 @@ void loop() {
#endif #endif
rtc_clock.tick(); rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
+4 -4
View File
@@ -290,8 +290,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
return true; return true;
} }
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
// just try to determine region for packet (apply later in allowPacketForward())
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
@@ -303,8 +302,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
} else { } else {
recv_pkt_region = NULL; recv_pkt_region = NULL;
} }
// do normal processing return Mesh::onRecvPacket(pkt);
return false;
} }
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, 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; _logging = false;
region_load_active = false; region_load_active = false;
set_radio_at = revert_radio_at = 0; set_radio_at = revert_radio_at = 0;
recv_pkt_region = NULL;
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); 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_unscoped = 64;
_prefs.flood_max_advert = 8; _prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled _prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
#ifdef ROOM_PASSWORD #ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif #endif
+4 -1
View File
@@ -144,6 +144,9 @@ protected:
int getInterferenceThreshold() const override { int getInterferenceThreshold() const override {
return _prefs.interference_threshold; return _prefs.interference_threshold;
} }
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override { int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
} }
@@ -151,7 +154,7 @@ protected:
return _prefs.multi_acks; return _prefs.multi_acks;
} }
bool filterRecvFloodPacket(mesh::Packet* pkt) override; mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
bool allowPacketForward(const mesh::Packet* packet) 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; void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
+42
View File
@@ -3,6 +3,11 @@
#include "MyMesh.h" #include "MyMesh.h"
#ifdef ETHERNET_ENABLED
#define ETHERNET_CLI_BANNER "MeshCore Room Server CLI"
#include <helpers/nrf52/EthernetCLI.h>
#endif
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
#include "UITask.h" #include "UITask.h"
static UITask ui_task(display); static UITask ui_task(display);
@@ -17,6 +22,9 @@ void halt() {
} }
static char command[MAX_POST_TEXT_LEN+1]; static char command[MAX_POST_TEXT_LEN+1];
#ifdef ETHERNET_ENABLED
static char ethernet_command[MAX_POST_TEXT_LEN+1];
#endif
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
@@ -24,6 +32,10 @@ void setup() {
board.begin(); board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
if (display.begin()) { if (display.begin()) {
display.startFrame(); display.startFrame();
@@ -67,6 +79,9 @@ void setup() {
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println(); mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
command[0] = 0; command[0] = 0;
#ifdef ETHERNET_ENABLED
ethernet_command[0] = 0;
#endif
sensors.begin(); sensors.begin();
@@ -76,6 +91,10 @@ void setup() {
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif #endif
#ifdef ETHERNET_ENABLED
ethernet_start_task();
#endif
// send out initial zero hop Advertisement to the mesh // send out initial zero hop Advertisement to the mesh
#if ENABLE_ADVERT_ON_BOOT == 1 #if ENABLE_ADVERT_ON_BOOT == 1
the_mesh.sendSelfAdvertisement(16000, false); the_mesh.sendSelfAdvertisement(16000, false);
@@ -101,7 +120,14 @@ void loop() {
if (len > 0 && command[len - 1] == '\r') { // received complete line if (len > 0 && command[len - 1] == '\r') { // received complete line
command[len - 1] = 0; // replace newline with C string null terminator command[len - 1] = 0; // replace newline with C string null terminator
char reply[160]; 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! the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
#endif
if (reply[0]) { if (reply[0]) {
Serial.print(" -> "); Serial.println(reply); Serial.print(" -> "); Serial.println(reply);
} }
@@ -109,10 +135,26 @@ void loop() {
command[0] = 0; // reset command buffer 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(); the_mesh.loop();
sensors.loop(); sensors.loop();
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
ui_task.loop(); ui_task.loop();
#endif #endif
rtc_clock.tick(); rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
} }
+7
View File
@@ -560,6 +560,10 @@ void setup() {
board.begin(); board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
fast_rng.begin(radio_driver.getRngSeed()); fast_rng.begin(radio_driver.getRngSeed());
@@ -591,4 +595,7 @@ void setup() {
void loop() { void loop() {
the_mesh.loop(); the_mesh.loop();
rtc_clock.tick(); rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
} }
+4
View File
@@ -323,6 +323,9 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
int SensorMesh::getInterferenceThreshold() const { int SensorMesh::getInterferenceThreshold() const {
return _prefs.interference_threshold; return _prefs.interference_threshold;
} }
bool SensorMesh::getCADEnabled() const {
return _prefs.cad_enabled;
}
int SensorMesh::getAGCResetInterval() const { int SensorMesh::getAGCResetInterval() const {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
} }
@@ -726,6 +729,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.disable_fwd = true; _prefs.disable_fwd = true;
_prefs.flood_max = 64; _prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled _prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// GPS defaults // GPS defaults
_prefs.gps_enabled = 0; _prefs.gps_enabled = 0;
+1
View File
@@ -120,6 +120,7 @@ protected:
uint32_t getRetransmitDelay(const mesh::Packet* packet) override; uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
int getInterferenceThreshold() const override; int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() 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; 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; int searchPeersByHash(const uint8_t* hash) override;
+7
View File
@@ -58,6 +58,10 @@ void setup() {
board.begin(); board.begin();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.begin();
#endif
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
if (display.begin()) { if (display.begin()) {
display.startFrame(); display.startFrame();
@@ -147,4 +151,7 @@ void loop() {
ui_task.loop(); ui_task.loop();
#endif #endif
rtc_clock.tick(); rtc_clock.tick();
#ifdef HAS_EXTERNAL_WATCHDOG
external_watchdog.loop();
#endif
} }
+4 -1
View File
@@ -19,7 +19,8 @@ monitor_speed = 115200
lib_deps = lib_deps =
SPI SPI
Wire Wire
jgromes/RadioLib @ ^7.6.0 ;jgromes/RadioLib @ ^7.7.1
https://github.com/jgromes/RadioLib.git#6d8934836678d8894e3d556550475b37dce3e2b6
rweather/Crypto @ ^0.4.0 rweather/Crypto @ ^0.4.0
adafruit/RTClib @ ^2.1.3 adafruit/RTClib @ ^2.1.3
melopero/Melopero RV3028 @ ^1.1.0 melopero/Melopero RV3028 @ ^1.1.0
@@ -157,6 +158,7 @@ lib_deps =
[env:native] [env:native]
platform = native platform = native
test_framework = googletest
build_flags = -std=c++17 build_flags = -std=c++17
-I src -I src
-I test/mocks -I test/mocks
@@ -164,5 +166,6 @@ test_build_src = yes
build_src_filter = build_src_filter =
-<*> -<*>
+<../src/Utils.cpp> +<../src/Utils.cpp>
+<../src/Packet.cpp>
lib_deps = lib_deps =
google/googletest @ 1.17.0 google/googletest @ 1.17.0
+1
View File
@@ -66,6 +66,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
void Dispatcher::loop() { void Dispatcher::loop() {
if (millisHasNowPassed(next_floor_calib_time)) { if (millisHasNowPassed(next_floor_calib_time)) {
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold()); _radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
_radio->setCADEnabled(getCADEnabled());
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL); next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
} }
_radio->loop(); _radio->loop();
+3
View File
@@ -65,6 +65,8 @@ public:
virtual void triggerNoiseFloorCalibrate(int threshold) { } virtual void triggerNoiseFloorCalibrate(int threshold) { }
virtual void setCADEnabled(bool enable) { }
virtual void resetAGC() { } virtual void resetAGC() { }
virtual bool isInRecvMode() const = 0; virtual bool isInRecvMode() const = 0;
@@ -166,6 +168,7 @@ protected:
virtual uint32_t getCADFailRetryDelay() const; virtual uint32_t getCADFailRetryDelay() const;
virtual uint32_t getCADFailMaxDuration() const; virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default 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 int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; } virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
+27 -16
View File
@@ -55,7 +55,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint16_t offset = (uint16_t)pkt->path_len << path_sz; uint16_t offset = (uint16_t)pkt->path_len << path_sz;
if (offset >= len) { // TRACE has reached end of given path if (offset >= len) { // TRACE has reached end of given path
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); 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!) // append SNR (Not hash!)
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); 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) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
return forwardMultipartDirect(pkt); return forwardMultipartDirect(pkt);
} else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { } 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); removeSelfFromPath(pkt);
routeDirectRecvAcks(pkt, 0); routeDirectRecvAcks(pkt, 0);
} }
return ACTION_RELEASE; return ACTION_RELEASE;
} }
if (!_tables->hasSeen(pkt)) { if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt); removeSelfFromPath(pkt);
uint32_t d = getDirectRetransmitDelay(pkt); uint32_t d = getDirectRetransmitDelay(pkt);
@@ -117,7 +120,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
if (i > pkt->payload_len) { if (i > pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime()); 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); onAckRecv(pkt, ack_crc);
action = routeRecvPacket(pkt); action = routeRecvPacket(pkt);
} }
@@ -134,7 +138,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { if (i + CIPHER_MAC_SIZE >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); 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. // 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. // 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(?) // FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?)
@@ -197,7 +202,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) { if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); 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)) { if (self_id.isHashMatch(&dest_hash)) {
Identity sender(sender_pub_key); Identity sender(sender_pub_key);
@@ -224,7 +230,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) { if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); 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) // scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
GroupChannel channels[4]; GroupChannel channels[4];
int num = searchChannelsByHash(&channel_hash, channels, 4); int num = searchChannelsByHash(&channel_hash, channels, 4);
@@ -255,7 +262,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime()); MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime());
} else if (self_id.matches(id.pub_key)) { } else if (self_id.matches(id.pub_key)) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime()); 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]; uint8_t* app_data = &pkt->payload[i];
int app_data_len = pkt->payload_len - i; int app_data_len = pkt->payload_len - i;
if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; } if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; }
@@ -282,7 +290,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
break; break;
} }
case PAYLOAD_TYPE_RAW_CUSTOM: { case PAYLOAD_TYPE_RAW_CUSTOM: {
if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) { if (pkt->isRouteDirect() && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onRawDataRecv(pkt); onRawDataRecv(pkt);
//action = routeRecvPacket(pkt); don't flood route these (yet) //action = routeRecvPacket(pkt); don't flood route these (yet)
} }
@@ -300,7 +309,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1; tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) { if (!_tables->wasSeen(&tmp)) {
_tables->markSeen(&tmp);
uint32_t ack_crc; uint32_t ack_crc;
memcpy(&ack_crc, tmp.payload, 4); memcpy(&ack_crc, tmp.payload, 4);
@@ -357,7 +367,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1; tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); 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); removeSelfFromPath(&tmp);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2) routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
} }
@@ -637,7 +648,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
packet->header |= ROUTE_TYPE_FLOOD; packet->header |= ROUTE_TYPE_FLOOD;
packet->setPathHashSizeAndCount(path_hash_size, 0); 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; uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -666,7 +677,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m
packet->transport_codes[1] = transport_codes[1]; packet->transport_codes[1] = transport_codes[1];
packet->setPathHashSizeAndCount(path_hash_size, 0); 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; uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -699,7 +710,7 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
pri = 0; 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); sendPacket(packet, pri, delay_millis);
} }
@@ -709,7 +720,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
packet->path_len = 0; // path_len of zero means Zero Hop 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); sendPacket(packet, 0, delay_millis);
} }
@@ -722,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 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); sendPacket(packet, 0, delay_millis);
} }
+3 -2
View File
@@ -15,7 +15,8 @@ public:
*/ */
class MeshTables { class MeshTables {
public: public:
virtual bool hasSeen(const Packet* packet) = 0; 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 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 auth_code a code to authenticate the packet
* \param flags zero for now * \param flags zero for now
* \param path_snrs single byte SNR*4 for each hop in the path * \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 * \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) { } 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) { }
+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_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_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_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_MULTIPART 0x0A // packet is one of a set of packets
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet #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)) { if (i2c_probe(wire, PCF8563_ADDRESS)) {
MESH_DEBUG_PRINTLN("PCF8563: Found");
rtc_8563_success = rtc_8563.begin(&wire); rtc_8563_success = rtc_8563.begin(&wire);
} }
+18 -13
View File
@@ -92,7 +92,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291 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->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->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
// next: 294 file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
// sanitise bad pref values // sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -123,6 +124,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings // sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean _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->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close(); file.close();
} }
@@ -187,7 +189,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291 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->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->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
// next: 294 file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
file.close(); file.close();
} }
@@ -503,6 +506,10 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->interference_threshold = atoi(&config[11]); _prefs->interference_threshold = atoi(&config[11]);
savePrefs(); savePrefs();
strcpy(reply, "OK"); 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) { } else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
_prefs->agc_reset_interval = atoi(&config[19]) / 4; _prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs(); savePrefs();
@@ -564,13 +571,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
savePrefs(); savePrefs();
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); 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) { } else if (memcmp(config, "radio.rxgain ", 13) == 0) {
_prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0; bool enabled = memcmp(&config[13], "on", 2) == 0;
strcpy(reply, "OK"); _prefs->rx_boosted_gain = enabled;
savePrefs(); savePrefs();
_callbacks->setRxBoostedGain(_prefs->rx_boosted_gain); if (_callbacks->setRxBoostedGain(enabled)) {
#endif strcpy(reply, "OK");
} else {
strcpy(reply, "Error: unsupported");
}
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) { } else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
if (!_board->canControlLoRaFemLna()) { if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported"); strcpy(reply, "Error: unsupported");
@@ -801,6 +810,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor)); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
} else if (memcmp(config, "int.thresh", 10) == 0) { } else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold); 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) { } else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4); sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) { } else if (memcmp(config, "multi.acks", 10) == 0) {
@@ -826,10 +837,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat)); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
} else if (memcmp(config, "lon", 3) == 0) { } else if (memcmp(config, "lon", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon)); 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) { } else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off"); sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif
} else if (memcmp(config, "radio.fem.rxgain", 16) == 0) { } else if (memcmp(config, "radio.fem.rxgain", 16) == 0) {
if (!_board->canControlLoRaFemLna()) { if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported"); strcpy(reply, "Error: unsupported");
@@ -944,13 +953,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "ERROR: Power management not supported"); strcpy(reply, "ERROR: Power management not supported");
#endif #endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) { } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s", sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()), _board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason())); _board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) { } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT #ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage()); sprintf(reply, "> %u mV", _board->getBootVoltage());
+3 -2
View File
@@ -64,6 +64,7 @@ struct NodePrefs { // persisted to file
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
uint8_t path_hash_mode; // which path mode to use when sending uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect; uint8_t loop_detect;
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
}; };
class CommonCLICallbacks { class CommonCLICallbacks {
@@ -110,8 +111,8 @@ public:
// no op by default // no op by default
}; };
virtual void setRxBoostedGain(bool enable) { virtual bool setRxBoostedGain(bool enable) {
// no op by default return false; // CommonCLI reports unsupported if not overridden by wrapper
}; };
}; };
+42
View File
@@ -1,6 +1,7 @@
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
#include "ESP32Board.h" #include "ESP32Board.h"
#include <target.h>
#if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only #if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
#include <WiFi.h> #include <WiFi.h>
@@ -44,4 +45,45 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
} }
#endif #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 #endif
+40
View File
@@ -14,6 +14,7 @@
#include <Wire.h> #include <Wire.h>
#include "soc/rtc.h" #include "soc/rtc.h"
#include "esp_system.h" #include "esp_system.h"
#include <driver/rtc_io.h>
class ESP32Board : public mesh::MainBoard { class ESP32Board : public mesh::MainBoard {
protected: protected:
@@ -62,6 +63,9 @@ public:
return raw / 4; return raw / 4;
} }
virtual void powerOff() override;
void enterDeepSleep(uint32_t secs);
uint32_t getIRQGpio() override { uint32_t getIRQGpio() override {
return P_LORA_DIO_1; // default for SX1262 return P_LORA_DIO_1; // default for SX1262
} }
@@ -155,6 +159,42 @@ public:
void setInhibitSleep(bool inhibit) { void setInhibitSleep(bool inhibit) {
inhibit_sleep = 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 { 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 "ESP32Board.h"
#include <driver/rtc_io.h>
class MeshadventurerBoard : public ESP32Board { class MeshadventurerBoard : public ESP32Board {
public: 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 { uint16_t getBattMilliVolts() override {
analogReadResolution(12); analogReadResolution(12);
+73
View File
@@ -1,5 +1,6 @@
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
#include "NRF52Board.h" #include "NRF52Board.h"
#include <target.h>
#include <bluefruit.h> #include <bluefruit.h>
#include <nrf_soc.h> #include <nrf_soc.h>
@@ -279,6 +280,18 @@ void NRF52Board::sleep(uint32_t secs) {
// Temperature from NRF52 MCU // Temperature from NRF52 MCU
float NRF52Board::getMCUTemperature() { float NRF52Board::getMCUTemperature() {
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 NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis(); long startTime = millis();
@@ -288,6 +301,7 @@ float NRF52Board::getMCUTemperature() {
return NAN; return NAN;
} }
} }
}
NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag
@@ -297,6 +311,65 @@ float NRF52Board::getMCUTemperature() {
return temp * 0.25f; // Convert to *C 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) { bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; 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 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 uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override; virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); } 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 getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) 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) { while (num_regions < MAX_REGION_ENTRIES) {
auto r = &regions[num_regions]; 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->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->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((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
success = success && file.read(pad, sizeof(pad)) == sizeof(pad); 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 if (r->id >= next_id) { // make sure next_id is valid
next_id = r->id + 1; next_id = r->id + 1;
@@ -108,7 +110,7 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
} }
} }
file.close(); file.close();
return true; return success;
} }
} }
return false; // failed return false; // failed
@@ -139,7 +141,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
} }
} }
file.close(); file.close();
return true; return success;
} }
return false; // failed return false; // failed
} }
+9 -5
View File
@@ -31,7 +31,7 @@ public:
} }
#endif #endif
bool hasSeen(const mesh::Packet* packet) override { bool wasSeen(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE]; uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash); packet->calculatePacketHash(hash);
@@ -39,19 +39,23 @@ public:
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) { 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()) { if (packet->isRouteDirect()) {
_direct_dups++; // keep some stats _direct_dups++;
} else { } else {
_flood_dups++; _flood_dups++;
} }
return true; 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; 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 { void clear(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE]; uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash); packet->calculatePacketHash(hash);
+2 -1
View File
@@ -39,7 +39,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
return; 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. // bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay); _mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
} else { } else {
+1 -1
View File
@@ -110,7 +110,7 @@ protected:
* @brief Common packet handling for received packets * @brief Common packet handling for received packets
* *
* Implements the standard pattern used by all bridges: * 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 * - Queue packet for mesh processing if not seen before
* - Free packet if already seen to prevent duplicates * - Free packet if already seen to prevent duplicates
* *
+3 -2
View File
@@ -32,7 +32,7 @@ void ESPNowBridge::begin() {
// Initialize WiFi in station mode // Initialize WiFi in station mode
WiFi.mode(WIFI_STA); 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) { 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); BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel);
return; return;
@@ -167,7 +167,8 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) {
return; 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 // Create a temporary buffer just for size calculation and reuse for actual writing
uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
uint16_t meshPacketLen = packet->writeTo(sizingBuffer); uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
+2 -1
View File
@@ -115,7 +115,8 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) {
return; return;
} }
if (!_seen_packets.hasSeen(packet)) { if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
uint16_t len = packet->writeTo(buffer + 4); 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 * These two functions do nothing for ESP-NOW, but are needed for the
* Radio interface. * Radio interface.
*/ */
virtual void setRxBoostedGainMode(bool) { } virtual bool setRxBoostedGainMode(bool) { }
virtual bool getRxBoostedGainMode() const { return false; } virtual bool getRxBoostedGainMode() const { return false; }
uint32_t intID(); uint32_t intID();
+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); setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif #endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN) #if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN #ifndef SX126X_RXEN
#define SX1262X_RXEN RADIOLIB_NC #define SX126X_RXEN RADIOLIB_NC
#endif #endif
#ifndef SX1262X_TXEN #ifndef SX126X_TXEN
#define SX1262X_TXEN RADIOLIB_NC #define SX126X_TXEN RADIOLIB_NC
#endif #endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif #endif
+2 -2
View File
@@ -33,8 +33,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override { bool setRxBoostedGainMode(bool en) override {
((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en); return ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
} }
bool getRxBoostedGainMode() const override { bool getRxBoostedGainMode() const override {
return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode(); return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode();
+47 -3
View File
@@ -4,6 +4,10 @@
#include "MeshCore.h" #include "MeshCore.h"
class CustomLR1110 : public LR1110 { class CustomLR1110 : public LR1110 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false; bool _rx_boosted = false;
public: public:
@@ -32,9 +36,49 @@ class CustomLR1110 : public LR1110 {
bool getRxBoostedGainMode() const { return _rx_boosted; } bool getRxBoostedGainMode() const { return _rx_boosted; }
bool isReceiving() { bool isReceiving() {
uint16_t irq = getIrqStatus(); uint32_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); bool preamble = irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; // bit 4
return detected; 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; } uint8_t getSpreadingFactor() const { return spreadingFactor; }
+10 -2
View File
@@ -14,6 +14,9 @@ public:
((CustomLR1110 *)_radio)->setBandwidth(bw); ((CustomLR1110 *)_radio)->setBandwidth(bw);
((CustomLR1110 *)_radio)->setCodingRate(cr); ((CustomLR1110 *)_radio)->setCodingRate(cr);
updatePreamble(sf); 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()); } void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
@@ -26,6 +29,11 @@ public:
return rssi; 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 { void onSendFinished() override {
RadioLibWrapper::onSendFinished(); RadioLibWrapper::onSendFinished();
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts _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(); } uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
void setRxBoostedGainMode(bool en) override { bool setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
} }
bool getRxBoostedGainMode() const override { bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
+62 -8
View File
@@ -1,11 +1,14 @@
#pragma once #pragma once
#include <RadioLib.h> #include <RadioLib.h>
#include "MeshCore.h"
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
class CustomSX1262 : public SX1262 { class CustomSX1262 : public SX1262 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public: public:
CustomSX1262(Module *mod) : SX1262(mod) { } CustomSX1262(Module *mod) : SX1262(mod) { }
@@ -27,6 +30,14 @@ class CustomSX1262 : public SX1262 {
uint8_t cr = 5; uint8_t cr = 5;
#endif #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) #if defined(P_LORA_SCLK)
#ifdef NRF52_PLATFORM #ifdef NRF52_PLATFORM
if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); } 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); if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif #endif
#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 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) { 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; 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) { if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: "); Serial.print("ERROR: radio init failed: ");
@@ -83,13 +95,55 @@ class CustomSX1262 : public SX1262 {
writeRegister(0x8B5, &r_data, 1); writeRegister(0x8B5, &r_data, 1);
#endif #endif
MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors());
return true; // success return true; // success
} }
bool isReceiving() { bool isReceiving() {
uint16_t irq = getIrqFlags(); uint32_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
return detected; 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() { bool getRxBoostedGainMode() {
+5 -2
View File
@@ -18,6 +18,9 @@ public:
((CustomSX1262 *)_radio)->setBandwidth(bw); ((CustomSX1262 *)_radio)->setBandwidth(bw);
((CustomSX1262 *)_radio)->setCodingRate(cr); ((CustomSX1262 *)_radio)->setCodingRate(cr);
updatePreamble(sf); updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomSX1262 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomSX1262 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
} }
bool isReceivingPacket() override { bool isReceivingPacket() override {
@@ -40,8 +43,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override { bool setRxBoostedGainMode(bool en) override {
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en); return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
} }
bool getRxBoostedGainMode() const override { bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode(); return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
+4 -4
View File
@@ -66,11 +66,11 @@ class CustomSX1268 : public SX1268 {
setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif #endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN) #if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN #ifndef SX126X_RXEN
#define SX1262X_RXEN RADIOLIB_NC #define SX126X_RXEN RADIOLIB_NC
#endif #endif
#ifndef SX1262X_TXEN #ifndef SX126X_TXEN
#define SX1262X_TXEN RADIOLIB_NC #define SX126X_TXEN RADIOLIB_NC
#endif #endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif #endif
+2 -2
View File
@@ -37,8 +37,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override { bool setRxBoostedGainMode(bool en) override {
((CustomSX1268 *)_radio)->setRxBoostedGainMode(en); return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
} }
bool getRxBoostedGainMode() const override { bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode(); return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
+6 -6
View File
@@ -50,14 +50,14 @@ class CustomSX1276 : public SX1276 {
setCurrentLimit(SX127X_CURRENT_LIMIT); setCurrentLimit(SX127X_CURRENT_LIMIT);
#endif #endif
#if defined(SX176X_RXEN) || defined(SX176X_TXEN) #if defined(SX127X_RXEN) || defined(SX127X_TXEN)
#ifndef SX176X_RXEN #ifndef SX127X_RXEN
#define SX176X_RXEN RADIOLIB_NC #define SX127X_RXEN RADIOLIB_NC
#endif #endif
#ifndef SX176X_TXEN #ifndef SX127X_TXEN
#define SX176X_TXEN RADIOLIB_NC #define SX127X_TXEN RADIOLIB_NC
#endif #endif
setRfSwitchPins(SX176X_RXEN, SX176X_TXEN); setRfSwitchPins(SX127X_RXEN, SX127X_TXEN);
#endif #endif
setCRC(1); setCRC(1);
+38 -3
View File
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0; _noise_floor = 0;
_threshold = 0; _threshold = 0;
_cad_enabled = false;
// start average out some samples // start average out some samples
_num_floor_samples = 0; _num_floor_samples = 0;
@@ -178,10 +179,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE; state = STATE_IDLE;
} }
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() { bool RadioLibWrapper::isChannelActive() {
return _threshold == 0 // int.thresh: RSSI-based interference detection (relative to noise floor)
? false // interference check is disabled if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
: getCurrentRSSI() > _noise_floor + _threshold;
// 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 { 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)); 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};
}
+10 -1
View File
@@ -3,12 +3,18 @@
#include <Mesh.h> #include <Mesh.h>
#include <RadioLib.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 { class RadioLibWrapper : public mesh::Radio {
protected: protected:
PhysicalLayer* _radio; PhysicalLayer* _radio;
mesh::MainBoard* _board; mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors; uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold; int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples; uint16_t _num_floor_samples;
int32_t _floor_sample_sum; int32_t _floor_sample_sum;
uint8_t _preamble_sf; uint8_t _preamble_sf;
@@ -46,9 +52,12 @@ public:
virtual uint8_t getSpreadingFactor() const { return LORA_SF; } virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; } static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); } 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; } int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override; void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override; void resetAGC() override;
void loop() 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 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; } virtual bool getRxBoostedGainMode() const { return false; }
}; };
@@ -807,6 +807,13 @@ void EnvironmentSensorManager::rakGPSInit(){
bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ 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 //set initial waking state
pinMode(ioPin,OUTPUT); pinMode(ioPin,OUTPUT);
digitalWrite(ioPin,LOW); digitalWrite(ioPin,LOW);
@@ -889,11 +896,11 @@ void EnvironmentSensorManager::stop_gps() {
void EnvironmentSensorManager::loop() { void EnvironmentSensorManager::loop() {
#if ENV_INCLUDE_GPS #if ENV_INCLUDE_GPS
static long next_gps_update = 0; static unsigned long next_gps_update = 0;
if (gps_active) { if (gps_active) {
_location->loop(); _location->loop();
} }
if (millis() > next_gps_update) { if ((long)(millis() - next_gps_update) > 0) {
if(gps_active){ if(gps_active){
#ifdef RAK_WISBLOCK_GPS #ifdef RAK_WISBLOCK_GPS
+1 -1
View File
@@ -142,7 +142,7 @@ public:
case LPP_GPS: case LPP_GPS:
_pos += 9; break; _pos += 9; break;
case LPP_POLYLINE: case LPP_POLYLINE:
_pos += 8; break; // TODO: this is MINIMIUM _pos += 8; break; // TODO: this is MINIMUM
case LPP_GYROMETER: case LPP_GYROMETER:
case LPP_ACCELEROMETER: case LPP_ACCELEROMETER:
_pos += 6; break; _pos += 6; break;
@@ -42,14 +42,14 @@ class MicroNMEALocationProvider : public LocationProvider {
int8_t _claims = 0; int8_t _claims = 0;
int _pin_reset; int _pin_reset;
int _pin_en; int _pin_en;
long next_check = 0; unsigned long next_check = 0;
long time_valid = 0; long time_valid = 0;
unsigned long _last_time_sync = 0; unsigned long _last_time_sync = 0;
static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes
public : public :
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : 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) { if (_pin_reset != -1) {
pinMode(_pin_reset, OUTPUT); pinMode(_pin_reset, OUTPUT);
digitalWrite(_pin_reset, GPS_RESET_FORCE); digitalWrite(_pin_reset, GPS_RESET_FORCE);
@@ -62,10 +62,8 @@ public :
void claim() { void claim() {
_claims++; _claims++;
if (_claims > 0) {
if (_peripher_power) _peripher_power->claim(); if (_peripher_power) _peripher_power->claim();
} }
}
void release() { void release() {
if (_claims == 0) return; // avoid negative _claims if (_claims == 0) return; // avoid negative _claims
@@ -143,7 +141,7 @@ public :
if (!isValid()) time_valid = 0; if (!isValid()) time_valid = 0;
if (millis() > next_check) { if ((long)(millis() - next_check) > 0) {
next_check = millis() + 1000; next_check = millis() + 1000;
// Re-enable time sync periodically when GPS has valid fix // Re-enable time sync periodically when GPS has valid fix
if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) { 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 // 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. // 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) 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. // Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes // 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. // 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) 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 // 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) static int _internal_flash_sync(const struct lfs_config *c)
{ {
return LFS_ERR_OK; // don't need sync return LFS_ERR_OK; // don't need sync
+553
View File
@@ -0,0 +1,553 @@
#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
static uint16_t mapColor(DisplayDriver::Color c) {
switch (c) {
case DisplayDriver::DARK: return 0x0000;
case DisplayDriver::LIGHT: return 0xffff;
case DisplayDriver::RED: return 0xf800;
case DisplayDriver::GREEN: return 0x07e0;
case DisplayDriver::BLUE: return 0x001f;
case DisplayDriver::YELLOW: return 0xffe0;
case DisplayDriver::ORANGE: return 0xfd20;
default: return 0xffff;
}
}
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 = 0x0000;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = saved;
}
void NV3001BDisplay::startFrame(Color bkg) {
color = mapColor(bkg);
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = 0xffff;
text_size = 1;
cursor_x = 0;
cursor_y = 0;
}
void NV3001BDisplay::setTextSize(int sz) {
text_size = sz < 1 ? 1 : sz;
}
void NV3001BDisplay::setColor(Color c) {
color = mapColor(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() {
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include "DisplayDriver.h"
#include <SPI.h>
#include <helpers/RefCountedDigitalPin.h>
#ifndef NV3001B_LOGICAL_WIDTH
#define NV3001B_LOGICAL_WIDTH 128
#endif
#ifndef NV3001B_LOGICAL_HEIGHT
#define NV3001B_LOGICAL_HEIGHT 64
#endif
#ifndef NV3001B_PANEL_WIDTH
#define NV3001B_PANEL_WIDTH 128
#endif
#ifndef NV3001B_PANEL_HEIGHT
#define NV3001B_PANEL_HEIGHT 220
#endif
#ifndef NV3001B_SPI_HOST
#define NV3001B_SPI_HOST HSPI
#endif
class NV3001BDisplay : public DisplayDriver {
SPIClass spi;
RefCountedDigitalPin* periph_power;
bool is_on = false;
uint16_t color = 0xffff;
uint8_t text_size = 1;
int cursor_x = 0;
int cursor_y = 0;
void writeCommand(uint8_t cmd);
void writeBytes(const uint8_t* data, size_t len);
void writeCommandData(uint8_t cmd, const uint8_t* data, size_t len);
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void writeColor(uint16_t rgb, uint32_t count);
void fillPhysicalRect(int x, int y, int w, int h);
void initPanel();
void drawChar(int x, int y, char ch);
public:
NV3001BDisplay(RefCountedDigitalPin* power = nullptr) :
DisplayDriver(NV3001B_LOGICAL_WIDTH, NV3001B_LOGICAL_HEIGHT), spi(NV3001B_SPI_HOST), periph_power(power) { }
bool begin();
static const char* driverName() { return "NV3001B"; }
static uint16_t physicalWidth() { return NV3001B_PANEL_WIDTH; }
static uint16_t physicalHeight() { return NV3001B_PANEL_HEIGHT; }
bool isOn() override { return is_on; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void setTextSize(int sz) override;
void setColor(Color 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;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};
+1 -1
View File
@@ -1166,7 +1166,7 @@ char DefaultFontTableLookup(const uint8_t ch) {
uint8_t last = LASTCHAR; // get last char uint8_t last = LASTCHAR; // get last char
LASTCHAR = ch; LASTCHAR = ch;
switch (last) { // conversion depnding on first UTF8-character switch (last) { // conversion depending on first UTF8-character
case 0xC2: return (uint8_t) ch; case 0xC2: return (uint8_t) ch;
case 0xC3: return (uint8_t) (ch | 0xC0); case 0xC3: return (uint8_t) (ch | 0xC0);
case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <Arduino.h>
enum class RotaryInputEvent : uint8_t {
None,
Next,
Prev,
};
class RotaryInput {
public:
virtual ~RotaryInput() = default;
virtual bool begin() = 0;
virtual RotaryInputEvent poll() = 0;
virtual bool isReady() const = 0;
};
+521 -55
View File
@@ -1,12 +1,409 @@
#include "ST7735Display.h" #include "ST7735Display.h"
#ifndef DISPLAY_ROTATION //#include <Fonts/GFXFF/FreeSans9pt7b.h>
#define DISPLAY_ROTATION 2
// Optimised ST7735 display driver, derived from Adafruit_ST7735 library.
#define ST_CMD_DELAY 0x80 // special signifier for command lists
#define ST77XX_NOP 0x00
#define ST77XX_SWRESET 0x01
#define ST77XX_RDDID 0x04
#define ST77XX_RDDST 0x09
#define ST77XX_SLPIN 0x10
#define ST77XX_SLPOUT 0x11
#define ST77XX_PTLON 0x12
#define ST77XX_NORON 0x13
#define ST77XX_INVOFF 0x20
#define ST77XX_INVON 0x21
#define ST77XX_DISPOFF 0x28
#define ST77XX_DISPON 0x29
#define ST77XX_CASET 0x2A
#define ST77XX_RASET 0x2B
#define ST77XX_RAMWR 0x2C
#define ST77XX_RAMRD 0x2E
#define ST77XX_PTLAR 0x30
#define ST77XX_TEOFF 0x34
#define ST77XX_TEON 0x35
#define ST77XX_MADCTL 0x36
#define ST77XX_COLMOD 0x3A
#define ST77XX_MADCTL_MY 0x80
#define ST77XX_MADCTL_MX 0x40
#define ST77XX_MADCTL_MV 0x20
#define ST77XX_MADCTL_ML 0x10
#define ST77XX_MADCTL_RGB 0x00
#define ST77XX_RDID1 0xDA
#define ST77XX_RDID2 0xDB
#define ST77XX_RDID3 0xDC
#define ST77XX_RDID4 0xDD
// Some ready-made 16-bit ('565') color settings:
#define ST77XX_BLACK 0x0000
#define ST77XX_WHITE 0xFFFF
#define ST77XX_RED 0xF800
#define ST77XX_GREEN 0x07E0
#define ST77XX_BLUE 0x001F
#define ST77XX_CYAN 0x07FF
#define ST77XX_MAGENTA 0xF81F
#define ST77XX_YELLOW 0xFFE0
#define ST77XX_ORANGE 0xFC00
// some flags for initR() :(
#define INITR_GREENTAB 0x00
#define INITR_REDTAB 0x01
#define INITR_BLACKTAB 0x02
#define INITR_18GREENTAB INITR_GREENTAB
#define INITR_18REDTAB INITR_REDTAB
#define INITR_18BLACKTAB INITR_BLACKTAB
#define INITR_144GREENTAB 0x01
#define INITR_MINI160x80 0x04
#define INITR_HALLOWING 0x05
#define INITR_MINI160x80_PLUGIN 0x06
// Some register settings
#define ST7735_MADCTL_BGR 0x08
#define ST7735_MADCTL_MH 0x04
#define ST7735_FRMCTR1 0xB1
#define ST7735_FRMCTR2 0xB2
#define ST7735_FRMCTR3 0xB3
#define ST7735_INVCTR 0xB4
#define ST7735_DISSET5 0xB6
#define ST7735_PWCTR1 0xC0
#define ST7735_PWCTR2 0xC1
#define ST7735_PWCTR3 0xC2
#define ST7735_PWCTR4 0xC3
#define ST7735_PWCTR5 0xC4
#define ST7735_VMCTR1 0xC5
#define ST7735_PWCTR6 0xFC
#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1
// Some ready-made 16-bit ('565') color settings:
#define ST7735_BLACK ST77XX_BLACK
#define ST7735_WHITE ST77XX_WHITE
#define ST7735_RED ST77XX_RED
#define ST7735_GREEN ST77XX_GREEN
#define ST7735_BLUE ST77XX_BLUE
#define ST7735_CYAN ST77XX_CYAN
#define ST7735_MAGENTA ST77XX_MAGENTA
#define ST7735_YELLOW ST77XX_YELLOW
#define ST7735_ORANGE ST77XX_ORANGE
static TFT_eSPI lcd = TFT_eSPI(160, 80);
static uint32_t curr_color;
#if defined(HELTEC_LORA_V3) || defined(HELTEC_TRACKER_V2)
static SPIClass tft_spi(HSPI);
#define _spi (&tft_spi)
#else
#define _spi (&SPI1)
#endif #endif
SPISettings _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
// clang-format off
static const uint8_t PROGMEM
Bcmd[] = { // Init commands for 7735B screens
18, // 18 commands in list:
ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay
50, // 50 ms delay
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay
255, // 255 = max (500 ms) delay
ST77XX_COLMOD, 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay:
0x05, // 16-bit color
10, // 10 ms delay
ST7735_FRMCTR1, 3+ST_CMD_DELAY, // 4: Frame rate control, 3 args + delay:
0x00, // fastest refresh
0x06, // 6 lines front porch
0x03, // 3 lines back porch
10, // 10 ms delay
ST77XX_MADCTL, 1, // 5: Mem access ctl (directions), 1 arg:
0x08, // Row/col addr, bottom-top refresh
ST7735_DISSET5, 2, // 6: Display settings #5, 2 args:
0x15, // 1 clk cycle nonoverlap, 2 cycle gate
// rise, 3 cycle osc equalize
0x02, // Fix on VTL
ST7735_INVCTR, 1, // 7: Display inversion control, 1 arg:
0x0, // Line inversion
ST7735_PWCTR1, 2+ST_CMD_DELAY, // 8: Power control, 2 args + delay:
0x02, // GVDD = 4.7V
0x70, // 1.0uA
10, // 10 ms delay
ST7735_PWCTR2, 1, // 9: Power control, 1 arg, no delay:
0x05, // VGH = 14.7V, VGL = -7.35V
ST7735_PWCTR3, 2, // 10: Power control, 2 args, no delay:
0x01, // Opamp current small
0x02, // Boost frequency
ST7735_VMCTR1, 2+ST_CMD_DELAY, // 11: Power control, 2 args + delay:
0x3C, // VCOMH = 4V
0x38, // VCOML = -1.1V
10, // 10 ms delay
ST7735_PWCTR6, 2, // 12: Power control, 2 args, no delay:
0x11, 0x15,
ST7735_GMCTRP1,16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
0x09, 0x16, 0x09, 0x20, // (Not entirely necessary, but provides
0x21, 0x1B, 0x13, 0x19, // accurate colors)
0x17, 0x15, 0x1E, 0x2B,
0x04, 0x05, 0x02, 0x0E,
ST7735_GMCTRN1,16+ST_CMD_DELAY, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
0x0B, 0x14, 0x08, 0x1E, // (Not entirely necessary, but provides
0x22, 0x1D, 0x18, 0x1E, // accurate colors)
0x1B, 0x1A, 0x24, 0x2B,
0x06, 0x06, 0x02, 0x0F,
10, // 10 ms delay
ST77XX_CASET, 4, // 15: Column addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 2
0x00, 0x81, // XEND = 129
ST77XX_RASET, 4, // 16: Row addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 1
0x00, 0x81, // XEND = 160
ST77XX_NORON, ST_CMD_DELAY, // 17: Normal display on, no args, w/delay
10, // 10 ms delay
ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay
255 }, // 255 = max (500 ms) delay
Rcmd1[] = { // 7735R init, part 1 (red or green tab)
14, // 14 commands in list:
/*ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay
150, */ // 150 ms delay
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay
120, // 120 ms delay
ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ST7735_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
0x01, 0x2C, 0x2D, // Dot inversion mode
0x01, 0x2C, 0x2D, // Line inversion mode
ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg:
0x07, // No inversion
ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay:
0xA2,
0x02, // -4.6V
0x84, // AUTO mode
ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay:
0x0A, // Opamp current small
0x00, // Boost frequency
ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay:
0x8A, // BCLK/2,
0x2A, // opamp current small & medium low
ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay:
0x8A, 0xEE,
ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay:
0x0E,
ST77XX_INVOFF, 0, // 13: Don't invert display, no args
ST77XX_MADCTL, 1, // 14: Mem access ctl (directions), 1 arg:
0xC8, // row/col addr, bottom-top refresh
ST77XX_COLMOD, 1, // 15: set color mode, 1 arg, no delay:
0x05 }, // 16-bit color
Rcmd2green[] = { // 7735R init, part 2 (green tab only)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x02, // XSTART = 0
0x00, 0x7F+0x02, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x01, // XSTART = 0
0x00, 0x9F+0x01 }, // XEND = 159
Rcmd2red[] = { // 7735R init, part 2 (red tab only)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x9F }, // XEND = 159
Rcmd2green144[] = { // 7735R init, part 2 (green 1.44 tab)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F, // XEND = 127
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x7F }, // XEND = 127
Rcmd2green160x80[] = { // 7735R init, part 2 (mini 160x80)
2, // 2 commands in list:
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x4F, // XEND = 79
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x9F }, // XEND = 159
Rcmd2green160x80plugin[] = { // 7735R init, part 2 (mini 160x80 with plugin FPC)
3, // 3 commands in list:
ST77XX_INVON, 0, // 1: Display is inverted
ST77XX_CASET, 4, // 2: Column addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x4F, // XEND = 79
ST77XX_RASET, 4, // 3: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
0x00, 0x9F }, // XEND = 159
Rcmd2invert[] = { // Tracker V1, part 2
1, // 1 command in list:
ST77XX_INVON, 0 }, // 1: Display is inverted
Rcmd3[] = { // 7735R init, part 3 (red or green tab)
2, // 2 commands in list:
ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay:
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
0x37, 0x32, 0x29, 0x2d, // accurate colors)
0x29, 0x25, 0x2B, 0x39,
0x00, 0x01, 0x03, 0x10,
ST7735_GMCTRN1, 16 , // 2: Gamma Adjustments (neg. polarity), 16 args + delay:
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
0x2E, 0x2E, 0x37, 0x3F,
0x00, 0x00, 0x02, 0x10 }; // 100 ms delay
static int16_t _xstart = 0; ///< Internal framebuffer X offset
static int16_t _ystart = 0; ///< Internal framebuffer Y offset
static uint8_t _colstart = 0; ///< Some displays need this changed to offset
static uint8_t _rowstart = 0; ///< Some displays need this changed to offset
static uint8_t rotation = 0;
static int16_t _width = 0; ///< Display width as modified by current rotation
static int16_t _height = 0; ///< Display height as modified by current rotation
static void set_CS(uint8_t level) {
//if (_cs != (uint8_t) -1) {
digitalWrite(PIN_TFT_CS, level);
//}
}
static void sendCommand(uint8_t com) {
set_CS(HIGH);
digitalWrite(PIN_TFT_DC, LOW);
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(com);
_spi->endTransaction();
set_CS(HIGH);
digitalWrite(PIN_TFT_DC, HIGH);
}
static void WriteData(uint8_t data) {
digitalWrite(PIN_TFT_CS, LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(data);
_spi->endTransaction();
digitalWrite(PIN_TFT_CS, HIGH);
}
static void SPI_WRITE32(uint32_t l) {
_spi->transfer(l >> 24);
_spi->transfer(l >> 16);
_spi->transfer(l >> 8);
_spi->transfer(l);
}
static void writeCommand(uint8_t cmd) {
digitalWrite(PIN_TFT_DC, LOW);
_spi->transfer(cmd);
digitalWrite(PIN_TFT_DC, HIGH);
}
static void displayInit(const uint8_t *addr) {
uint8_t numCommands, cmd, numArgs;
uint16_t ms;
numCommands = pgm_read_byte(addr++); // Number of commands to follow
while (numCommands--) { // For each command...
cmd = pgm_read_byte(addr++); // Read command
numArgs = pgm_read_byte(addr++); // Number of args to follow
ms = numArgs & ST_CMD_DELAY; // If hibit set, delay follows args
numArgs &= ~ST_CMD_DELAY; // Mask out delay bit
sendCommand(cmd);
for (int k = 0; k < numArgs; k++) {
WriteData(addr[k]);
}
addr += numArgs;
if (ms) {
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
if (ms == 255)
ms = 500; // If 255, delay for 500 ms
delay(ms);
}
}
}
static void setRotation(uint8_t m) {
uint8_t madctl = 0;
rotation = m & 3; // can't be higher than 3
switch (rotation) {
case 0:
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST7735_MADCTL_BGR;
_height = 160;
_width = 80;
_xstart = _colstart;
_ystart = _rowstart;
break;
case 1:
madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
_width = 160;
_height = 80;
_ystart = _colstart;
_xstart = _rowstart;
break;
case 2:
madctl = ST7735_MADCTL_BGR;
_height = 160;
_width = 80;
_xstart = _colstart;
_ystart = _rowstart;
break;
case 3:
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
_width = 160;
_height = 80;
_ystart = _colstart;
_xstart = _rowstart;
break;
}
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
}
static void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
x += _xstart;
y += _ystart;
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
writeCommand(ST77XX_CASET); // Column addr set
SPI_WRITE32(xa);
writeCommand(ST77XX_RASET); // Row addr set
SPI_WRITE32(ya);
writeCommand(ST77XX_RAMWR); // write to RAM
}
#define SCALE_X 1.25f // 160 / 128 #define SCALE_X 1.25f // 160 / 128
#define SCALE_Y 1.25f // 80 / 64 #define SCALE_Y 1.25f // 80 / 64
static TFT_eSprite *sprite = NULL;
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
return true; return true;
/* /*
@@ -16,53 +413,110 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
*/ */
} }
#ifndef PIN_TFT_LEDA_CTL_ACTIVE
#define PIN_TFT_LEDA_CTL_ACTIVE HIGH
#endif
bool ST7735Display::begin() { bool ST7735Display::begin() {
if (!sprite) {
// alloc offscreen canvas
sprite = new TFT_eSprite(&lcd);
if (sprite) {
if (sprite->createSprite(160, 80)) {
sprite->fillScreen(ST77XX_BLACK);
sprite->setTextColor(curr_color = ST77XX_WHITE);
} else {
Serial.printf("ST7735Display: failed to alloc canvas pixels");
}
} else {
Serial.printf("ST7735Display: failed to alloc canvas");
}
}
if (!_isOn) { if (!_isOn) {
if (_peripher_power) _peripher_power->claim(); if (_peripher_power) _peripher_power->claim();
delay(100); // TEMP!!
pinMode(PIN_TFT_RST, OUTPUT);
pinMode(PIN_TFT_CS, OUTPUT);
pinMode(PIN_TFT_DC, OUTPUT);
pinMode(PIN_TFT_LEDA_CTL, OUTPUT); pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
#else
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
#endif
digitalWrite(PIN_TFT_RST, HIGH);
#if defined(HELTEC_T1) #ifdef ESP_PLATFORM
display.initR(INITR_MINI160x80); _spi->begin(PIN_TFT_SCL, -1 /* _miso */, PIN_TFT_SDA /* _mosi */, -1);
display.setRotation(DISPLAY_ROTATION);
#elif defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
display.initR(INITR_MINI160x80);
display.setRotation(DISPLAY_ROTATION);
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
display.sendCommand(ST77XX_MADCTL, &madctl, 1);
#else #else
display.initR(INITR_MINI160x80_PLUGIN); _spi->begin();
display.setRotation(DISPLAY_ROTATION);
#endif #endif
display.setSPISpeed(40000000); _spi->setClockDivider(SPI_CLOCK_DIV2);
display.fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE); _height = 80;
display.setTextSize(2); _width = 160;
display.cp437(true); // Use full 256 char 'Code Page 437' font #if defined(HELTEC_LORA_V3) // Tracker v1
_colstart = 26;
_rowstart = 1;
#else
_colstart = 24;
_rowstart = 0;
#endif
_resetAndInit();
sendCommand(ST77XX_DISPON);
_isOn = true; _isOn = true;
} }
return true; return true;
} }
void ST7735Display::_resetAndInit() {
// Pulse Reset low for 10ms
digitalWrite(PIN_TFT_RST, HIGH);
delay(2);
digitalWrite(PIN_TFT_RST, LOW);
delay(10);
digitalWrite(PIN_TFT_RST, HIGH);
delay(2);
// run init commands
displayInit(Rcmd1);
#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
displayInit(Rcmd2green160x80);
//uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
//display.sendCommand(ST77XX_MADCTL, &madctl, 1);
#elif defined(HELTEC_LORA_V3) // Tracker v1
displayInit(Rcmd2invert); // invert RGB
#endif
displayInit(Rcmd3);
setRotation(DISPLAY_ROTATION);
// clear the buffer before display on
sprite->fillScreen(ST77XX_BLACK);
endFrame();
// turn on backlight
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
}
void ST7735Display::turnOn() { void ST7735Display::turnOn() {
ST7735Display::begin(); if (!_isOn) {
if (_peripher_power) _peripher_power->claim();
_resetAndInit();
sendCommand(ST77XX_DISPON);
// Now turn on the backlight
// digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
_isOn = true;
}
} }
void ST7735Display::turnOff() { void ST7735Display::turnOff() {
if (_isOn) { if (_isOn) {
digitalWrite(PIN_TFT_RST, LOW); sendCommand(ST77XX_DISPOFF);
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
//digitalWrite(PIN_TFT_RST, LOW);
// Now turn off the backlight
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE); digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
#else
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
#endif
_isOn = false; _isOn = false;
if (_peripher_power) _peripher_power->release(); if (_peripher_power) _peripher_power->release();
@@ -70,78 +524,90 @@ void ST7735Display::turnOff() {
} }
void ST7735Display::clear() { void ST7735Display::clear() {
//Serial.println("DBG: display.Clear"); sprite->fillScreen(ST77XX_BLACK);
display.fillScreen(ST77XX_BLACK);
} }
void ST7735Display::startFrame(Color bkg) { void ST7735Display::startFrame(Color bkg) {
display.fillScreen(0x00); sprite->fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE); sprite->setTextColor(curr_color = ST77XX_WHITE);
display.setTextSize(1); // This one affects size of Please wait... message sprite->setFreeFont();
display.cp437(true); // Use full 256 char 'Code Page 437' font sprite->setTextSize(1); // This one affects size of Please wait... message
//sprite->cp437(true); // Use full 256 char 'Code Page 437' font
} }
void ST7735Display::setTextSize(int sz) { void ST7735Display::setTextSize(int sz) {
display.setTextSize(sz); sprite->setTextSize(sz);
} }
void ST7735Display::setColor(Color c) { void ST7735Display::setColor(Color c) {
switch (c) { switch (c) {
case DisplayDriver::DARK : case DisplayDriver::DARK :
_color = ST77XX_BLACK; curr_color = ST77XX_BLACK;
break; break;
case DisplayDriver::LIGHT : case DisplayDriver::LIGHT :
_color = ST77XX_WHITE; curr_color = ST77XX_WHITE;
break; break;
case DisplayDriver::RED : case DisplayDriver::RED :
_color = ST77XX_RED; curr_color = ST77XX_RED;
break; break;
case DisplayDriver::GREEN : case DisplayDriver::GREEN :
_color = ST77XX_GREEN; curr_color = ST77XX_GREEN;
break; break;
case DisplayDriver::BLUE : case DisplayDriver::BLUE :
_color = ST77XX_BLUE; curr_color = ST77XX_BLUE;
break; break;
case DisplayDriver::YELLOW : case DisplayDriver::YELLOW :
_color = ST77XX_YELLOW; curr_color = ST77XX_YELLOW;
break; break;
case DisplayDriver::ORANGE : case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE; curr_color = ST77XX_ORANGE;
break; break;
default: default:
_color = ST77XX_WHITE; curr_color = ST77XX_WHITE;
break; break;
} }
display.setTextColor(_color); sprite->setTextColor(curr_color);
} }
void ST7735Display::setCursor(int x, int y) { void ST7735Display::setCursor(int x, int y) {
display.setCursor(x*SCALE_X, y*SCALE_Y); sprite->setCursor(x*SCALE_X, y*SCALE_Y);
} }
void ST7735Display::print(const char* str) { void ST7735Display::print(const char* str) {
display.print(str); sprite->print(str);
} }
void ST7735Display::fillRect(int x, int y, int w, int h) { void ST7735Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); sprite->fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color);
} }
void ST7735Display::drawRect(int x, int y, int w, int h) { void ST7735Display::drawRect(int x, int y, int w, int h) {
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); sprite->drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color);
} }
void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color); sprite->drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, curr_color);
} }
uint16_t ST7735Display::getTextWidth(const char* str) { uint16_t ST7735Display::getTextWidth(const char* str) {
int16_t x1, y1; return sprite->textWidth(str) / SCALE_X;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / SCALE_X;
} }
void ST7735Display::endFrame() { void ST7735Display::endFrame() {
// display.display(); // blit the canvas buffer to LCD
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
uint16_t x, y;
uint16_t* pixels = (uint16_t *) ((TFT_eSprite *) sprite)->getPointer();
for (y = 0; y < 80; y++, pixels += 160) {
setAddrWindow(0, y, 160, 1);
#ifdef ESP_PLATFORM
_spi->transferBytes((uint8_t *)pixels, NULL, 2 * 160);
#else
_spi->transfer(pixels, NULL, 2 * 160);
#endif
}
_spi->endTransaction();
set_CS(HIGH);
} }
+6 -6
View File
@@ -3,28 +3,25 @@
#include "DisplayDriver.h" #include "DisplayDriver.h"
#include <Wire.h> #include <Wire.h>
#include <SPI.h> #include <SPI.h>
#include <Adafruit_GFX.h> #include "TFT_eSPI.h"
#include <Adafruit_ST7735.h>
#include <helpers/RefCountedDigitalPin.h> #include <helpers/RefCountedDigitalPin.h>
class ST7735Display : public DisplayDriver { class ST7735Display : public DisplayDriver {
Adafruit_ST7735 display;
bool _isOn; bool _isOn;
uint16_t _color;
RefCountedDigitalPin* _peripher_power; RefCountedDigitalPin* _peripher_power;
bool i2c_probe(TwoWire& wire, uint8_t addr); bool i2c_probe(TwoWire& wire, uint8_t addr);
public: public:
#ifdef USE_PIN_TFT #ifdef USE_PIN_TFT
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), // display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
_peripher_power(peripher_power) _peripher_power(peripher_power)
{ {
_isOn = false; _isOn = false;
} }
#else #else
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), // display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
_peripher_power(peripher_power) _peripher_power(peripher_power)
{ {
_isOn = false; _isOn = false;
@@ -46,4 +43,7 @@ public:
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override; uint16_t getTextWidth(const char* str) override;
void endFrame() override; void endFrame() override;
protected:
void _resetAndInit();
}; };
+14
View File
@@ -18,6 +18,14 @@
#define SCALE_Y 2.109375f // 135 / 64 #define SCALE_Y 2.109375f // 135 / 64
#endif #endif
#ifdef DISPLAY_SCALE_X
#define SCALE_X DISPLAY_SCALE_X
#endif
#ifdef DISPLAY_SCALE_Y
#define SCALE_Y DISPLAY_SCALE_Y
#endif
bool ST7789Display::begin() { bool ST7789Display::begin() {
if(!_isOn) { if(!_isOn) {
pinMode(PIN_TFT_VDD_CTL, OUTPUT); pinMode(PIN_TFT_VDD_CTL, OUTPUT);
@@ -32,6 +40,9 @@ bool ST7789Display::begin() {
display.init(); display.init();
display.landscapeScreen(); display.landscapeScreen();
#ifdef DISPLAY_FLIP_VERTICALLY
display.flipScreenVertically();
#endif
display.displayOn(); display.displayOn();
setCursor(0,0); setCursor(0,0);
@@ -49,6 +60,9 @@ void ST7789Display::turnOn() {
// Re-initialize the display // Re-initialize the display
display.init(); display.init();
display.displayOn(); display.displayOn();
#ifdef DISPLAY_FLIP_VERTICALLY
display.flipScreenVertically();
#endif
delay(20); delay(20);
// Now turn on the backlight // Now turn on the backlight
+3 -1
View File
@@ -14,8 +14,10 @@ class ST7789Display : public DisplayDriver {
bool i2c_probe(TwoWire& wire, uint8_t addr); bool i2c_probe(TwoWire& wire, uint8_t addr);
public: public:
#ifdef HELTEC_VISION_MASTER_T190 #if defined(HELTEC_VISION_MASTER_T190)
ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;} ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;}
#elif defined(THINKNODE_M9)
ST7789Display() : DisplayDriver(128, 64), display(&SPI, ST7789_RESET, ST7789_RS, ST7789_CS, GEOMETRY_RAWMODE, 320, 240, ST7789_SDA, ST7789_MISO, ST7789_SCK) {_isOn = false;}
#else #else
ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
#endif #endif
+6 -2
View File
@@ -1,5 +1,9 @@
#include "ST7789LCDDisplay.h" #include "ST7789LCDDisplay.h"
#ifndef PIN_TFT_MISO
#define PIN_TFT_MISO -1
#endif
#ifndef DISPLAY_ROTATION #ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 3 #define DISPLAY_ROTATION 3
#endif #endif
@@ -29,8 +33,8 @@ bool ST7789LCDDisplay::begin() {
} }
// Im not sure if this is just a t-deck problem or not, if your display is slow try this. // Im not sure if this is just a t-deck problem or not, if your display is slow try this.
#if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); displaySPI.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS);
#endif #endif
display.init(DISPLAY_WIDTH, DISPLAY_HEIGHT); display.init(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+2 -2
View File
@@ -8,7 +8,7 @@
#include <helpers/RefCountedDigitalPin.h> #include <helpers/RefCountedDigitalPin.h>
class ST7789LCDDisplay : public DisplayDriver { class ST7789LCDDisplay : public DisplayDriver {
#if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
SPIClass displaySPI; SPIClass displaySPI;
#endif #endif
Adafruit_ST7789 display; Adafruit_ST7789 display;
@@ -25,7 +25,7 @@ public:
{ {
_isOn = false; _isOn = false;
} }
#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) #elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT)
ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
displaySPI(HSPI), displaySPI(HSPI),
display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
+25 -4
View File
@@ -3,12 +3,33 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
// Mock SHA256 class for testing // Mock SHA256 for native testing — deterministic but not cryptographic.
// Provides minimal interface to allow Utils.cpp to compile // finalize() writes real (non-garbage) output so calculatePacketHash() produces
// distinguishable results for packets with different payloads.
#include <string.h>
class SHA256 { class SHA256 {
uint8_t _state[32];
size_t _len;
public: public:
void update(const uint8_t* data, size_t len) {} SHA256() : _len(0) { memset(_state, 0, sizeof(_state)); }
void finalize(uint8_t* hash, size_t hashLen) {}
void update(const void* data, size_t len) {
const uint8_t* bytes = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < len; i++) {
uint8_t b = bytes[i];
_state[_len % 32] ^= b;
_state[(_len + 1) % 32] += (uint8_t)((b >> 1) | (b << 7));
_len++;
}
}
void finalize(uint8_t* hash, size_t hashLen) {
for (size_t i = 0; i < hashLen; i++) {
hash[i] = _state[i % 32];
}
}
void resetHMAC(const uint8_t* key, size_t keyLen) {} void resetHMAC(const uint8_t* key, size_t keyLen) {}
void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {} void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {}
}; };
@@ -0,0 +1,103 @@
#include <gtest/gtest.h>
#include "helpers/SimpleMeshTables.h"
using namespace mesh;
// Build a packet that calculatePacketHash() distinguishes by payload content.
// header selects ROUTE_TYPE_FLOOD so isRouteDirect() returns false.
static Packet makeFloodPacket(uint8_t seed) {
Packet p;
p.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
p.payload[0] = seed;
p.payload_len = 1;
p.path_len = 0;
return p;
}
static Packet makeDirectPacket(uint8_t seed) {
Packet p;
p.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
p.payload[0] = seed;
p.payload_len = 1;
p.path_len = 0;
return p;
}
// ── wasSeen: pure query ───────────────────────────────────────────────────────
TEST(SimpleMeshTables, WasSeen_ReturnsFalseForUnseen) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
}
// wasSeen shouldn't change state
TEST(SimpleMeshTables, WasSeen_IsPureQuery_DoesNotInsert) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
EXPECT_FALSE(t.wasSeen(&p));
}
// ── markSeen + wasSeen ───────────────────────────────────────────────────────
TEST(SimpleMeshTables, MarkSeen_MakesWasSeenReturnTrue) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
}
TEST(SimpleMeshTables, MarkSeen_DoesNotAffectOtherPackets) {
SimpleMeshTables t;
Packet p1 = makeFloodPacket(0x01);
Packet p2 = makeFloodPacket(0x02);
t.markSeen(&p1);
EXPECT_FALSE(t.wasSeen(&p2));
}
// Canonical pattern used at every onRecvPacket call site:
// if (!wasSeen(pkt)) { markSeen(pkt); process(pkt); }
TEST(SimpleMeshTables, QueryThenMark_WorksCorrectly) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
}
// ── dup stats ────────────────────────────────────────────────────────────────
TEST(SimpleMeshTables, WasSeen_IncrementsFloodDupStat) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
t.wasSeen(&p);
EXPECT_EQ(1u, t.getNumFloodDups());
EXPECT_EQ(0u, t.getNumDirectDups());
}
TEST(SimpleMeshTables, WasSeen_IncrementsDirectDupStat) {
SimpleMeshTables t;
Packet p = makeDirectPacket(0x01);
t.markSeen(&p);
t.wasSeen(&p);
EXPECT_EQ(0u, t.getNumFloodDups());
EXPECT_EQ(1u, t.getNumDirectDups());
}
// ── clear ────────────────────────────────────────────────────────────────────
TEST(SimpleMeshTables, Clear_RemovesSeenPacket) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
ASSERT_TRUE(t.wasSeen(&p));
t.clear(&p);
EXPECT_FALSE(t.wasSeen(&p));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1; uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off(); NRF52Board::powerOff();
} }
}; };
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1; uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off(); NRF52Board::powerOff();
} }
}; };
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1; uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off(); NRF52Board::powerOff();
} }
}; };
@@ -38,7 +38,7 @@ public:
uint32_t button_pin = PIN_BUTTON1; uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off(); NRF52Board::powerOff();
} }
}; };
-27
View File
@@ -20,33 +20,6 @@ void HeltecE213Board::begin() {
} }
} }
void HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold 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 HeltecE213Board::powerOff() {
enterDeepSleep(0);
}
uint16_t HeltecE213Board::getBattMilliVolts() { uint16_t HeltecE213Board::getBattMilliVolts() {
analogReadResolution(10); analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, HIGH); digitalWrite(PIN_ADC_CTRL, HIGH);
-3
View File
@@ -3,7 +3,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h> #include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h> #include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
class HeltecE213Board : public ESP32Board { class HeltecE213Board : public ESP32Board {
@@ -13,8 +12,6 @@ public:
HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
void begin(); void begin();
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override; uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ; const char* getManufacturerName() const override ;
}; };
-27
View File
@@ -20,33 +20,6 @@ void HeltecE290Board::begin() {
} }
} }
void HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold 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 HeltecE290Board::powerOff() {
enterDeepSleep(0);
}
uint16_t HeltecE290Board::getBattMilliVolts() { uint16_t HeltecE290Board::getBattMilliVolts() {
analogReadResolution(10); analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, HIGH); digitalWrite(PIN_ADC_CTRL, HIGH);
-3
View File
@@ -3,7 +3,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h> #include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h> #include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
class HeltecE290Board : public ESP32Board { class HeltecE290Board : public ESP32Board {
@@ -13,8 +12,6 @@ public:
HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { } HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { }
void begin(); void begin();
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override; uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ; const char* getManufacturerName() const override ;

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