Compare commits

...

198 Commits

Author SHA1 Message Date
Scott Powell
d072e7b575 * ver bump to v1.6.0 2025-05-09 18:12:42 +10:00
Scott Powell
d8952f3710 * ESP32Board: can now download entire log file via OTA webserver (URL: /log) 2025-05-09 16:17:36 +10:00
Scott Powell
810fc8b8f0 * Heltec tracker: new 'periph_power' shared pin (between Display & GPS) 2025-05-08 15:50:53 +10:00
Scott Powell
997261a68e * Heltec tracker: added GPS to custom HWTSensorManager 2025-05-08 13:55:09 +10:00
Scott Powell
98f1785104 * refactor: LocationProvider classes moved to src/helpers/sensors
* refactor:  Heltec_Wireless_Tracker* env moved to new variants/heltec_tracker dir
2025-05-08 13:23:53 +10:00
Scott Powell
60b7897665 * ST7735Display: now applies SCALE_X, SCALE_Y 2025-05-08 12:48:34 +10:00
Scott Powell
7a7f436921 * Heltec Wireless Tracker fixes: getTextWidth() missing, PIN_BOARD_SDA/SCL 2025-05-08 12:42:28 +10:00
ripplebiz
0e208f01cd Merge pull request #258 from seagull9000/Heltec-Wireless-Tracker-support
Heltec Wireless Tracker support
2025-05-08 12:29:02 +10:00
ripplebiz
eba0daf70a Merge pull request #255 from jquatier/bitmap-scaling-7789
fixing scaling of bitmaps for 7789 display
2025-05-08 11:33:49 +10:00
JQ
94db70d511 new implementation 2025-05-07 18:14:56 -07:00
seagull9000
c2ef0a3f0b Heltec Wireless Tracker support 2025-05-07 21:42:29 +12:00
seagull9000
e076e797e6 Heltec Wireless Tracker support 2025-05-07 21:40:27 +12:00
ripplebiz
90b3b1b6fe Merge pull request #257 from liamcottle/fix/valid-ble-pin
ble pin must be zero or a valid 6 digit pin
2025-05-07 19:03:57 +10:00
liamcottle
f18a3b78ad ble pin must be zero or a valid 6 digit pin 2025-05-07 20:53:59 +12:00
ripplebiz
6962a043e2 Merge pull request #256 from liamcottle/feature/enable-repeater-neighbours
enable neighbours feature for all repeater variants
2025-05-07 18:34:05 +10:00
liamcottle
d04eda9f16 enable neighbours feature for all repeater variants 2025-05-07 20:26:15 +12:00
JQ
941d2d5c13 fixing scaling of bitmaps for 7789 display 2025-05-06 20:47:14 -07:00
Scott Powell
f855523481 * refactor: removed mesh::Mesh dependency from CommonCLI 2025-05-06 11:51:51 +10:00
ripplebiz
6dd85880e4 Merge pull request #246 from liamcottle/dev
Use identity key as default node name
2025-05-06 10:39:58 +10:00
ripplebiz
dfe3561f39 Merge pull request #251 from recrof/dev
Disable debug flags that were not commented out after debugging
2025-05-06 10:37:04 +10:00
ripplebiz
bff90a5102 Merge pull request #250 from fdlamotte/fix_set_custom_var
fix for set_custom_var
2025-05-06 10:35:30 +10:00
Rastislav Vysoky
078a60040d Merge branch 'ripplebiz:dev' into dev 2025-05-05 22:48:56 +02:00
recrof
eaea26267b disable debug flags that were not commented out after debugging 2025-05-05 22:48:21 +02:00
Florent de Lamotte
a39c000f5d fix for set_custom_var 2025-05-05 16:40:00 +02:00
ripplebiz
fb5fcae614 Merge pull request #249 from recrof/dev
lilygo t-echo, elecrow thinknode m1: correct display scalling
2025-05-05 19:30:39 +10:00
Rastislav Vysoky
81863a5995 Delete src/helpers/CustomLR1121Wrapper.h 2025-05-05 08:34:41 +02:00
Rastislav Vysoky
310e6c64d4 Delete src/helpers/CustomLR1121.h 2025-05-05 08:34:24 +02:00
recrof
5780b50a48 echo, m1: correct display scalling; all nrf52 boards jsons: added debug.openocd_target 2025-05-05 08:30:12 +02:00
Scott Powell
791da53c7b * ST7789Display: now with SCALE_X,SCALE_Y
* fix for GxEPDDisplay
2025-05-05 15:54:31 +10:00
ripplebiz
5b27bef485 Merge pull request #248 from jquatier/t114-landscape
T114 landscape display
2025-05-05 15:43:35 +10:00
JQ
d3a88e9206 T114 Landscape 2025-05-04 21:54:47 -07:00
JQ
67d709b3aa T114 Landscape 2025-05-04 21:51:58 -07:00
Scott Powell
136f3d1000 * GxEPDDIsplay: driver now applying SCALE_X, SCALE_Y 2025-05-05 13:37:48 +10:00
ripplebiz
458f309065 Merge pull request #243 from recrof/dev
initial support for Elecrow ThinkNode M1
2025-05-05 13:17:32 +10:00
Scott Powell
af606343a7 * FIX: UI should show "< Room Server >" 2025-05-05 13:11:43 +10:00
ripplebiz
1f06d22bde Merge pull request #247 from jquatier/ui-text-width
UI Text Width - minor improvement
2025-05-05 11:53:47 +10:00
Scott Powell
bcb64d8a4c * companion: fix for _GET_STATUS response 2025-05-05 11:49:17 +10:00
Scott Powell
cb80ceee47 * companion: protocol ver bump to 5
* companion: new prefs: telemetry_mode_base, telemetry_mode_loc
* companion: CMD_SET_OTHER_PARAMS, now optionally can set telemetry_modes
2025-05-05 11:21:55 +10:00
JQ
9d967388f7 cleanup 2025-05-04 18:20:53 -07:00
JQ
678f36a57b Implement getTextWidth for display classes
- Added getTextWidth method to DisplayDriver interface
- Implemented getTextWidth in all display classes
- Updated examples to use getTextWidth directly
2025-05-04 18:17:18 -07:00
liamcottle
8f32ee61ce no need for prefs check before prefs are loaded 2025-05-05 11:34:02 +12:00
liamcottle
0bccf29f64 use hex of first 4 bytes of identity public key as default node name 2025-05-05 11:21:31 +12:00
Scott Powell
e442e94e3d * SensorManager: now can influence advert lat/lon, new custom name:value pairs for custom settings (eg, gps on/off)
* companion: new CMD_GET_CUSTOM_VARS, CMD_SET_CUSTOM_VAR
* T1000e: now supports "gps" custom setting (value "0" or "1")
2025-05-05 00:15:35 +10:00
ripplebiz
cd9691ba81 Merge pull request #244 from fdlamotte/t1000e_telemetry_gps
t1000e quick and dirty integration of gps into telemetry framework
2025-05-04 23:46:59 +10:00
Florent
933e7ba847 t1000e quick and dirty integration of gps into telemetry framework 2025-05-03 17:00:53 +02:00
recrof
b407f923e0 initial support for Elecrow ThinkNode M1 2025-05-03 15:42:10 +02:00
recrof
1e031e989d Merge branch 'dev' of github.com:recrof/MeshCore into dev 2025-05-03 14:59:06 +02:00
Scott Powell
26f01e0605 * companion: new CMD_SEND_TELEMETRY_REQ, PUSH_CODE_TELEMETRY_RESPONSE 2025-05-03 20:08:44 +10:00
Scott Powell
99774f10ac * new: SensorManager
* BasChatMesh: new onContactRequest(), for PAYLOAD_TYPE_REQ handling.
* companion, repeater and room_server: now with basic 'plumbing' to handle REQ_TYPE_GET_TELEMETRY_DATA (0x03).
* dependency: added CayenneLPP to libdeps
* all target.* modules now with a stub 'sensors' object.
2025-05-03 13:14:03 +10:00
ripplebiz
6aa4df6ca5 Merge pull request #242 from fdlamotte/xiao-nrf52_pindef_in_config
xiao-nrf : move pindef to platformio.ini
2025-05-03 12:10:44 +10:00
Florent
e1c3dfca92 xiao-nrf : move pindef in pio.ini 2025-05-02 08:27:26 +02:00
Scott Powell
c0870960d6 * repeater CLI: 'neighbors' command now responds with "-none-" if no neighbors 2025-05-02 13:24:06 +10:00
ripplebiz
73231b1d22 Merge pull request #213 from jquatier/ui-enhancements
Device UI Enhancements: Battery indicator, boot screen, radio settings
2025-05-02 11:32:09 +10:00
JQ
2818749a09 revert file 2025-05-01 18:29:25 -07:00
JQ
77f44f727e merge from dev 2025-05-01 18:10:03 -07:00
ripplebiz
8f84a5d990 Merge pull request #237 from fdlamotte/t114_monochrome
T114 monochrome
2025-05-01 16:01:36 +10:00
ripplebiz
9813ec6d96 Merge pull request #229 from recrof/dev
build.sh: detect if we have nrf52 by probing for *.zip and *.hex output files
2025-05-01 12:01:25 +10:00
ripplebiz
d63775b878 Merge pull request #234 from fdlamotte/Arduino_serial_using_stream
use Stream abstract interface for serial port in ArduinoSerialInterface
2025-04-30 22:14:55 +10:00
Scott Powell
8a8e89f282 * refactor: "neighbors" command 2025-04-30 21:41:09 +10:00
Florent de Lamotte
05254bd67b t114 display : some fixes 2025-04-30 11:26:04 +02:00
Florent de Lamotte
f68b9bbfca Merge branch 'dev' into t114_monochrome 2025-04-30 11:11:01 +02:00
Florent de Lamotte
1c67d1cb42 change screen rotation and fix bitmap 2025-04-30 11:09:43 +02:00
Scott Powell
056bcf83d9 * Repeater: neighbour table now only of other repeaters 2025-04-30 18:43:48 +10:00
Scott Powell
f261599608 * bug fix for CLI retry attempts (should be ignored) 2025-04-30 18:10:58 +10:00
Scott Powell
e6325db72b * repeater: new CLI command 'neighbors' 2025-04-30 18:01:30 +10:00
recrof
154b5e4014 New Board: Elecrow ThinkNode M1 2025-04-29 17:32:08 +02:00
ripplebiz
21756d5e1c Merge pull request #232 from fdlamotte/xiao_s3_screen
add display to companion_radio_ble target
2025-04-29 15:50:25 +10:00
Florent de Lamotte
7eebd81cd0 use Stream abstract interface for serial port in ArduinoSerialInterface 2025-04-28 16:17:29 +02:00
Florent de Lamotte
2cdb3b501c add display to companion_radio_ble target 2025-04-28 11:08:20 +02:00
Jacob Quatier
13654347c7 Merge branch 'ripplebiz:main' into ui-enhancements 2025-04-27 20:37:49 -07:00
recrof
4f2aaa47d3 detect if we have nrf52 by probing for *.zip and *.hex output files 2025-04-27 10:24:38 +02:00
ripplebiz
b614cef980 Merge pull request #228 from liamcottle/dev
remove pin mode setup from uitask
2025-04-26 20:07:48 +10:00
ripplebiz
569ef18b35 Merge pull request #225 from alesgenova/short-led
companion_radio: greatly reduce the status LED usage
2025-04-26 17:27:03 +10:00
liamcottle
8f5a2ac832 remove pin mode setup from uitask 2025-04-26 17:54:59 +12:00
Scott Powell
c942aa06f9 * Packet::readFrom() payload_len guard 2025-04-26 11:05:13 +10:00
ripplebiz
2f047da3a3 Merge pull request #223 from neutralinsomniac/add_direnv_to_gitignore
Add .direnv/ to .gitignore. This is the directory that holds the nix-generated development environment
2025-04-25 19:28:12 +10:00
Alessandro Genova
f51ab11cf1 companion_radio: greatly reduce the status LED usage 2025-04-24 23:22:14 -04:00
Florent
2a7e105c59 some tests 2025-04-24 22:37:06 +02:00
Jeremy O'Brien
0fc4d244ea Add .direnv/ to .gitignore. This is the directory that holds the nix-generated development environment 2025-04-24 12:22:52 -04:00
Scott Powell
36b981c9eb * new targets: Generic_E22_*_repeater 2025-04-24 13:50:18 +10:00
Scott Powell
e1092118d9 * ESPNOW: packet rx/tx counters 2025-04-24 12:16:55 +10:00
Scott Powell
00f0bb7471 * ESPNOW: now using hardware RNG for radio_new_identity() 2025-04-24 10:59:01 +10:00
ripplebiz
10df19d3a3 Merge pull request #222 from rusty-labs/tbeam_sx1262_support
Support for TBeam SX1262 board /dev
2025-04-24 10:28:56 +10:00
rusty.labs
da1febdd88 Support for TBeam SX1262 board 2025-04-23 20:23:06 -04:00
ripplebiz
70b6e01c49 Merge pull request #205 from neutralinsomniac/add_default_nix
Add default.nix/.envrc for automagic dev environment on NixOS
2025-04-24 09:56:48 +10:00
ripplebiz
285423ca55 Merge pull request #220 from fdlamotte/xiao-nrf52-targets
xiao_nrf52-missing_targets
2025-04-23 19:23:37 +10:00
Florent
8c992d5037 xiao_nrf52-missing_targets 2025-04-23 11:20:28 +02:00
ripplebiz
977b76c47e Merge pull request #219 from recrof/dev
bugfix: Faketec: only include SSD1306Display during build
2025-04-23 19:06:47 +10:00
recrof
669597ea4f bugfix: only include SSD1306Display during build 2025-04-23 10:43:56 +02:00
Scott Powell
a87b5231cc * RP2040 IdentityStore begin(), to ensure mkdir() 2025-04-22 15:26:04 +10:00
ripplebiz
2ba3f42f30 Merge pull request #217 from AeroXuk/feature/rp2040_picow_dev
Support for Pi PicoW
2025-04-22 15:04:09 +10:00
AeroXuk
26efe2fb19 Hopefully the correct ADC_MULTIPLIER value. 2025-04-21 23:01:44 +01:00
AeroXuk
4d9964ff98 Correct opens to use "w" filemode instead of "w+" filemode. 2025-04-21 21:49:41 +01:00
AeroXuk
b1c8963e1e Merge 'dev' into 'feature/rp2040_picow_dev'. 2025-04-21 21:21:10 +01:00
AeroXuk
99246e6b6f Added Pi PIcoW support in the following modes:
- Companion Radio over USB Serial
- Repeater
- Room Server
- Terminal Chat
2025-04-21 21:17:03 +01:00
Scott Powell
76847a7756 Merge branch 'dev' 2025-04-21 21:57:55 +10:00
Scott Powell
9d82911e18 * ver bump to v1.5.1 2025-04-21 21:50:55 +10:00
Scott Powell
631f593895 * companion: bug fix for 'export contact' 2025-04-21 21:38:57 +10:00
Scott Powell
933764546a Merge branch 'dev' 2025-04-21 17:39:57 +10:00
Scott Powell
1e263cab2b * ver bump to v1.5.0 2025-04-21 17:39:36 +10:00
ripplebiz
a81e8b4b54 Merge pull request #211 from fdlamotte/techo-display
Techo display
2025-04-21 16:44:52 +10:00
Scott Powell
8f70d48ea1 * fix for AutoDiscoverRTCClock 2025-04-21 16:35:55 +10:00
ripplebiz
c0eb5bffaa Merge pull request #212 from fdlamotte/xiao_nrf52-batread
xiao-nrf keep vbat_en to low to prevent issues ...
2025-04-21 16:29:26 +10:00
ripplebiz
1b25a63996 Merge pull request #210 from fdlamotte/xiao_expansion
support for xiao expansion board screen
2025-04-21 16:25:51 +10:00
ripplebiz
7669b97b8b Merge pull request #208 from cod3doomy/dev
updated: t-beam supreme sx1262 to be merged
2025-04-21 16:23:06 +10:00
JQ
7d7692a13b adding connected check 2025-04-20 19:17:20 -07:00
Jacob Quatier
c34dd2a40c UI: battery indicator, boot screen, radio settings 2025-04-20 19:17:02 -07:00
Florent
6735960a4e xiao-nrf keep vbat_en to low to prevent issues ... 2025-04-20 21:15:59 +02:00
Florent
2d6c834887 std behaviour 2025-04-20 17:10:57 +02:00
Florent
052ca9f12f fix screen boot 2025-04-20 16:44:30 +02:00
Florent
512f0900da led and button assigned in ui 2025-04-20 16:41:33 +02:00
Florent
04fe2f567f support for xiao expansion board screen 2025-04-20 11:03:01 +02:00
cod3doomy
f64470c581 t-beam s3 supreme: repeater, room server, and ble companion verified working. RTC working. No screen, GPS or sensors. 2025-04-19 19:51:01 -07:00
cod3doomy
50f6e8a089 Merge branch 'dev' of https://github.com/cod3doomy/MeshCore_HW_Dev into dev 2025-04-17 19:27:08 -07:00
Scott Powell
7b1582a0b9 * room server keep_alive ACKs now have unsynced_count appended. 2025-04-17 15:46:51 +10:00
Jeremy O'Brien
21564ae494 add default.nix/.envrc for automagic platformio dev environment on NixOS 2025-04-16 12:18:49 -04:00
Scott Powell
b17196acb4 * room server login response now includes unsynced posts counter 2025-04-16 16:51:04 +10:00
Florent
ea24a12ba3 techo-display: first bits 2025-04-15 22:37:50 +02:00
ripplebiz
1f1d39d179 Merge pull request #193 from fdlamotte/t114-display
T114 display
2025-04-15 18:18:16 +10:00
Florent
f9bc3a1ebb Merge branch 'dev' into t114-display 2025-04-15 07:46:45 +02:00
Scott Powell
fbfa8bbe57 * fix: compilation error for T1000e 2025-04-15 15:15:06 +10:00
cod3doomy
310ab9710c Merge branch 'dev' into dev 2025-04-14 20:43:33 -07:00
ripplebiz
69d1d920bb Merge pull request #202 from fdlamotte/rx_boost_toggle_lr1110
permit access to rx_boosted_mode at runtime for LR1110
2025-04-15 12:03:02 +10:00
Florent
7f7b03e442 permit access to rx_boosted_mode at runtime for LR1110 (for dpm tests on that setting) 2025-04-14 12:23:28 +02:00
ripplebiz
2a875d9930 Merge pull request #192 from fdlamotte/prevent-errors-when-built-against-XiaoNrf52Board-by-error
compiles xiao_nrf52 board files only if we compile for xiao_nrf52
2025-04-13 13:39:53 +10:00
ripplebiz
5848080f58 Merge pull request #196 from liamcottle/dev
Fix RAK4631 builds after addition of Xio nRF52 board
2025-04-13 13:22:58 +10:00
liamcottle
e825e4474f fix rak4631 builds after addition of xio nrf52 board 2025-04-13 13:36:39 +12:00
Scott Powell
04118f01fc * companion: protocol ver (FIRMWARE_VER_CODE) now 4 2025-04-12 22:06:47 +10:00
Scott Powell
34faa49685 * new CMD_SET_OTHER_PARAMS (38) 2025-04-12 22:00:05 +10:00
Scott Powell
561d289ea5 Companion: new 'manual_add_contacts' pref. New PUSH_CODE_NEW_ADVERT frames 2025-04-12 21:43:30 +10:00
ripplebiz
2de87d1875 Merge pull request #189 from fdlamotte/t1000_sensors_disable
t1000 : power bat sensor only when necessary
2025-04-12 13:32:01 +10:00
cod3doomy
67ca4a1c8e T-Beam Supreme: Repeater and BLE working. Added PCF8563 RTC 2025-04-11 18:17:20 -07:00
Florent
cf3d55201f ui : manage colors and ensure fw using ssd1306 still compile 2025-04-11 22:23:47 +02:00
Florent
b4330e376c compiles xiao_nrf52 board files only if we compile for xiao_nrf52 2025-04-11 14:51:10 +02:00
ripplebiz
8ee251a19e Merge pull request #185 from fdlamotte/xiao-nrf52
Xiao nrf52
2025-04-11 20:32:44 +10:00
Scott Powell
1d4ae9f3c4 minor refactor 2025-04-11 19:11:06 +10:00
Florent de Lamotte
82bcd74932 xiao-nrf52 : initial support for meshcore
initial config for xiao_nrf52

repeater role for xiao-nrf52

xiao-nrf52 correction on bat voltage

xiao-nrf52 this file should not be here ...
2025-04-11 08:10:18 +02:00
Scott Powell
4704ea8dae Merge branch 'trans-codes' into dev 2025-04-11 15:12:14 +10:00
Scott Powell
ab8cd85d8e * added Packet::getRawLength() helper 2025-04-11 15:12:04 +10:00
Scott Powell
366461a3a1 * companion: onRawDataRecv() guard for payload_len too long 2025-04-11 14:12:09 +10:00
Florent
96d6ffefad t1000 : power bat sensor only when necessary 2025-04-10 22:00:25 +02:00
Scott Powell
3c7ff8da29 * Packet::header, new: ROUTE_TYPE_TRANS_FLOOD, ROUTE_TYPE_TRANS_DIRECT
* Packet wire format change: (optional) extra 4 bytes for two trans_codes
2025-04-11 04:56:13 +10:00
Florent de Lamotte
7534c5143f display and btn handling starts working ... 2025-04-10 16:24:17 +02:00
ripplebiz
a5f210779f Merge pull request #177 from fdlamotte/t114_usb
add companion radio usb to t114
2025-04-10 19:24:04 +10:00
ripplebiz
87ca6e19ae Merge pull request #188 from recrof/dev
lilygo t-echo: disable gps to conserve battery, move init stuff to variant.cpp; lilygo t-beam: report battery voltage
2025-04-10 17:09:16 +10:00
cod3doomy
c4c175cab8 T-Beam Supreme: added PMU config, enabled pwr button. Repeater env verified working. 2025-04-09 18:28:46 -07:00
recrof
a3c8597747 LilyGo T-Beam: added battery voltage reporting 2025-04-09 14:08:13 +02:00
recrof
511a935bbf move gps standby pin to gps section 2025-04-09 13:21:34 +02:00
recrof
1718657829 lilygo t-echo: move init stuff to variant.cpp, disable gps to conserve battery 2025-04-09 13:18:35 +02:00
ripplebiz
cc5c7b3ab7 Merge pull request #187 from oltaco/rak-oled-pins
add defines for RAK OLED pins
2025-04-09 20:45:41 +10:00
ripplebiz
d4544804b5 Merge pull request #186 from oltaco/wire-setpins-buildfix
Wire.setpins buildfix
2025-04-09 19:55:20 +10:00
taco
be64fa7ca0 build: fix RAK, T114, T1000 builds 2025-04-09 18:22:01 +10:00
taco
5c2c248f70 build: fix RAK, T114, T1000e builds 2025-04-09 18:19:13 +10:00
taco
9b3e7e5a21 FIX: define OLED pins for RAK 2025-04-09 18:07:01 +10:00
ripplebiz
c4b221f386 Merge pull request #184 from adrian-immel/Fix-Floating-Pin
Fix Floating Pin on FakeTec/Promicro: Update BUTTON_PIN to INPUT_PULLUP
2025-04-09 10:27:58 +10:00
cod3doomy
3eded4581a Checkpoint: Repeater env built and works with HW. No screen or other periphs 2025-04-08 17:08:28 -07:00
cod3doomy
6092f5737c Initial commit with t-beam supreme sx1262 additions 2025-04-08 09:12:43 -07:00
Adrian
329c76629d Fix Floating Pin on Promicro
change INPUT to INPUT_PULLUP
2025-04-08 16:08:04 +02:00
ripplebiz
b98668150b Merge pull request #183 from liamcottle/dev
Show version in build info for devices with a screen
2025-04-08 21:37:45 +10:00
liamcottle
c4d32eba74 remove unused variables 2025-04-08 23:17:22 +12:00
liamcottle
bc820ae93e show firmware version and build date on room server screen 2025-04-08 23:09:55 +12:00
liamcottle
4a51cb98c6 show firmware version and build date on repeater screen 2025-04-08 23:05:27 +12:00
liamcottle
28aa94b899 show firmware version and build date on companion screen 2025-04-08 22:58:17 +12:00
ripplebiz
348db9b82f Merge pull request #182 from oltaco/tbeam-8-group-channels
set TBeam MAX_GROUP_CHANNELS=8
2025-04-08 11:06:58 +10:00
taco
a0d9449e21 set TBeam MAX_GROUP_CHANNELS=8 2025-04-08 11:04:20 +10:00
Scott Powell
b2b755c0fb Merge branch 'dev' 2025-04-07 22:01:08 +10:00
Scott Powell
d7e6a361b5 * ver bump to v1.4.3 2025-04-07 21:59:53 +10:00
Scott Powell
396a7a24b1 * default SF now 11 2025-04-07 21:55:24 +10:00
Scott Powell
9498d2e824 * ID hash 0x00 and 0xFF now reserved for future. 2025-04-06 12:34:09 +10:00
Florent
019a829121 add companion radio usb to t114 2025-04-05 17:28:25 +02:00
ripplebiz
86a3f592b9 Merge pull request #172 from recrof/dev
set repeater/room server Lat & Lon to 0.0 as default
2025-04-04 19:26:30 +11:00
ripplebiz
4d2380d168 Merge pull request #173 from oltaco/promicro-rfswitch
FIX: Set RfSwitch pins properly.
2025-04-04 19:25:12 +11:00
taco
96faf423e3 FIX: Need to explicitly setRfSwitchPins to drive RXEN high when in receive mode. 2025-04-04 15:39:11 +11:00
recrof
04ad06b8be set repeater/room server Lat & Lon to 0.0 as default 2025-04-03 14:59:20 +02:00
ripplebiz
259f4ec169 Merge pull request #171 from LitBomb/patch-9
Update faq.md
2025-04-02 19:15:33 +11:00
uncle lit
49d24283f7 Update faq.md
Add table of content
Add http://config.meshcore.dev for repeater and room server setup
Add T-Deck GPS info screen sentences counter
Add explanation who 869.525MHz is the chosen frequency for EU and UK and why 867.5MHz isn't good comparing to 869.525
2025-04-01 22:49:09 -07:00
ripplebiz
2bec0f1418 Merge pull request #167 from fdlamotte/xiao_s3_serial_onpins
Allow the use companion radio on other serial interfaces
2025-04-02 00:02:46 +11:00
Florent de Lamotte
c762d88edd Merge branch 'dev' into xiao_s3_serial_onpins 2025-04-01 11:33:00 +02:00
Florent de Lamotte
87f476354f initial commit 2025-04-01 11:32:04 +02:00
ripplebiz
accd80db77 Merge pull request #165 from liamcottle/dev
Show please wait message on boot
2025-04-01 14:42:59 +11:00
liamcottle
4d4a0ae4e3 init display before radio 2025-04-01 16:10:12 +13:00
liamcottle
7e583d7f98 show please wait message on room server boot 2025-04-01 16:04:06 +13:00
liamcottle
f93a5156bb show please wait message on repeater boot 2025-04-01 16:00:09 +13:00
liamcottle
af070af554 show please wait message on companion boot 2025-04-01 15:50:24 +13:00
ripplebiz
ec320cb5a8 Merge pull request #164 from liamcottle/dev
Fix Companion Display Noise
2025-04-01 13:02:57 +11:00
liamcottle
87443ad43f clear display on init to avoid static 2025-04-01 14:46:48 +13:00
ripplebiz
0a62ab663f Merge pull request #155 from oltaco/promicro-refactor
ProMicro refactoring
2025-04-01 12:43:09 +11:00
ripplebiz
7d24c65c4e Merge pull request #163 from LitBomb/patch-7
Update faq.md
2025-04-01 12:37:08 +11:00
uncle lit
5e553b1fc8 Update faq.md
added the following to FAQ:

public/private flood and repeater set flood.max CLI
public key in hex and base64
How paths are created and reuse to minimize utilization
T-Deck Diag explanation
Update repeater with a Raspberry Pi
clients don't repeat explanation
other meshcore projects
fix discord link to build instruction
2025-03-31 14:51:52 -07:00
Scott Powell
3a6b8fda93 * room server: faster sync of unread posts, MAX_UNSYNCED_POSTS now 32 (was 16) 2025-03-31 14:01:49 +11:00
ripplebiz
6e109779f3 Merge pull request #149 from jameshall9327/main
TBeam: Added Repeater
2025-03-31 13:10:20 +11:00
Scott Powell
3a920986ca * Tbeam fix: GPIO assignments wrong 2025-03-31 13:03:33 +11:00
Scott Powell
8740528d37 Merge commit '5493dbc12096f3f090cc48e70547a4491f5a06b1' into dev 2025-03-31 00:15:38 +11:00
ripplebiz
5493dbc120 Merge pull request #157 from fdlamotte/t1000e_clock
Remove rtc auto-detect on t1000
2025-03-31 00:14:17 +11:00
Florent
2f6427ce21 Remove rtc auto-detect on t1000 2025-03-30 15:09:40 +02:00
taco
b8f09531c4 fix: give ProMicroLLCC68 a different definition 2025-03-30 15:57:20 +11:00
taco
01e98caea7 Refactor: FaketecBoard to PromicroBoard 2025-03-30 15:52:25 +11:00
James Hall
74e7af3f57 TBeam: Added Repeater 2025-03-27 09:21:29 -04:00
148 changed files with 8272 additions and 499 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use nix

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.direnv
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
out/
.direnv/

View File

@@ -34,7 +34,8 @@
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"

View File

@@ -8,7 +8,7 @@
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
[
[
"0x239A",
"0x00B3"
],
@@ -51,7 +51,8 @@
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino",

View File

@@ -0,0 +1,61 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_Seeed_XIAO_nRF52840 -DNRF52840_XXAA -DSEEED_XIAO_NRF52840 ",
"f_cpu": "64000000L",
"hwids": [
[ "0x2886", "0x8044" ],
[ "0x2886", "0x0044" ]
],
"mcu": "nrf52840",
"variant": "Seeed_XIAO_nRF52840",
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bsp": {
"name": "adafruit"
},
"bootloader": {
"settings_addr": "0xFF000"
},
"usb_product": "XIAO nRF52840"
},
"connectivity": [
"bluetooth"
],
"debug": {
"jlink_device": "nRF52840_xxAA",
"openocd_target": "nrf52.cfg",
"svd_path": "nrf52840.svd"
},
"frameworks": [
"arduino"
],
"name": "Seeed Studio XIAO nRF52840",
"upload": {
"maximum_ram_size": 237568,
"maximum_size": 811008,
"protocol": "nrfutil",
"speed": 115200,
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"cmsis-dap",
"sam-ba",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://wiki.seeedstudio.com/XIAO_BLE",
"vendor": "Seeed Studio"
}

View File

@@ -1,32 +1,32 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
[
"0x239A",
"0x8029"
]
[
"0x239A",
"0x8029"
]
],
"usb_product": "NRF52 DK",
"mcu": "nrf52840",
"variant": "pca10056",
"bsp": {
"name": "adafruit"
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
"settings_addr": "0xFF000"
}
},
"connectivity": [
@@ -35,9 +35,10 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": [
"jlink"
"jlink"
],
"svd_path": "nrf52840.svd"
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"
@@ -50,13 +51,13 @@
"speed": 115200,
"protocol": "jlink",
"protocols": [
"jlink",
"nrfjprog",
"stlink",
"cmsis-dap",
"blackmagic"
"jlink",
"nrfjprog",
"stlink",
"cmsis-dap",
"blackmagic"
]
},
"url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/",
"vendor": "Nordic"
}
}

View File

@@ -0,0 +1,51 @@
{
"build": {
"arduino":{
"ldscript": "esp32s3_out.ld",
"partitions": "default.csv",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": [
"wifi"
],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": [
"esp-builtin"
],
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://www.lilygo.cc/products/t-beamsupreme-m",
"vendor": "LilyGo"
}

72
boards/thinknode_m1.json Normal file
View File

@@ -0,0 +1,72 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
[
"0x239A",
"0x4405"
],
[
"0x239A",
"0x0029"
],
[
"0x239A",
"0x002A"
]
],
"usb_product": "elecrow_eink",
"mcu": "nrf52840",
"variant": "ELECROW-ThinkNode-M1",
"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",
"onboard_tools": [
"jlink"
],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": [
"arduino"
],
"name": "elecrow eink",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink"
]
},
"url": "https://github.com/Elecrow-RD",
"vendor": "ELECROW"
}

View File

@@ -32,7 +32,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": ["arduino"],
"name": "Seeed T1000-E",

2
build.sh Normal file → Executable file
View File

@@ -58,7 +58,7 @@ build_firmware() {
fi
# build .uf2 for nrf52 boards
if [[ $1 == *"RAK_4631"* || $1 == *"t1000e"* || $1 == *"t114"* || $1 == *"T-Echo"* || $1 == *"Faketec"* || $1 == *"ProMicro"* ]]; then
if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then
python bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840
fi

10
default.nix Normal file
View File

@@ -0,0 +1,10 @@
{ pkgs ? import <nixpkgs> {} }:
let
in
pkgs.mkShell {
buildInputs = [
pkgs.platformio
# optional: needed as a programmer i.e. for esp32
pkgs.avrdude
];
}

View File

@@ -372,4 +372,4 @@ You can update repeater and room server firmware with a bluetooth connection bet
---

View File

@@ -0,0 +1,26 @@
#ifndef NODE_PREFS_H
#define NODE_PREFS_H
#include <cstdint> // For uint8_t, uint32_t
#define TELEM_MODE_DENY 0
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
#define TELEM_MODE_ALLOW_ALL 2
struct NodePrefs { // persisted to file
float airtime_factor;
char node_name[32];
float freq;
uint8_t sf;
uint8_t cr;
uint8_t reserved1;
uint8_t manual_add_contacts;
float bw;
uint8_t tx_power_dbm;
uint8_t telemetry_mode_base;
uint8_t telemetry_mode_loc;
float rx_delay_base;
uint32_t ble_pin;
};
#endif // NODE_PREFS_H

View File

@@ -1,8 +1,16 @@
#include "UITask.h"
#include <Arduino.h>
#include <helpers/TxtDataHelpers.h>
#include "NodePrefs.h"
#define AUTO_OFF_MILLIS 15000 // 15 seconds
#define AUTO_OFF_MILLIS 15000 // 15 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
#ifdef PIN_STATUS_LED
#define LED_ON_MILLIS 20
#define LED_ON_MSG_MILLIS 200
#define LED_CYCLE_MILLIS 4000
#endif
#ifndef USER_BTN_PRESSED
#define USER_BTN_PRESSED LOW
@@ -25,16 +33,26 @@ static const uint8_t meshcore_logo [] PROGMEM = {
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
};
void UITask::begin(DisplayDriver* display, const char* node_name, const char* build_date, uint32_t pin_code) {
void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code) {
_display = display;
_auto_off = millis() + AUTO_OFF_MILLIS;
clearMsgPreview();
_node_name = node_name;
_build_date = build_date;
_node_prefs = node_prefs;
_pin_code = pin_code;
if (_display != NULL) {
_display->turnOn();
}
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(firmware_version);
char *dash = strchr(version, '-');
if(dash){
*dash = 0;
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
}
void UITask::msgRead(int msgcount) {
@@ -47,6 +65,7 @@ void UITask::msgRead(int msgcount) {
void UITask::clearMsgPreview() {
_origin[0] = 0;
_msg[0] = 0;
_need_refresh = true;
}
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
@@ -62,71 +81,127 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
if (_display != NULL) {
if (!_display->isOn()) _display->turnOn();
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
_need_refresh = true;
}
}
void renderBatteryIndicator(DisplayDriver* _display, uint16_t batteryMilliVolts) {
// Convert millivolts to percentage
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V)
int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts);
if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0%
if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100%
// battery icon
int iconWidth = 24;
int iconHeight = 12;
int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner
int iconY = 0;
_display->setColor(DisplayDriver::GREEN);
// battery outline
_display->drawRect(iconX, iconY, iconWidth, iconHeight);
// battery "cap"
_display->fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);
// fill the battery based on the percentage
int fillWidth = (batteryPercentage * (iconWidth - 2)) / 100;
_display->fillRect(iconX + 1, iconY + 1, fillWidth, iconHeight - 2);
}
void UITask::renderCurrScreen() {
if (_display == NULL) return; // assert() ??
char tmp[80];
if (_origin[0] && _msg[0]) {
if (_origin[0] && _msg[0]) { // message preview
// render message preview
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->print(_node_name);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
_display->setCursor(0, 12);
_display->setColor(DisplayDriver::YELLOW);
_display->print(_origin);
_display->setCursor(0, 24);
_display->setColor(DisplayDriver::LIGHT);
_display->print(_msg);
_display->setCursor(100, 9);
_display->setCursor(_display->width() - 28, 9);
_display->setTextSize(2);
_display->setColor(DisplayDriver::ORANGE);
sprintf(tmp, "%d", _msgcount);
_display->print(tmp);
} else {
// render 'home' screen
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
_display->setCursor(0, 20);
_display->setTextSize(1);
_display->print(_node_name);
_display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114
} else if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
sprintf(tmp, "Build: %s", _build_date);
_display->setCursor(0, 32);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t textWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - textWidth) / 2, 22);
_display->print(_version_info);
} else { // home screen
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
// battery voltage
renderBatteryIndicator(_display, _board->getBattMilliVolts());
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
if (_connected) {
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
} else if (_pin_code != 0) {
// bw / cr
_display->setCursor(0, 30);
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
_display->print(tmp);
// BT pin
if (!_connected && _pin_code != 0) {
_display->setColor(DisplayDriver::RED);
_display->setTextSize(2);
_display->setCursor(0, 43);
sprintf(tmp, "Pin:%d", _pin_code);
_display->print(tmp);
_display->setColor(DisplayDriver::GREEN);
} else {
_display->setColor(DisplayDriver::LIGHT);
}
}
_need_refresh = false;
}
void UITask::userLedHandler() {
#ifdef PIN_STATUS_LED
static int state = 0;
static int next_change = 0;
static int last_increment = 0;
int cur_time = millis();
if (cur_time > next_change) {
if (state == 0) {
state = 1; // led on, short = unread msg
state = 1;
if (_msgcount > 0) {
next_change = cur_time + 500;
last_increment = LED_ON_MSG_MILLIS;
} else {
next_change = cur_time + 2000;
last_increment = LED_ON_MILLIS;
}
next_change = cur_time + last_increment;
} else {
state = 0;
if (_board->getBattMilliVolts() > 3800) {
next_change = cur_time + 2000;
} else {
next_change = cur_time + 4000; // 4s blank if bat level low
}
next_change = cur_time + LED_CYCLE_MILLIS - last_increment;
}
digitalWrite(PIN_STATUS_LED, state);
}
@@ -148,6 +223,7 @@ void UITask::buttonHandler() {
clearMsgPreview();
} else {
_display->turnOn();
_need_refresh = true;
}
_auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer
}
@@ -173,7 +249,12 @@ void UITask::loop() {
userLedHandler();
if (_display != NULL && _display->isOn()) {
if (millis() >= _next_refresh) {
static bool _firstBoot = true;
if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) {
_need_refresh = true;
_firstBoot = false;
}
if (millis() >= _next_refresh && _need_refresh) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();

View File

@@ -4,17 +4,20 @@
#include <helpers/ui/DisplayDriver.h>
#include <stddef.h>
#include "NodePrefs.h"
class UITask {
DisplayDriver* _display;
mesh::MainBoard* _board;
unsigned long _next_refresh, _auto_off;
bool _connected;
uint32_t _pin_code;
const char* _node_name;
const char* _build_date;
NodePrefs* _node_prefs;
char _version_info[32];
char _origin[62];
char _msg[80];
int _msgcount;
bool _need_refresh = true;
void renderCurrScreen();
void buttonHandler();
@@ -26,7 +29,7 @@ public:
_next_refresh = 0;
_connected = false;
}
void begin(DisplayDriver* display, const char* node_name, const char* build_date, uint32_t pin_code);
void begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code);
void setHasConnection(bool connected) { _connected = connected; }
bool hasDisplay() const { return _display != NULL; }

View File

@@ -3,6 +3,8 @@
#if defined(NRF52_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#endif
@@ -12,6 +14,7 @@
#include <helpers/SimpleMeshTables.h>
#include <helpers/IdentityStore.h>
#include <helpers/BaseSerialInterface.h>
#include "NodePrefs.h"
#include <RTClib.h>
#include <target.h>
@@ -57,11 +60,24 @@
#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
#ifdef DISPLAY_CLASS
#ifdef DISPLAY_CLASS // TODO: refactor this -- move to variants/*/target
#include "UITask.h"
#include <helpers/ui/SSD1306Display.h>
#ifdef ST7735
#include <helpers/ui/ST7735Display.h>
#elif ST7789
#include <helpers/ui/ST7789Display.h>
#elif defined(HAS_GxEPD)
#include <helpers/ui/GxEPDDisplay.h>
#else
#include <helpers/ui/SSD1306Display.h>
#endif
#if defined(HELTEC_LORA_V3) && defined(ST7735)
static DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared
#else
static DISPLAY_CLASS display;
#endif
static DISPLAY_CLASS display;
#define HAS_UI
#endif
@@ -83,14 +99,14 @@ static uint32_t _atoi(const char* sp) {
/*------------ Frame Protocol --------------*/
#define FIRMWARE_VER_CODE 3
#define FIRMWARE_VER_CODE 5
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
#define FIRMWARE_BUILD_DATE "9 May 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.4.2"
#define FIRMWARE_VERSION "v1.6.0"
#endif
#define CMD_APP_START 1
@@ -130,6 +146,10 @@ static uint32_t _atoi(const char* sp) {
#define CMD_SIGN_FINISH 35
#define CMD_SEND_TRACE_PATH 36
#define CMD_SET_DEVICE_PIN 37
#define CMD_SET_OTHER_PARAMS 38
#define CMD_SEND_TELEMETRY_REQ 39
#define CMD_GET_CUSTOM_VARS 40
#define CMD_SET_CUSTOM_VAR 41
#define RESP_CODE_OK 0
#define RESP_CODE_ERR 1
@@ -152,6 +172,7 @@ static uint32_t _atoi(const char* sp) {
#define RESP_CODE_CHANNEL_INFO 18 // a reply to CMD_GET_CHANNEL
#define RESP_CODE_SIGN_START 19
#define RESP_CODE_SIGNATURE 20
#define RESP_CODE_CUSTOM_VARS 21
// these are _pushed_ to client app at any time
#define PUSH_CODE_ADVERT 0x80
@@ -164,6 +185,8 @@ static uint32_t _atoi(const char* sp) {
#define PUSH_CODE_STATUS_RESPONSE 0x87
#define PUSH_CODE_LOG_RX_DATA 0x88
#define PUSH_CODE_TRACE_DATA 0x89
#define PUSH_CODE_NEW_ADVERT 0x8A
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
#define ERR_CODE_UNSUPPORTED_CMD 1
#define ERR_CODE_NOT_FOUND 2
@@ -174,23 +197,11 @@ static uint32_t _atoi(const char* sp) {
/* -------------------------------------------------------------------------------------- */
#define MAX_SIGN_DATA_LEN (8*1024) // 8K
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
struct NodePrefs { // persisted to file
float airtime_factor;
char node_name[32];
double node_lat, node_lon;
float freq;
uint8_t sf;
uint8_t cr;
uint8_t reserved1;
uint8_t reserved2;
float bw;
uint8_t tx_power_dbm;
uint8_t unused[3];
float rx_delay_base;
uint32_t ble_pin;
};
#define MAX_SIGN_DATA_LEN (8*1024) // 8K
class MyMesh : public BaseChatMesh {
FILESYSTEM* _fs;
@@ -198,6 +209,7 @@ class MyMesh : public BaseChatMesh {
NodePrefs _prefs;
uint32_t pending_login;
uint32_t pending_status;
uint32_t pending_telemetry;
BaseSerialInterface* _serial;
ContactsIterator _iter;
uint32_t _iter_filter_since;
@@ -209,6 +221,7 @@ class MyMesh : public BaseChatMesh {
uint32_t sign_data_len;
uint8_t cmd_frame[MAX_FRAME_SIZE+1];
uint8_t out_frame[MAX_FRAME_SIZE+1];
CayenneLPP telemetry;
struct Frame {
uint8_t len;
@@ -228,6 +241,10 @@ class MyMesh : public BaseChatMesh {
void loadMainIdentity() {
if (!_identity_store->load("_main", self_id)) {
self_id = radio_new_identity(); // create new random identity
int count = 0;
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
self_id = radio_new_identity(); count++;
}
saveMainIdentity(self_id);
}
}
@@ -238,7 +255,11 @@ class MyMesh : public BaseChatMesh {
void loadContacts() {
if (_fs->exists("/contacts3")) {
#if defined(RP2040_PLATFORM)
File file = _fs->open("/contacts3", "r");
#else
File file = _fs->open("/contacts3");
#endif
if (file) {
bool full = false;
while (!full) {
@@ -273,6 +294,8 @@ class MyMesh : public BaseChatMesh {
#if defined(NRF52_PLATFORM)
File file = _fs->open("/contacts3", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open("/contacts3", "w");
#else
File file = _fs->open("/contacts3", "w", true);
#endif
@@ -303,7 +326,11 @@ class MyMesh : public BaseChatMesh {
void loadChannels() {
if (_fs->exists("/channels2")) {
#if defined(RP2040_PLATFORM)
File file = _fs->open("/channels2", "r");
#else
File file = _fs->open("/channels2");
#endif
if (file) {
bool full = false;
uint8_t channel_idx = 0;
@@ -332,6 +359,8 @@ class MyMesh : public BaseChatMesh {
#if defined(NRF52_PLATFORM)
File file = _fs->open("/channels2", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open("/channels2", "w");
#else
File file = _fs->open("/channels2", "w", true);
#endif
@@ -362,7 +391,11 @@ class MyMesh : public BaseChatMesh {
sprintf(path, "/bl/%s", fname);
if (_fs->exists(path)) {
#if defined(RP2040_PLATFORM)
File f = _fs->open(path, "r");
#else
File f = _fs->open(path);
#endif
if (f) {
int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!!
f.close();
@@ -383,6 +416,8 @@ class MyMesh : public BaseChatMesh {
#if defined(NRF52_PLATFORM)
File f = _fs->open(path, FILE_O_WRITE);
if (f) { f.seek(0); f.truncate(); }
#elif defined(RP2040_PLATFORM)
File f = _fs->open(path, "w");
#else
File f = _fs->open(path, "w", true);
#endif
@@ -495,11 +530,19 @@ protected:
}
}
bool isAutoAddEnabled() const override {
return (_prefs.manual_add_contacts & 1) == 0;
}
void onDiscoveredContact(ContactInfo& contact, bool is_new) override {
if (_serial->isConnected()) {
out_frame[0] = PUSH_CODE_ADVERT;
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
if (!isAutoAddEnabled() && is_new) {
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
} else {
out_frame[0] = PUSH_CODE_ADVERT;
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
}
} else {
soundBuzzer();
}
@@ -620,9 +663,42 @@ protected:
#endif
}
uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override {
if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
uint8_t permissions = 0;
uint8_t cp = contact.flags >> 1; // LSB used as 'favourite' bit (so only use upper bits)
if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_ALL) {
permissions = TELEM_PERM_BASE;
} else if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_FLAGS) {
permissions = cp & TELEM_PERM_BASE;
}
if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_ALL) {
permissions |= TELEM_PERM_LOCATION;
} else if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_FLAGS) {
permissions |= cp & TELEM_PERM_LOCATION;
}
if (permissions & TELEM_PERM_BASE) { // only respond if base permission bit is set
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
// query other sensors -- target specific
sensors.querySensors(permissions, telemetry);
memcpy(reply, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
uint8_t tlen = telemetry.getSize();
memcpy(&reply[4], telemetry.getBuffer(), tlen);
return 4 + tlen;
}
}
return 0; // unknown
}
void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4);
uint32_t tag;
memcpy(&tag, data, 4);
if (pending_login && memcmp(&pending_login, contact.id.pub_key, 4) == 0) { // check for login response
// yes, is response to pending sendLogin()
@@ -645,8 +721,10 @@ protected:
}
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
_serial->writeFrame(out_frame, i);
} else if (len > 4 && pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0) { // check for status response
// yes, is response to pending sendStatusRequest()
} else if (len > 4 && // check for status response
pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
// FUTURE: tag == pending_status
) {
pending_status = 0;
int i = 0;
@@ -655,10 +733,23 @@ protected:
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4);
_serial->writeFrame(out_frame, i);
} else if (len > 4 && tag == pending_telemetry) { // check for telemetry response
pending_telemetry = 0;
int i = 0;
out_frame[i++] = PUSH_CODE_TELEMETRY_RESPONSE;
out_frame[i++] = 0; // reserved
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4);
_serial->writeFrame(out_frame, i);
}
}
void onRawDataRecv(mesh::Packet* packet) override {
if (packet->payload_len + 4 > sizeof(out_frame)) {
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
return;
}
int i = 0;
out_frame[i++] = PUSH_CODE_RAW_DATA;
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
@@ -706,13 +797,14 @@ protected:
public:
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables)
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL)
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL),
telemetry(MAX_PACKET_PAYLOAD - 4)
{
_iter_started = false;
offline_queue_len = 0;
app_target_ver = 0;
_identity_store = NULL;
pending_login = pending_status = 0;
pending_login = pending_status = pending_telemetry = 0;
next_ack_idx = 0;
sign_data = NULL;
@@ -729,23 +821,29 @@ public:
}
void loadPrefsInt(const char* filename) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(filename, "r");
#else
File file = _fs->open(filename);
#endif
if (file) {
uint8_t pad[8];
file.read((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0
file.read((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4
file.read(pad, 4); // 36
file.read((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40
file.read((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48
file.read((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40
file.read((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48
file.read((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56
file.read((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60
file.read((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61
file.read((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
file.read((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63
file.read((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
file.read((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64
file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
file.read((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69
file.read((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
file.read((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
file.read(pad, 1); // 71
file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
file.read(pad, 4); // 76
file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
@@ -770,12 +868,20 @@ public:
#if defined(NRF52_PLATFORM)
_identity_store = new IdentityStore(fs, "");
#elif defined(RP2040_PLATFORM)
_identity_store = new IdentityStore(fs, "/identity");
_identity_store->begin();
#else
_identity_store = new IdentityStore(fs, "/identity");
#endif
loadMainIdentity();
// use hex of first 4 bytes of identity public key as default node name
char pub_key_hex[10];
mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4);
strcpy(_prefs.node_name, pub_key_hex);
// load persisted prefs
if (_fs->exists("/new_prefs")) {
loadPrefsInt("/new_prefs"); // new filename
@@ -816,6 +922,9 @@ public:
}
const char* getNodeName() { return _prefs.node_name; }
NodePrefs* getNodePrefs() {
return &_prefs;
}
uint32_t getBLEPin() { return _active_ble_pin; }
void startInterface(BaseSerialInterface& serial) {
@@ -827,6 +936,8 @@ public:
#if defined(NRF52_PLATFORM)
File file = _fs->open("/new_prefs", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open("/new_prefs", "w");
#else
File file = _fs->open("/new_prefs", "w", true);
#endif
@@ -837,16 +948,18 @@ public:
file.write((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0
file.write((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4
file.write(pad, 4); // 36
file.write((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40
file.write((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48
file.write((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40
file.write((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48
file.write((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56
file.write((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60
file.write((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61
file.write((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
file.write((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63
file.write((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
file.write((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64
file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
file.write((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69
file.write((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
file.write((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
file.write(pad, 1); // 71
file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
file.write(pad, 4); // 76
file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
@@ -884,12 +997,15 @@ public:
out_frame[i++] = MAX_LORA_TX_POWER;
memcpy(&out_frame[i], self_id.pub_key, PUB_KEY_SIZE); i += PUB_KEY_SIZE;
int32_t lat, lon, alt = 0;
lat = (_prefs.node_lat * 1000000.0);
lon = (_prefs.node_lon * 1000000.0);
int32_t lat, lon;
lat = (sensors.node_lat * 1000000.0);
lon = (sensors.node_lon * 1000000.0);
memcpy(&out_frame[i], &lat, 4); i += 4;
memcpy(&out_frame[i], &lon, 4); i += 4;
memcpy(&out_frame[i], &alt, 4); i += 4;
out_frame[i++] = 0; // reserved
out_frame[i++] = 0; // reserved
out_frame[i++] = (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+
out_frame[i++] = _prefs.manual_add_contacts;
uint32_t freq = _prefs.freq * 1000;
memcpy(&out_frame[i], &freq, 4); i += 4;
@@ -996,8 +1112,8 @@ public:
memcpy(&alt, &cmd_frame[9], 4); // for FUTURE support
}
if (lat <= 90*1E6 && lat >= -90*1E6 && lon <= 180*1E6 && lon >= -180*1E6) {
_prefs.node_lat = ((double)lat) / 1000000.0;
_prefs.node_lon = ((double)lon) / 1000000.0;
sensors.node_lat = ((double)lat) / 1000000.0;
sensors.node_lon = ((double)lon) / 1000000.0;
savePrefs();
writeOKFrame();
} else {
@@ -1020,7 +1136,7 @@ public:
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else if (cmd_frame[0] == CMD_SEND_SELF_ADVERT) {
auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
if (pkt) {
if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop)
sendFlood(pkt);
@@ -1094,8 +1210,10 @@ public:
} else if (cmd_frame[0] == CMD_EXPORT_CONTACT) {
if (len < 1 + PUB_KEY_SIZE) {
// export SELF
auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
if (pkt) {
pkt->header |= ROUTE_TYPE_FLOOD; // would normally be sent in this mode
out_frame[0] = RESP_CODE_EXPORT_CONTACT;
uint8_t out_len = pkt->writeTo(&out_frame[1]);
releasePacket(pkt); // undo the obtainNewPacket()
@@ -1173,6 +1291,14 @@ public:
_prefs.airtime_factor = ((float)af) / 1000.0f;
savePrefs();
writeOKFrame();
} else if (cmd_frame[0] == CMD_SET_OTHER_PARAMS) {
_prefs.manual_add_contacts = cmd_frame[1];
if (len >= 3) {
_prefs.telemetry_mode_base = cmd_frame[2] & 0x03; // v5+
_prefs.telemetry_mode_loc = (cmd_frame[2] >> 2) & 0x03;
}
savePrefs();
writeOKFrame();
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
board.reboot();
} else if (cmd_frame[0] == CMD_GET_BATTERY_VOLTAGE) {
@@ -1229,7 +1355,7 @@ public:
if (result == MSG_SEND_FAILED) {
writeErrFrame(ERR_CODE_TABLE_FULL);
} else {
pending_status = 0;
pending_telemetry = pending_status = 0;
memcpy(&pending_login, recipient->id.pub_key, 4); // match this to onContactResponse()
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
@@ -1244,16 +1370,37 @@ public:
uint8_t* pub_key = &cmd_frame[1];
ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (recipient) {
uint32_t est_timeout;
int result = sendStatusRequest(*recipient, est_timeout);
uint32_t tag, est_timeout;
int result = sendRequest(*recipient, REQ_TYPE_GET_STATUS, tag, est_timeout);
if (result == MSG_SEND_FAILED) {
writeErrFrame(ERR_CODE_TABLE_FULL);
} else {
pending_login = 0;
memcpy(&pending_status, recipient->id.pub_key, 4); // match this to onContactResponse()
pending_telemetry = pending_login = 0;
// FUTURE: pending_status = tag; // match this in onContactResponse()
memcpy(&pending_status, recipient->id.pub_key, 4); // legacy matching scheme
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
memcpy(&out_frame[2], &pending_status, 4);
memcpy(&out_frame[2], &tag, 4);
memcpy(&out_frame[6], &est_timeout, 4);
_serial->writeFrame(out_frame, 10);
}
} else {
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
}
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4+PUB_KEY_SIZE) {
uint8_t* pub_key = &cmd_frame[4];
ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (recipient) {
uint32_t tag, est_timeout;
int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout);
if (result == MSG_SEND_FAILED) {
writeErrFrame(ERR_CODE_TABLE_FULL);
} else {
pending_status = pending_login = 0;
pending_telemetry = tag; // match this in onContactResponse()
out_frame[0] = RESP_CODE_SENT;
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
memcpy(&out_frame[2], &tag, 4);
memcpy(&out_frame[6], &est_timeout, 4);
_serial->writeFrame(out_frame, 10);
}
@@ -1351,9 +1498,45 @@ public:
writeErrFrame(ERR_CODE_TABLE_FULL);
}
} else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) {
memcpy(&_prefs.ble_pin, &cmd_frame[1], 4);
savePrefs();
writeOKFrame();
// get pin from command frame
uint32_t pin;
memcpy(&pin, &cmd_frame[1], 4);
// ensure pin is zero, or a valid 6 digit pin
if(pin == 0 || (pin >= 100000 && pin <= 999999)){
_prefs.ble_pin = pin;
savePrefs();
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) {
out_frame[0] = RESP_CODE_CUSTOM_VARS;
char* dp = (char *) &out_frame[1];
for (int i = 0; i < sensors.getNumSettings() && dp - (char *) &out_frame[1] < 140; i++) {
if (i > 0) { *dp++ = ','; }
strcpy(dp, sensors.getSettingName(i)); dp = strchr(dp, 0);
*dp++ = ':';
strcpy(dp, sensors.getSettingValue(i)); dp = strchr(dp, 0);
}
_serial->writeFrame(out_frame, dp - (char *)out_frame);
} else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) {
cmd_frame[len] = 0;
char* sp = (char *) &cmd_frame[1];
char* np = strchr(sp, ':'); // look for separator char
if (np) {
*np++ = 0; // modify 'cmd_frame', replace ':' with null
bool success = sensors.setSettingValue(sp, np);
if (success) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
@@ -1404,6 +1587,28 @@ public:
#elif defined(BLE_PIN_CODE)
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#elif defined(SERIAL_RX)
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
HardwareSerial companion_serial(1);
#else
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
#endif
#elif defined(RP2040_PLATFORM)
//#ifdef WIFI_SSID
// #include <helpers/rp2040/SerialWifiInterface.h>
// SerialWifiInterface serial_interface;
// #ifndef TCP_PORT
// #define TCP_PORT 5000
// #endif
// #elif defined(BLE_PIN_CODE)
// #include <helpers/rp2040/SerialBLEInterface.h>
// SerialBLEInterface serial_interface;
#if defined(SERIAL_RX)
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
HardwareSerial companion_serial(1);
#else
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
@@ -1433,19 +1638,22 @@ void setup() {
board.begin();
if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed());
#ifdef HAS_UI
DisplayDriver* disp = NULL;
#ifdef DISPLAY_CLASS
if (display.begin()) {
disp = &display;
disp->startFrame();
disp->print("Please wait...");
disp->endFrame();
}
#endif
#endif
if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed());
#if defined(NRF52_PLATFORM)
InternalFS.begin();
the_mesh.begin(InternalFS,
@@ -1464,6 +1672,31 @@ void setup() {
serial_interface.begin(Serial);
#endif
the_mesh.startInterface(serial_interface);
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
the_mesh.begin(LittleFS,
#ifdef HAS_UI
disp != NULL
#else
false
#endif
);
//#ifdef WIFI_SSID
// WiFi.begin(WIFI_SSID, WIFI_PWD);
// serial_interface.begin(TCP_PORT);
// #elif defined(BLE_PIN_CODE)
// char dev_name[32+16];
// sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
// serial_interface.begin(dev_name, the_mesh.getBLEPin());
#if defined(SERIAL_RX)
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
companion_serial.begin(115200);
serial_interface.begin(companion_serial);
#else
serial_interface.begin(Serial);
#endif
the_mesh.startInterface(serial_interface);
#elif defined(ESP32)
SPIFFS.begin(true);
the_mesh.begin(SPIFFS,
@@ -1481,6 +1714,10 @@ void setup() {
char dev_name[32+16];
sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
serial_interface.begin(dev_name, the_mesh.getBLEPin());
#elif defined(SERIAL_RX)
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
companion_serial.begin(115200);
serial_interface.begin(companion_serial);
#else
serial_interface.begin(Serial);
#endif
@@ -1489,11 +1726,14 @@ void setup() {
#error "need to define filesystem"
#endif
sensors.begin();
#ifdef HAS_UI
ui_task.begin(disp, the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, the_mesh.getBLEPin());
ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin());
#endif
}
void loop() {
the_mesh.loop();
sensors.loop();
}

View File

@@ -1,7 +1,9 @@
#include "UITask.h"
#include <Arduino.h>
#include <helpers/CommonCLI.h>
#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
// 'meshcore', 128x13px
static const uint8_t meshcore_logo [] PROGMEM = {
@@ -20,29 +22,62 @@ static const uint8_t meshcore_logo [] PROGMEM = {
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
};
void UITask::begin(const char* node_name, const char* build_date) {
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
_prevBtnState = HIGH;
_auto_off = millis() + AUTO_OFF_MILLIS;
_node_name = node_name;
_build_date = build_date;
_node_prefs = node_prefs;
_display->turnOn();
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(firmware_version);
char *dash = strchr(version, '-');
if(dash){
*dash = 0;
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
}
void UITask::renderCurrScreen() {
char tmp[80];
// render 'home' screen
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
_display->setCursor(0, 20);
_display->setTextSize(1);
_display->print(_node_name);
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
sprintf(tmp, "Build: %s", _build_date);
_display->setCursor(0, 32);
_display->print(tmp);
_display->setCursor(0, 43);
_display->print("< Repeater >");
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 22);
_display->print(_version_info);
// node type
const char* node_type = "< Repeater >";
uint16_t typeWidth = _display->getTextWidth(node_type);
_display->setCursor((_display->width() - typeWidth) / 2, 35);
_display->print(node_type);
} else { // home screen
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
// bw / cr
_display->setCursor(0, 30);
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
_display->print(tmp);
}
}
void UITask::loop() {

View File

@@ -1,18 +1,19 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
#include <helpers/CommonCLI.h>
class UITask {
DisplayDriver* _display;
unsigned long _next_read, _next_refresh, _auto_off;
int _prevBtnState;
const char* _node_name;
const char* _build_date;
NodePrefs* _node_prefs;
char _version_info[32];
void renderCurrScreen();
public:
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
void begin(const char* node_name, const char* build_date);
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop();
};

View File

@@ -3,6 +3,8 @@
#if defined(NRF52_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#endif
@@ -20,11 +22,11 @@
/* ------------------------------ Config -------------------------------- */
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
#define FIRMWARE_BUILD_DATE "9 May 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.4.2"
#define FIRMWARE_VERSION "v1.6.0"
#endif
#ifndef LORA_FREQ
@@ -72,7 +74,9 @@
/* ------------------------------ Code -------------------------------- */
#define CMD_GET_STATUS 0x01
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
@@ -103,6 +107,13 @@ struct ClientInfo {
#define MAX_CLIENTS 4
struct NeighbourInfo {
mesh::Identity id;
uint32_t advert_timestamp;
uint32_t heard_timestamp;
int8_t snr; // multiplied by 4, user should divide to get float value
};
// NOTE: need to space the ACK and the reply text apart (in CLI)
#define CLI_REPLY_DELAY_MILLIS 1500
@@ -114,6 +125,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
CommonCLI _cli;
uint8_t reply_data[MAX_PACKET_PAYLOAD];
ClientInfo known_clients[MAX_CLIENTS];
#if MAX_NEIGHBOURS
NeighbourInfo neighbours[MAX_NEIGHBOURS];
#endif
CayenneLPP telemetry;
ClientInfo* putClient(const mesh::Identity& id) {
uint32_t min_time = 0xFFFFFFFF;
@@ -133,12 +148,40 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
return oldest;
}
int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) {
uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) {
#if MAX_NEIGHBOURS // check if neighbours enabled
// find existing neighbour, else use least recently updated
uint32_t oldest_timestamp = 0xFFFFFFFF;
NeighbourInfo* neighbour = &neighbours[0];
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
// if neighbour already known, we should update it
if (id.matches(neighbours[i].id)) {
neighbour = &neighbours[i];
break;
}
// otherwise we should update the least recently updated neighbour
if (neighbours[i].heard_timestamp < oldest_timestamp) {
neighbour = &neighbours[i];
oldest_timestamp = neighbour->heard_timestamp;
}
}
// update neighbour info
neighbour->id = id;
neighbour->advert_timestamp = timestamp;
neighbour->heard_timestamp = getRTCClock()->getCurrentTime();
neighbour->snr = (int8_t) (snr * 4);
#endif
}
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) {
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
switch (payload[0]) {
case CMD_GET_STATUS: { // guests can also access this now
case REQ_TYPE_GET_STATUS: { // guests can also access this now
RepeaterStats stats;
stats.batt_milli_volts = board.getBattMilliVolts();
stats.curr_tx_queue_len = _mgr->getOutboundCount();
@@ -161,9 +204,18 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
return 4 + sizeof(stats); // reply_len
}
case REQ_TYPE_GET_TELEMETRY_DATA: {
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
// query other sensors -- target specific
sensors.querySensors(sender->is_admin ? 0xFF : 0x00, telemetry);
uint8_t tlen = telemetry.getSize();
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
return 4 + tlen; // reply_len
}
}
// unknown command
return 0; // reply_len
return 0; // unknown command
}
mesh::Packet* createSelfAdvert() {
@@ -180,6 +232,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
File openAppend(const char* fname) {
#if defined(NRF52_PLATFORM)
return _fs->open(fname, FILE_O_WRITE);
#elif defined(RP2040_PLATFORM)
return _fs->open(fname, "a");
#else
return _fs->open(fname, "a", true);
#endif
@@ -357,6 +411,18 @@ protected:
}
}
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) {
mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl
// if this a zero hop advert, add it to neighbours
if (packet->path_len == 0) {
AdvertDataParser parser(app_data, app_data_len);
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
putNeighbour(id, timestamp, packet->getSNR());
}
}
}
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override {
int i = matching_peer_indexes[sender_idx];
if (i < 0 || i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context)
@@ -369,7 +435,7 @@ protected:
memcpy(&timestamp, data, 4);
if (timestamp > client->last_timestamp) { // prevent replay attacks
int reply_len = handleRequest(client, &data[4], len - 4);
int reply_len = handleRequest(client, timestamp, &data[4], len - 4);
if (reply_len == 0) return; // invalid command
client->last_timestamp = timestamp;
@@ -423,12 +489,14 @@ protected:
}
uint8_t temp[166];
const char *command = (const char *) &data[5];
char *reply = (char *) &temp[5];
if (is_retry) {
temp[0] = 0;
*reply = 0;
} else {
_cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]);
_cli.handleCommand(sender_timestamp, command, reply);
}
int text_len = strlen((char *) &temp[5]);
int text_len = strlen(reply);
if (text_len > 0) {
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
if (timestamp == sender_timestamp) {
@@ -472,12 +540,16 @@ protected:
public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, this, &_prefs, this)
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
{
memset(known_clients, 0, sizeof(known_clients));
next_local_advert = next_flood_advert = 0;
_logging = false;
#if MAX_NEIGHBOURS
memset(neighbours, 0, sizeof(neighbours));
#endif
// defaults
memset(&_prefs, 0, sizeof(_prefs));
_prefs.airtime_factor = 1.0; // one half
@@ -516,6 +588,9 @@ public:
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
const char* getRole() override { return FIRMWARE_ROLE; }
const char* getNodeName() { return _prefs.node_name; }
NodePrefs* getNodePrefs() {
return &_prefs;
}
void savePrefs() override {
_cli.savePrefs(_fs);
@@ -524,10 +599,12 @@ public:
bool formatFileSystem() override {
#if defined(NRF52_PLATFORM)
return InternalFS.format();
#elif defined(RP2040_PLATFORM)
return LittleFS.format();
#elif defined(ESP32)
return SPIFFS.format();
#else
#error "need to implement file system erase"
#error "need to implement file system erase"
return false;
#endif
}
@@ -563,7 +640,11 @@ public:
}
void dumpLogFile() override {
#if defined(RP2040_PLATFORM)
File f = _fs->open(PACKET_LOG_FILE, "r");
#else
File f = _fs->open(PACKET_LOG_FILE);
#endif
if (f) {
while (f.available()) {
int c = f.read();
@@ -578,6 +659,34 @@ public:
radio_set_tx_power(power_dbm);
}
void formatNeighborsReply(char *reply) override {
char *dp = reply;
#if MAX_NEIGHBOURS
for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) {
NeighbourInfo* neighbour = &neighbours[i];
if (neighbour->heard_timestamp == 0) continue; // skip empty slots
// add new line if not first item
if (i > 0) *dp++ = '\n';
char hex[10];
// get 4 bytes of neighbour id as hex
mesh::Utils::toHex(hex, neighbour->id.pub_key, 4);
// add next neighbour
sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr);
while (*dp) dp++; // find end of string
}
#endif
if (dp == reply) { // no neighbours, need empty response
strcpy(dp, "-none-"); dp += 6;
}
*dp = 0; // null terminator
}
const uint8_t* getSelfIdPubKey() { return self_id.pub_key; }
void loop() {
mesh::Mesh::loop();
@@ -616,6 +725,14 @@ void setup() {
board.begin();
#ifdef DISPLAY_CLASS
if(display.begin()){
display.startFrame();
display.print("Please wait...");
display.endFrame();
}
#endif
if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed());
@@ -629,12 +746,21 @@ void setup() {
SPIFFS.begin(true);
fs = &SPIFFS;
IdentityStore store(SPIFFS, "/identity");
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
fs = &LittleFS;
IdentityStore store(LittleFS, "/identity");
store.begin();
#else
#error "need to define filesystem"
#endif
if (!store.load("_main", the_mesh.self_id)) {
MESH_DEBUG_PRINTLN("Generating new keypair");
the_mesh.self_id = radio_new_identity(); // create new random identity
int count = 0;
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
the_mesh.self_id = radio_new_identity(); count++;
}
store.save("_main", the_mesh.self_id);
}
@@ -643,11 +769,12 @@ void setup() {
command[0] = 0;
sensors.begin();
the_mesh.begin(fs);
#ifdef DISPLAY_CLASS
display.begin();
ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE);
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
// send out initial Advertisement to the mesh
@@ -680,4 +807,5 @@ void loop() {
}
the_mesh.loop();
sensors.loop();
}

View File

@@ -1,7 +1,9 @@
#include "UITask.h"
#include <Arduino.h>
#include <helpers/CommonCLI.h>
#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define AUTO_OFF_MILLIS 20000 // 20 seconds
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
// 'meshcore', 128x13px
static const uint8_t meshcore_logo [] PROGMEM = {
@@ -20,29 +22,62 @@ static const uint8_t meshcore_logo [] PROGMEM = {
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
};
void UITask::begin(const char* node_name, const char* build_date) {
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
_prevBtnState = HIGH;
_auto_off = millis() + AUTO_OFF_MILLIS;
_node_name = node_name;
_build_date = build_date;
_node_prefs = node_prefs;
_display->turnOn();
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(firmware_version);
char *dash = strchr(version, '-');
if(dash){
*dash = 0;
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
}
void UITask::renderCurrScreen() {
char tmp[80];
// render 'home' screen
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
_display->setCursor(0, 20);
_display->setTextSize(1);
_display->print(_node_name);
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
sprintf(tmp, "Build: %s", _build_date);
_display->setCursor(0, 32);
_display->print(tmp);
_display->setCursor(0, 43);
_display->print("< Room Server >");
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - versionWidth) / 2, 22);
_display->print(_version_info);
// node type
const char* node_type = "< Room Server >";
uint16_t typeWidth = _display->getTextWidth(node_type);
_display->setCursor((_display->width() - typeWidth) / 2, 35);
_display->print(node_type);
} else { // home screen
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
// bw / cr
_display->setCursor(0, 30);
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
_display->print(tmp);
}
}
void UITask::loop() {

View File

@@ -1,18 +1,19 @@
#pragma once
#include <helpers/ui/DisplayDriver.h>
#include <helpers/CommonCLI.h>
class UITask {
DisplayDriver* _display;
unsigned long _next_read, _next_refresh, _auto_off;
int _prevBtnState;
const char* _node_name;
const char* _build_date;
NodePrefs* _node_prefs;
char _version_info[32];
void renderCurrScreen();
public:
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
void begin(const char* node_name, const char* build_date);
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
void loop();
};

View File

@@ -3,6 +3,8 @@
#if defined(NRF52_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#endif
@@ -20,11 +22,11 @@
/* ------------------------------ Config -------------------------------- */
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
#define FIRMWARE_BUILD_DATE "9 May 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.4.2"
#define FIRMWARE_VERSION "v1.6.0"
#endif
#ifndef LORA_FREQ
@@ -62,7 +64,7 @@
#endif
#ifndef MAX_UNSYNCED_POSTS
#define MAX_UNSYNCED_POSTS 16
#define MAX_UNSYNCED_POSTS 32
#endif
#ifdef DISPLAY_CLASS
@@ -111,7 +113,7 @@ struct PostInfo {
#define REPLY_DELAY_MILLIS 1500
#define PUSH_NOTIFY_DELAY_MILLIS 2000
#define SYNC_PUSH_INTERVAL 2000
#define SYNC_PUSH_INTERVAL 1200
#define PUSH_ACK_TIMEOUT_FLOOD 12000
#define PUSH_TIMEOUT_BASE 4000
@@ -119,8 +121,9 @@ struct PostInfo {
#define CLIENT_KEEP_ALIVE_SECS 128
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
@@ -155,6 +158,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
int next_client_idx; // for round-robin polling
int next_post_idx;
PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue
CayenneLPP telemetry;
ClientInfo* putClient(const mesh::Identity& id) {
for (int i = 0; i < num_clients; i++) {
@@ -232,6 +236,17 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
}
}
uint8_t getUnsyncedCount(ClientInfo* client) {
uint8_t count = 0;
for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) {
if (posts[k].post_timestamp > client->sync_since // is new post for this Client?
&& !posts[k].author.matches(client->id)) { // don't push posts to the author
count++;
}
}
return count;
}
bool processAck(const uint8_t *data) {
for (int i = 0; i < num_clients; i++) {
auto client = &known_clients[i];
@@ -259,11 +274,58 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
File openAppend(const char* fname) {
#if defined(NRF52_PLATFORM)
return _fs->open(fname, FILE_O_WRITE);
#elif defined(RP2040_PLATFORM)
return _fs->open(fname, "a");
#else
return _fs->open(fname, "a", true);
#endif
}
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) {
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
switch (payload[0]) {
case REQ_TYPE_GET_STATUS: {
ServerStats stats;
stats.batt_milli_volts = board.getBattMilliVolts();
stats.curr_tx_queue_len = _mgr->getOutboundCount();
stats.curr_free_queue_len = _mgr->getFreeCount();
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
stats.n_packets_recv = radio_driver.getPacketsRecv();
stats.n_packets_sent = radio_driver.getPacketsSent();
stats.total_air_time_secs = getTotalAirTime() / 1000;
stats.total_up_time_secs = _ms->getMillis() / 1000;
stats.n_sent_flood = getNumSentFlood();
stats.n_sent_direct = getNumSentDirect();
stats.n_recv_flood = getNumRecvFlood();
stats.n_recv_direct = getNumRecvDirect();
stats.n_full_events = getNumFullEvents();
stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4);
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
stats.n_posted = _num_posted;
stats.n_post_push = _num_post_pushes;
memcpy(&reply_data[4], &stats, sizeof(stats));
return 4 + sizeof(stats);
}
case REQ_TYPE_GET_TELEMETRY_DATA: {
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
// query other sensors -- target specific
sensors.querySensors(sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00, telemetry);
uint8_t tlen = telemetry.getSize();
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
return 4 + tlen; // reply_len
}
}
return 0; // unknown command
}
protected:
float getAirtimeBudgetFactor() const override {
return _prefs.airtime_factor;
@@ -398,7 +460,7 @@ protected:
reply_data[4] = RESP_SERVER_LOGIN_OK;
reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16)
reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2));
reply_data[7] = 0; // FUTURE: reserved
reply_data[7] = getUnsyncedCount(client); // NEW
memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first
@@ -572,47 +634,26 @@ protected:
auto reply = createAck(ack_hash);
if (reply) {
reply->payload[reply->payload_len++] = getUnsyncedCount(client); // NEW: add unsynced counter to end of ACK packet
sendDirect(reply, client->out_path, client->out_path_len);
}
}
} else if (data[4] == REQ_TYPE_GET_STATUS) {
ServerStats stats;
stats.batt_milli_volts = board.getBattMilliVolts();
stats.curr_tx_queue_len = _mgr->getOutboundCount();
stats.curr_free_queue_len = _mgr->getFreeCount();
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
stats.n_packets_recv = radio_driver.getPacketsRecv();
stats.n_packets_sent = radio_driver.getPacketsSent();
stats.total_air_time_secs = getTotalAirTime() / 1000;
stats.total_up_time_secs = _ms->getMillis() / 1000;
stats.n_sent_flood = getNumSentFlood();
stats.n_sent_direct = getNumSentDirect();
stats.n_recv_flood = getNumRecvFlood();
stats.n_recv_direct = getNumRecvDirect();
stats.n_full_events = getNumFullEvents();
stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4);
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
stats.n_posted = _num_posted;
stats.n_post_push = _num_post_pushes;
now = getRTCClock()->getCurrentTimeUnique();
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
memcpy(&reply_data[4], &stats, sizeof(stats));
uint8_t reply_len = 4 + sizeof(stats);
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len);
} else {
sendFlood(reply);
} else {
int reply_len = handleRequest(client, sender_timestamp, &data[4], len - 4);
if (reply_len > 0) { // valid command
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
if (reply) {
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, client->out_path, client->out_path_len);
} else {
sendFlood(reply);
}
}
}
}
@@ -651,7 +692,7 @@ protected:
public:
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
_cli(board, this, &_prefs, this)
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
{
next_local_advert = next_flood_advert = 0;
_logging = false;
@@ -705,6 +746,9 @@ public:
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
const char* getRole() override { return FIRMWARE_ROLE; }
const char* getNodeName() { return _prefs.node_name; }
NodePrefs* getNodePrefs() {
return &_prefs;
}
void savePrefs() override {
_cli.savePrefs(_fs);
@@ -713,6 +757,8 @@ public:
bool formatFileSystem() override {
#if defined(NRF52_PLATFORM)
return InternalFS.format();
#elif defined(RP2040_PLATFORM)
return LittleFS.format();
#elif defined(ESP32)
return SPIFFS.format();
#else
@@ -752,7 +798,11 @@ public:
}
void dumpLogFile() override {
#if defined(RP2040_PLATFORM)
File f = _fs->open(PACKET_LOG_FILE, "r");
#else
File f = _fs->open(PACKET_LOG_FILE);
#endif
if (f) {
while (f.available()) {
int c = f.read();
@@ -767,6 +817,12 @@ public:
radio_set_tx_power(power_dbm);
}
void formatNeighborsReply(char *reply) override {
strcpy(reply, "not supported");
}
const uint8_t* getSelfIdPubKey() { return self_id.pub_key; }
void loop() {
mesh::Mesh::loop();
@@ -782,6 +838,7 @@ public:
}
// check next Round-Robin client, and sync next new post
auto client = &known_clients[next_client_idx];
bool did_push = false;
if (client->pending_ack == 0 && client->last_activity != 0 && client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max
MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t) client->id.pub_key[0]);
for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) {
@@ -789,6 +846,7 @@ public:
&& !posts[idx].author.matches(client->id)) { // don't push posts to the author
// push this post to Client, then wait for ACK
pushPostToClient(client, posts[idx]);
did_push = true;
MESH_DEBUG_PRINTLN("loop - pushed to client %02X: %s", (uint32_t) client->id.pub_key[0], posts[idx].text);
break;
}
@@ -799,7 +857,12 @@ public:
}
next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client
next_push = futureMillis(SYNC_PUSH_INTERVAL);
if (did_push) {
next_push = futureMillis(SYNC_PUSH_INTERVAL);
} else {
// were no unsynced posts for curr client, so proccess next client much quicker! (in next loop())
next_push = futureMillis(SYNC_PUSH_INTERVAL / 8);
}
}
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
@@ -839,6 +902,14 @@ void setup() {
board.begin();
#ifdef DISPLAY_CLASS
if(display.begin()){
display.startFrame();
display.print("Please wait...");
display.endFrame();
}
#endif
if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed());
@@ -848,6 +919,11 @@ void setup() {
InternalFS.begin();
fs = &InternalFS;
IdentityStore store(InternalFS, "");
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
fs = &LittleFS;
IdentityStore store(LittleFS, "/identity");
store.begin();
#elif defined(ESP32)
SPIFFS.begin(true);
fs = &SPIFFS;
@@ -857,6 +933,10 @@ void setup() {
#endif
if (!store.load("_main", the_mesh.self_id)) {
the_mesh.self_id = radio_new_identity(); // create new random identity
int count = 0;
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
the_mesh.self_id = radio_new_identity(); count++;
}
store.save("_main", the_mesh.self_id);
}
@@ -865,11 +945,12 @@ void setup() {
command[0] = 0;
sensors.begin();
the_mesh.begin(fs);
#ifdef DISPLAY_CLASS
display.begin();
ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE);
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
#endif
// send out initial Advertisement to the mesh
@@ -902,4 +983,5 @@ void loop() {
}
the_mesh.loop();
sensors.loop();
}

View File

@@ -3,6 +3,8 @@
#if defined(NRF52_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#endif
@@ -88,7 +90,11 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
void loadContacts() {
if (_fs->exists("/contacts")) {
#if defined(RP2040_PLATFORM)
File file = _fs->open("/contacts", "r");
#else
File file = _fs->open("/contacts");
#endif
if (file) {
bool full = false;
while (!full) {
@@ -123,6 +129,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
#if defined(NRF52_PLATFORM)
File file = _fs->open("/contacts", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open("/contacts", "w");
#else
File file = _fs->open("/contacts", "w", true);
#endif
@@ -246,6 +254,10 @@ protected:
Serial.printf(" %s\n", text);
}
uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override {
return 0; // unknown
}
void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
// not supported
}
@@ -287,6 +299,9 @@ public:
#if defined(NRF52_PLATFORM)
IdentityStore store(fs, "");
#elif defined(RP2040_PLATFORM)
IdentityStore store(fs, "/identity");
store.begin();
#else
IdentityStore store(fs, "/identity");
#endif
@@ -300,12 +315,20 @@ public:
((StdRNG *)getRNG())->begin(millis());
self_id = mesh::LocalIdentity(getRNG()); // create new random identity
int count = 0;
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
self_id = mesh::LocalIdentity(getRNG()); count++;
}
store.save("_main", self_id);
}
// load persisted prefs
if (_fs->exists("/node_prefs")) {
#if defined(RP2040_PLATFORM)
File file = _fs->open("/node_prefs", "r");
#else
File file = _fs->open("/node_prefs");
#endif
if (file) {
file.read((uint8_t *) &_prefs, sizeof(_prefs));
file.close();
@@ -320,6 +343,8 @@ public:
#if defined(NRF52_PLATFORM)
File file = _fs->open("/node_prefs", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open("/node_prefs", "w");
#else
File file = _fs->open("/node_prefs", "w", true);
#endif
@@ -541,6 +566,9 @@ void setup() {
#if defined(NRF52_PLATFORM)
InternalFS.begin();
the_mesh.begin(InternalFS);
#elif defined(RP2040_PLATFORM)
LittleFS.begin();
the_mesh.begin(LittleFS);
#elif defined(ESP32)
SPIFFS.begin(true);
the_mesh.begin(SPIFFS);

View File

@@ -22,10 +22,11 @@ lib_deps =
rweather/Crypto @ ^0.4.0
adafruit/RTClib @ ^2.1.3
melopero/Melopero RV3028 @ ^1.1.0
electroniccats/CayenneLPP @ 1.4.0
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
-D LORA_FREQ=869.525
-D LORA_BW=250
-D LORA_SF=10
-D LORA_SF=11
build_src_filter =
+<*.cpp>
+<helpers/*.cpp>
@@ -47,6 +48,7 @@ lib_deps =
file://arch/esp32/AsyncElegantOTA
; ----------------- NRF52 ---------------------
[nrf52_base]
extends = arduino_base
platform = nordicnrf52
@@ -60,3 +62,10 @@ lib_deps =
${nrf52_base.lib_deps}
rweather/Crypto @ ^0.4.0
https://github.com/adafruit/Adafruit_nRF52_Arduino
; ----------------- RP2040 ---------------------
[rp2040_base]
extends = arduino_base
build_flags = ${arduino_base.build_flags}
-D RP2040_PLATFORM

View File

@@ -101,6 +101,12 @@ void Dispatcher::checkRecv() {
#endif
pkt->header = raw[i++];
if (pkt->hasTransportCodes()) {
memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2;
memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2;
} else {
pkt->transport_codes[0] = pkt->transport_codes[1] = 0;
}
pkt->path_len = raw[i++];
if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) {
@@ -132,7 +138,7 @@ void Dispatcher::checkRecv() {
#if MESH_PACKET_LOGGING
Serial.print(getLogDateTime());
Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d",
2 + pkt->path_len + pkt->payload_len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
(int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000));
static uint8_t packet_hash[MAX_HASH_SIZE];
@@ -147,7 +153,7 @@ void Dispatcher::checkRecv() {
Serial.printf("\n");
}
#endif
logRx(pkt, 2 + pkt->path_len + pkt->payload_len, score); // hook for custom logging
logRx(pkt, pkt->getRawLength(), score); // hook for custom logging
if (pkt->isRouteFlood()) {
n_recv_flood++;
@@ -212,6 +218,10 @@ void Dispatcher::checkSend() {
raw[len++] = NODE_ID;
#endif
raw[len++] = outbound->header;
if (outbound->hasTransportCodes()) {
memcpy(&raw[len], &outbound->transport_codes[0], 2); len += 2;
memcpy(&raw[len], &outbound->transport_codes[1], 2); len += 2;
}
raw[len++] = outbound->path_len;
memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len;

View File

@@ -15,7 +15,7 @@ bool Mesh::allowPacketForward(const mesh::Packet* packet) {
return false; // by default, Transport NOT enabled
}
uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) {
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 52 / 50) / 2;
uint32_t t = (_radio->getEstAirtimeFor(packet->getRawLength()) * 52 / 50) / 2;
return _rng->nextInt(0, 5)*t;
}

View File

@@ -10,6 +10,10 @@ Packet::Packet() {
payload_len = 0;
}
int Packet::getRawLength() const {
return 2 + path_len + payload_len + (hasTransportCodes() ? 4 : 0);
}
void Packet::calculatePacketHash(uint8_t* hash) const {
SHA256 sha;
uint8_t t = getPayloadType();
@@ -24,6 +28,10 @@ void Packet::calculatePacketHash(uint8_t* hash) const {
uint8_t Packet::writeTo(uint8_t dest[]) const {
uint8_t i = 0;
dest[i++] = header;
if (hasTransportCodes()) {
memcpy(&dest[i], &transport_codes[0], 2); i += 2;
memcpy(&dest[i], &transport_codes[1], 2); i += 2;
}
dest[i++] = path_len;
memcpy(&dest[i], path, path_len); i += path_len;
memcpy(&dest[i], payload, payload_len); i += payload_len;
@@ -33,11 +41,18 @@ uint8_t Packet::writeTo(uint8_t dest[]) const {
bool Packet::readFrom(const uint8_t src[], uint8_t len) {
uint8_t i = 0;
header = src[i++];
if (hasTransportCodes()) {
memcpy(&transport_codes[0], &src[i], 2); i += 2;
memcpy(&transport_codes[1], &src[i], 2); i += 2;
} else {
transport_codes[0] = transport_codes[1] = 0;
}
path_len = src[i++];
if (path_len > sizeof(path)) return false; // bad encoding
memcpy(path, &src[i], path_len); i += path_len;
if (i >= len) return false; // bad encoding
payload_len = len - i;
if (payload_len > sizeof(payload)) return false; // bad encoding
memcpy(payload, &src[i], payload_len); //i += payload_len;
return true; // success
}

View File

@@ -11,10 +11,10 @@ namespace mesh {
#define PH_VER_SHIFT 6
#define PH_VER_MASK 0x03 // 2-bits
#define ROUTE_TYPE_RESERVED1 0x00 // FUTURE
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
#define ROUTE_TYPE_RESERVED2 0x03 // FUTURE
#define ROUTE_TYPE_TRANSPORT_FLOOD 0x00 // flood mode + transport codes
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
#define ROUTE_TYPE_TRANSPORT_DIRECT 0x03 // direct route + transport codes
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
@@ -43,6 +43,7 @@ public:
uint8_t header;
uint16_t payload_len, path_len;
uint16_t transport_codes[2];
uint8_t path[MAX_PATH_SIZE];
uint8_t payload[MAX_PACKET_PAYLOAD];
int8_t _snr;
@@ -58,8 +59,10 @@ public:
*/
uint8_t getRouteType() const { return header & PH_ROUTE_MASK; }
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD; }
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT; }
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; }
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
bool hasTransportCodes() const { return getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
/**
* \returns one of PAYLOAD_TYPE_ values
@@ -76,6 +79,11 @@ public:
float getSNR() const { return ((float)_snr) / 4.0f; }
/**
* \returns the encoded/wire format length of this packet
*/
int getRawLength() const;
/**
* \brief save entire packet as a blob
* \param dest (OUT) destination buffer (assumed to be MAX_MTU_SIZE)

View File

@@ -8,30 +8,18 @@ class ArduinoSerialInterface : public BaseSerialInterface {
uint8_t _state;
uint16_t _frame_len;
uint16_t rx_len;
#ifdef LILYGO_T3S3
HWCDC* _serial;
#elif defined(NRF52_PLATFORM)
Adafruit_USBD_CDC* _serial;
#else
HardwareSerial* _serial;
#endif
Stream* _serial;
uint8_t rx_buf[MAX_FRAME_SIZE];
public:
ArduinoSerialInterface() { _isEnabled = false; _state = 0; }
#ifdef LILYGO_T3S3
void begin(HWCDC& serial) { _serial = &serial; }
#elif defined(NRF52_PLATFORM)
void begin(Adafruit_USBD_CDC& serial) {
_serial = &serial;
void begin(Stream& serial) {
_serial = &serial;
#ifdef RAK_4631
pinMode(WB_IO2, OUTPUT);
#endif
#endif
}
#else
void begin(HardwareSerial& serial) { _serial = &serial; }
#endif
// BaseSerialInterface methods
void enable() override;

View File

@@ -8,8 +8,12 @@ static bool ds3231_success = false;
static Melopero_RV3028 rtc_rv3028;
static bool rv3028_success = false;
static RTC_PCF8563 rtc_8563;
static bool rtc_8563_success = false;
#define DS3231_ADDRESS 0x68
#define RV3028_ADDRESS 0x52
#define PCF8563_ADDRESS 0x51
bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
wire.beginTransmission(addr);
@@ -28,6 +32,9 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
rv3028_success = true;
}
if(i2c_probe(wire,PCF8563_ADDRESS)){
rtc_8563_success = rtc_8563.begin(&wire);
}
}
uint32_t AutoDiscoverRTCClock::getCurrentTime() {
@@ -44,6 +51,9 @@ uint32_t AutoDiscoverRTCClock::getCurrentTime() {
rtc_rv3028.getSecond()
).unixtime();
}
if(rtc_8563_success){
return rtc_8563.now().unixtime();
}
return _fallback->getCurrentTime();
}
@@ -52,9 +62,10 @@ void AutoDiscoverRTCClock::setCurrentTime(uint32_t time) {
rtc_3231.adjust(DateTime(time));
} else if (rv3028_success) {
auto dt = DateTime(time);
uint8_t weekday = (dt.day() + (uint16_t)((2.6 * dt.month()) - 0.2) - (2 * (dt.year() / 100)) + dt.year() + (uint16_t)(dt.year() / 4) + (uint16_t)(dt.year() / 400)) % 7;
uint8_t weekday = (dt.day() + (uint16_t)((2.6 * dt.month()) - 0.2) - (2 * (dt.year() / 100)) + dt.year() + (uint16_t)(dt.year() / 4) + (uint16_t)(dt.year() / 400)) % 7;
rtc_rv3028.setTime(dt.year(), dt.month(), weekday, dt.day(), dt.hour(), dt.minute(), dt.second());
} else if (rtc_8563_success) {
rtc_8563.adjust(DateTime(time));
} else {
_fallback->setCurrentTime(time);
}

View File

@@ -31,8 +31,29 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
}
}
// save a copy of raw advert packet (to support "Share..." function)
int plen = packet->writeTo(temp_buf);
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
bool is_new = false;
if (from == NULL) {
if (!isAutoAddEnabled()) {
ContactInfo ci;
memset(&ci, 0, sizeof(ci));
ci.id = id;
ci.out_path_len = -1; // initially out_path is unknown
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
ci.type = parser.getType();
if (parser.hasLatLon()) {
ci.gps_lat = parser.getIntLat();
ci.gps_lon = parser.getIntLon();
}
ci.last_advert_timestamp = timestamp;
ci.lastmod = getRTCClock()->getCurrentTime();
onDiscoveredContact(ci, true); // let UI know
return;
}
is_new = true;
if (num_contacts < MAX_CONTACTS) {
from = &contacts[num_contacts++];
@@ -50,10 +71,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
}
}
// save a copy of raw advert packet (to support "Share..." function)
int plen = packet->writeTo(temp_buf);
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
// update
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
from->type = parser.getType();
@@ -161,6 +178,27 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
} else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);
}
} else if (type == PAYLOAD_TYPE_REQ && len > 4) {
uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4);
uint8_t reply_len = onContactRequest(from, sender_timestamp, &data[4], len - 4, temp_buf);
if (reply_len > 0) {
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
if (reply) {
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, from.out_path, from.out_path_len);
} else {
sendFlood(reply);
}
}
}
}
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
onContactResponse(from, data, len);
}
@@ -252,7 +290,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
mesh::Packet* pkt = composeMsgPacket(recipient, timestamp, attempt, text, expected_ack);
if (pkt == NULL) return MSG_SEND_FAILED;
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
int rc;
if (recipient.out_path_len < 0) {
@@ -279,7 +317,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len);
if (pkt == NULL) return MSG_SEND_FAILED;
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
int rc;
if (recipient.out_path_len < 0) {
sendFlood(pkt);
@@ -362,7 +400,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
auto pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen);
if (pkt) {
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
if (recipient.out_path_len < 0) {
sendFlood(pkt);
est_timeout = calcFloodTimeoutMillisFor(t);
@@ -376,17 +414,17 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
return MSG_SEND_FAILED;
}
int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout) {
int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) {
uint8_t temp[13];
uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = REQ_TYPE_GET_STATUS;
tag = getRTCClock()->getCurrentTimeUnique();
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = req_type;
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp));
if (pkt) {
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
if (recipient.out_path_len < 0) {
sendFlood(pkt);
est_timeout = calcFloodTimeoutMillisFor(t);

View File

@@ -103,6 +103,7 @@ protected:
}
// 'UI' concepts, for sub-classes to implement
virtual bool isAutoAddEnabled() const { return true; }
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new) = 0;
virtual bool processAck(const uint8_t *data) = 0;
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
@@ -113,6 +114,7 @@ protected:
virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
virtual void onSendTimeout() = 0;
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
// storage concepts, for sub-classes to override/implement
@@ -145,7 +147,7 @@ public:
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout);
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
bool shareContactZeroHop(const ContactInfo& contact);
uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]);
bool importContact(const uint8_t src_buf[], uint8_t len);

View File

@@ -24,7 +24,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
}
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
#if defined(RP2040_PLATFORM)
File file = fs->open(filename, "r");
#else
File file = fs->open(filename);
#endif
if (file) {
uint8_t pad[8];
@@ -72,6 +76,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
#if defined(NRF52_PLATFORM)
File file = fs->open("/com_prefs", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = fs->open("/com_prefs", "w");
#else
File file = fs->open("/com_prefs", "w", true);
#endif
@@ -155,6 +161,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else {
strcpy(reply, "(ERR: clock cannot go backwards)");
}
} else if (memcmp(command, "neighbors", 9) == 0) {
_callbacks->formatNeighborsReply(reply);
} else if (memcmp(command, "password ", 9) == 0) {
// change admin password
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
@@ -200,7 +208,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
} else if (memcmp(config, "public.key", 10) == 0) {
strcpy(reply, "> ");
mesh::Utils::toHex(&reply[2], _mesh->self_id.pub_key, PUB_KEY_SIZE);
mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE);
} else if (memcmp(config, "role", 4) == 0) {
sprintf(reply, "> %s", _callbacks->getRole());
} else {

View File

@@ -40,16 +40,18 @@ public:
virtual void eraseLogFile() = 0;
virtual void dumpLogFile() = 0;
virtual void setTxPower(uint8_t power_dbm) = 0;
virtual void formatNeighborsReply(char *reply) = 0;
virtual const uint8_t* getSelfIdPubKey() = 0;
};
class CommonCLI {
mesh::Mesh* _mesh;
mesh::RTCClock* _rtc;
NodePrefs* _prefs;
CommonCLICallbacks* _callbacks;
mesh::MainBoard* _board;
char tmp[80];
mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); }
mesh::RTCClock* getRTCClock() { return _rtc; }
void savePrefs() { _callbacks->savePrefs(); }
void checkAdvertInterval();
@@ -57,8 +59,8 @@ class CommonCLI {
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
public:
CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks)
: _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { }
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks)
: _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { }
void loadPrefs(FILESYSTEM* _fs);
void savePrefs(FILESYSTEM* _fs);

View File

@@ -27,4 +27,5 @@ public:
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
int16_t setRxBoostedGainMode(bool en) { return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); };
};

View File

@@ -8,6 +8,8 @@
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <SPIFFS.h>
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
WiFi.softAP("MeshCore-OTA", NULL);
@@ -24,6 +26,9 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", home_buf);
});
server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/packet_log", "text/plain");
});
AsyncElegantOTA.setID(id_buf);
AsyncElegantOTA.begin(server); // Start ElegantOTA

View File

@@ -1,8 +1,10 @@
#pragma once
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
// LoRa radio module pins for Heltec V3
// Also for Heltec Wireless Tracker
#define P_LORA_DIO_1 14
#define P_LORA_NSS 8
#define P_LORA_RESET RADIOLIB_NC
@@ -13,11 +15,12 @@
// built-ins
#define PIN_VBAT_READ 1
#define PIN_ADC_CTRL 37
#ifndef PIN_ADC_CTRL // set in platformio.ini for Heltec Wireless Tracker (2)
#define PIN_ADC_CTRL 37
#endif
#define PIN_ADC_CTRL_ACTIVE LOW
#define PIN_ADC_CTRL_INACTIVE HIGH
#define PIN_LED_BUILTIN 35
#define PIN_VEXT_EN 36
//#define PIN_LED_BUILTIN 35
#include "ESP32Board.h"
@@ -28,6 +31,10 @@ private:
bool adc_active_state;
public:
RefCountedDigitalPin periph_power;
HeltecV3Board() : periph_power(PIN_VEXT_EN) { }
void begin() {
ESP32Board::begin();
@@ -38,8 +45,7 @@ public:
pinMode(PIN_ADC_CTRL, OUTPUT);
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
pinMode(PIN_VEXT_EN, OUTPUT);
digitalWrite(PIN_VEXT_EN, LOW); // for V3.2 boards
periph_power.begin();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {

View File

@@ -5,7 +5,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) {
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
if (_fs->exists(filename)) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(filename, "r");
#else
File file = _fs->open(filename);
#endif
if (file) {
loaded = id.readFrom(file);
file.close();
@@ -19,7 +23,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id, char display
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
if (_fs->exists(filename)) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(filename, "r");
#else
File file = _fs->open(filename);
#endif
if (file) {
loaded = id.readFrom(file);
@@ -41,6 +49,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
#if defined(NRF52_PLATFORM)
File file = _fs->open(filename, FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open(filename, "w");
#else
File file = _fs->open(filename, "w", true);
#endif
@@ -61,6 +71,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const
#if defined(NRF52_PLATFORM)
File file = _fs->open(filename, FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open(filename, "w");
#else
File file = _fs->open(filename, "w", true);
#endif

View File

@@ -1,6 +1,6 @@
#pragma once
#if defined(ESP32)
#if defined(ESP32) || defined(RP2040_PLATFORM)
#include <FS.h>
#define FILESYSTEM fs::FS
#elif defined(NRF52_PLATFORM)
@@ -18,7 +18,7 @@ class IdentityStore {
public:
IdentityStore(FILESYSTEM& fs, const char* dir): _fs(&fs), _dir(dir) { }
void begin() { _fs->mkdir(_dir); }
void begin() { if (_dir && _dir[0] == '/') { _fs->mkdir(_dir); } }
bool load(const char *name, mesh::LocalIdentity& id);
bool load(const char *name, mesh::LocalIdentity& id, char display_name[], int max_name_sz);
bool save(const char *name, const mesh::LocalIdentity& id);

View File

@@ -0,0 +1,29 @@
#pragma once
#include <Arduino.h>
class RefCountedDigitalPin {
uint8_t _pin;
int8_t _claims = 0;
public:
RefCountedDigitalPin(uint8_t pin): _pin(pin) { }
void begin() {
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW); // initial state
}
void claim() {
_claims++;
if (_claims > 0) {
digitalWrite(_pin, HIGH);
}
}
void release() {
_claims--;
if (_claims == 0) {
digitalWrite(_pin, LOW);
}
}
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include <CayenneLPP.h>
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
#define TELEM_PERM_LOCATION 0x02
#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device
class SensorManager {
public:
double node_lat, node_lon; // modify these, if you want to affect Advert location
SensorManager() { node_lat = 0; node_lon = 0; }
virtual bool begin() { return false; }
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
virtual void loop() { }
virtual int getNumSettings() const { return 0; }
virtual const char* getSettingName(int i) const { return NULL; }
virtual const char* getSettingValue(int i) const { return NULL; }
virtual bool setSettingValue(const char* name, const char* value) { return false; }
};

View File

@@ -33,11 +33,11 @@ class TBeamBoard : public ESP32Board {
public:
void begin() {
ESP32Board::begin();
power.setALDO2Voltage(3300);
power.enableALDO2();
pinMode(38,INPUT_PULLUP);
pinMode(38, INPUT_PULLUP);
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
@@ -75,7 +75,7 @@ public:
}
uint16_t getBattMilliVolts() override {
return 0;
return power.getBattVoltage();
}
const char* getManufacturerName() const override {

View File

@@ -0,0 +1,79 @@
#pragma once
#include <Wire.h>
#include <Arduino.h>
#include "XPowersLib.h"
#define XPOWERS_CHIP_AXP192
// LoRa radio module pins for TBeam
#define P_LORA_DIO_1 33 // SX1262 IRQ pin
#define P_LORA_NSS 18
#define P_LORA_RESET 23
#define P_LORA_BUSY 32 // SX1262 Busy pin
#define P_LORA_SCLK 5
#define P_LORA_MISO 19
#define P_LORA_MOSI 27
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class TBeamBoardSX1262 : public ESP32Board {
XPowersAXP192 power;
public:
void begin() {
ESP32Board::begin();
power.setLDO2Voltage(3300);
power.enableLDO2();
power.enableBattVoltageMeasure();
pinMode(38, INPUT_PULLUP);
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
}
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 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!
}
uint16_t getBattMilliVolts() override {
return power.getBattVoltage();
}
const char* getManufacturerName() const override {
return "LilyGo T-Beam SX1262";
}
};

View File

@@ -0,0 +1,106 @@
#pragma once
#include "ESP32Board.h"
#include <driver/rtc_io.h>
#include <Wire.h>
#include <Arduino.h>
#include "XPowersLib.h"
// LoRa radio module pins for TBeam S3 Supreme
#define P_LORA_DIO_1 1 //SX1262 IRQ pin
#define P_LORA_NSS 10 //SX1262 SS pin
#define P_LORA_RESET 5 //SX1262 Rest pin
#define P_LORA_BUSY 4 //SX1262 Busy pin
#define P_LORA_SCLK 12 //SX1262 SCLK pin
#define P_LORA_MISO 13 //SX1262 MISO pin
#define P_LORA_MOSI 11 //SX1262 MOSI pin
#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC)
#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC)
#define PIN_PMU_IRQ 40 //IRQ pin for PMU
#define PIN_USER_BTN 0
#define P_BOARD_SPI_MOSI 35 //SPI for SD Card and QMI8653 (IMU)
#define P_BOARD_SPI_MISO 37 //SPI for SD Card and QMI8653 (IMU)
#define P_BOARD_SPI_SCK 36 //SPI for SD Card and QMI8653 (IMU)
#define P_BPARD_SPI_CS 47 //SPI for SD Card and QMI8653 (IMU)
#define P_BOARD_IMU_CS 34 //Pin for QMI8653 (IMU) CS
#define P_BOARD_IMU_INT 33 //IMU Int pin
#define P_BOARD_RTC_INT 14 //RTC Int pin
#define P_GPS_RX 9 //GPS RX pin
#define P_GPS_TX 8 //GPS TX pin
#define P_GPS_WAKE 7 //GPS Wakeup pin
#define P_GPS_1PPS 6 //GPS 1PPS pin
//I2C Wire addresses
#define I2C_BME280_ADD 0x76 //BME280 sensor I2C address on Wire
#define I2C_OLED_ADD 0x3C //SH1106 OLED I2C address on Wire
#define I2C_QMC6310U_ADD 0x1C //QMC6310U mag sensor I2C address on Wire
//I2C Wire1 addresses
#define I2C_RTC_ADD 0x51 //RTC I2C address on Wire1
#define I2C_PMU_ADD 0x34 //AXP2101 I2C address on Wire1
class TBeamS3SupremeBoard : public ESP32Board {
public:
void begin() {
bool power_init();
ESP32Board::begin();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
}
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 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!
}
uint16_t getBattMilliVolts() override {
return 0;
}
uint16_t getBattPercent();
const char* getManufacturerName() const override {
return "LilyGo T-Beam S3 Supreme SX1262";
}
};

View File

@@ -22,7 +22,7 @@ static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
last_rx_len = len;
}
void ESPNOWRadio::begin() {
void ESPNOWRadio::init() {
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Long Range mode
@@ -72,6 +72,7 @@ void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
is_send_complete = false;
esp_err_t result = esp_now_send(broadcastAddress, bytes, len);
if (result == ESP_OK) {
n_sent++;
ESPNOW_DEBUG_PRINTLN("Send success");
} else {
last_send_result = result;
@@ -94,6 +95,7 @@ int ESPNOWRadio::recvRaw(uint8_t* bytes, int sz) {
if (last_rx_len > 0) {
memcpy(bytes, rx_buf, last_rx_len);
last_rx_len = 0;
n_recv++;
}
return len;
}

View File

@@ -9,7 +9,7 @@ protected:
public:
ESPNOWRadio() { n_recv = n_sent = 0; }
void begin() override;
void init();
int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override;
void startSendRaw(const uint8_t* bytes, int len) override;

View File

@@ -1,12 +1,12 @@
#include <Arduino.h>
#include "FaketecBoard.h"
#include "PromicroBoard.h"
#include <bluefruit.h>
#include <Wire.h>
static BLEDfu bledfu;
void FaketecBoard::begin() {
void PromicroBoard::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
btn_prev_state = HIGH;
@@ -14,7 +14,7 @@ void FaketecBoard::begin() {
pinMode(PIN_VBAT_READ, INPUT);
#ifdef BUTTON_PIN
pinMode(BUTTON_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
@@ -39,7 +39,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
bool FaketecBoard::startOTAUpdate(const char* id, char reply[]) {
bool PromicroBoard::startOTAUpdate(const char* id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
@@ -50,7 +50,7 @@ bool FaketecBoard::startOTAUpdate(const char* id, char reply[]) {
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName("Faketec_OTA");
Bluefruit.setName("ProMicro_OTA");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

View File

@@ -19,7 +19,7 @@
#define PIN_VBAT_READ 17
#define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
class FaketecBoard : public mesh::MainBoard {
class PromicroBoard : public mesh::MainBoard {
protected:
uint8_t startup_reason;
uint8_t btn_prev_state;
@@ -43,7 +43,7 @@ public:
}
const char* getManufacturerName() const override {
return "Faketec DIY";
return "ProMicro DIY";
}
int buttonStateChanged() {

View File

@@ -27,7 +27,7 @@ void RAK4631Board::begin() {
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();

View File

@@ -18,7 +18,7 @@ void T1000eBoard::begin() {
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();

View File

@@ -29,9 +29,16 @@ public:
uint16_t getBattMilliVolts() override {
#ifdef BATTERY_PIN
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, HIGH);
#endif
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12);
delay(10);
float volts = (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096;
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
analogReference(AR_DEFAULT); // put back to default
analogReadResolution(10);

View File

@@ -27,7 +27,7 @@ void T114Board::begin() {
pinMode(PIN_VBAT_READ, INPUT);
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();

View File

@@ -24,19 +24,6 @@ void TechoBoard::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
delay(200);
pinMode(PIN_PWR_EN, OUTPUT);
digitalWrite(PIN_PWR_EN, HIGH);
pinMode(PIN_BUTTON1, INPUT_PULLUP);
pinMode(PIN_BUTTON2, INPUT_PULLUP);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
delay(200);
pinMode(PIN_TXCO, OUTPUT);
digitalWrite(PIN_TXCO, HIGH);
Wire.begin();
pinMode(SX126X_POWER_EN, OUTPUT);

View File

@@ -0,0 +1,90 @@
#include <Arduino.h>
#include "ThinkNodeM1Board.h"
#ifdef THINKNODE_M1
#include <bluefruit.h>
#include <Wire.h>
static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle) {
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void ThinkNodeM1Board::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
Wire.begin();
pinMode(SX126X_POWER_EN, OUTPUT);
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
uint16_t ThinkNodeM1Board::getBattMilliVolts() {
int adcvalue = 0;
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12);
delay(10);
// ADC range is 0..3000mV and resolution is 12-bit (0..4095)
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
}
bool ThinkNodeM1Board::startOTAUpdate(const char* id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName("THINKNODE_M1_OTA");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Set up and start advertising
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
strcpy(reply, "OK - started");
return true;
}
#endif

View File

@@ -0,0 +1,49 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
// LoRa radio module pins for Elecrow ThinkNode M1
#define P_LORA_DIO_1 20
#define P_LORA_NSS 24
#define P_LORA_RESET 25
#define P_LORA_BUSY 17
#define P_LORA_SCLK 19
#define P_LORA_MISO 23
#define P_LORA_MOSI 22
#define SX126X_POWER_EN 37
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// built-ins
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT
#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
#define PIN_VBAT_READ (4)
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
class ThinkNodeM1Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin();
uint16_t getBattMilliVolts() override;
bool startOTAUpdate(const char* id, char reply[]) override;
uint8_t getStartupReason() const override {
return startup_reason;
}
const char* getManufacturerName() const override {
return "Elecrow ThinkNode-M1";
}
void reboot() override {
NVIC_SystemReset();
}
};

View File

@@ -0,0 +1,95 @@
#ifdef XIAO_NRF52
#include <Arduino.h>
#include "XiaoNrf52Board.h"
#include <bluefruit.h>
#include <Wire.h>
static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle)
{
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void XiaoNrf52Board::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
pinMode(PIN_VBAT, INPUT);
pinMode(VBAT_ENABLE, OUTPUT);
digitalWrite(VBAT_ENABLE, HIGH);
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
#endif
Wire.begin();
#ifdef P_LORA_TX_LED
pinMode(P_LORA_TX_LED, OUTPUT);
digitalWrite(P_LORA_TX_LED, HIGH);
#endif
// pinMode(SX126X_POWER_EN, OUTPUT);
// digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
bool XiaoNrf52Board::startOTAUpdate(const char* id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName("XIAO_NRF52_OTA");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Set up and start advertising
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
strcpy(reply, "OK - started");
return true;
return false;
}
#endif

View File

@@ -0,0 +1,67 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
#ifdef XIAO_NRF52
// LoRa radio module pins for Seeed Xiao-nrf52
#ifdef SX1262_XIAO_S3_VARIANT
#undef P_LORA_DIO_1
#undef P_LORA_BUSY
#undef P_LORA_RESET
#undef P_LORA_NSS
#define P_LORA_DIO_1 D0
#define P_LORA_BUSY D1
#define P_LORA_RESET D2
#define P_LORA_NSS D3
#endif
//#define SX126X_POWER_EN 37
class XiaoNrf52Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin();
uint8_t getStartupReason() const override { return startup_reason; }
#if defined(P_LORA_TX_LED)
void onBeforeTransmit() override {
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
}
void onAfterTransmit() override {
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
}
#endif
uint16_t getBattMilliVolts() override {
// Please read befor going further ;)
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
// We can't drive VBAT_ENABLE to HIGH as long
// as we don't know wether we are charging or not ...
// this is a 3mA loss (4/1500)
digitalWrite(VBAT_ENABLE, LOW);
int adcvalue = 0;
analogReadResolution(12);
analogReference(AR_INTERNAL_3_0);
delay(10);
adcvalue = analogRead(PIN_VBAT);
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
}
const char* getManufacturerName() const override {
return "Seeed Xiao-nrf52";
}
void reboot() override {
NVIC_SystemReset();
}
bool startOTAUpdate(const char* id, char reply[]) override;
};
#endif

View File

@@ -0,0 +1,42 @@
#include <Arduino.h>
#include "PicoWBoard.h"
//#include <bluefruit.h>
#include <Wire.h>
//static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle) {
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void PicoWBoard::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
pinMode(PIN_VBAT_READ, INPUT);
#ifdef PIN_USER_BTN
pinMode(PIN_USER_BTN, INPUT_PULLUP);
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();
//pinMode(SX126X_POWER_EN, OUTPUT);
//digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
bool PicoWBoard::startOTAUpdate(const char* id, char reply[]) {
return false;
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
// LoRa radio module pins for PicoW
#define P_LORA_DIO_1 20
#define P_LORA_NSS 3
#define P_LORA_RESET 15
#define P_LORA_BUSY 2
#define P_LORA_SCLK 10
#define P_LORA_MISO 12
#define P_LORA_MOSI 11
//#define SX126X_POWER_EN ??? // Not Sure
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// built-ins
#define PIN_VBAT_READ 26
#define ADC_MULTIPLIER (3.1 * 3.3 * 1000) // MT Uses 3.1
#define PIN_LED_BUILTIN LED_BUILTIN
class PicoWBoard : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin();
uint8_t getStartupReason() const override { return startup_reason; }
void onBeforeTransmit() override {
digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
}
void onAfterTransmit() override {
digitalWrite(LED_BUILTIN, LOW); // turn TX LED off
}
#define BATTERY_SAMPLES 8
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
uint32_t raw = 0;
for (int i = 0; i < BATTERY_SAMPLES; i++) {
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
}
const char* getManufacturerName() const override {
return "Pico W";
}
void reboot() override {
//NVIC_SystemReset();
rp2040.reboot();
}
bool startOTAUpdate(const char* id, char reply[]) override;
};

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Mesh.h"
class LocationProvider {
public:
virtual long getLatitude() = 0;
virtual long getLongitude() = 0;
virtual bool isValid() = 0;
virtual long getTimestamp() = 0;
virtual void reset();
virtual void begin();
virtual void stop();
virtual void loop();
};

View File

@@ -0,0 +1,80 @@
#pragma once
#include "LocationProvider.h"
#include <MicroNMEA.h>
#include <RTClib.h>
#ifndef GPS_EN
#define GPS_EN (-1)
#endif
#ifndef GPS_RESET
#define GPS_RESET (-1)
#endif
#ifndef GPS_RESET_FORCE
#define GPS_RESET_FORCE LOW
#endif
class MicroNMEALocationProvider : public LocationProvider {
char _nmeaBuffer[100];
MicroNMEA nmea;
Stream* _gps_serial;
int _pin_reset;
int _pin_en;
public :
MicroNMEALocationProvider(Stream& ser, int pin_reset = GPS_RESET, int pin_en = GPS_EN) :
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en) {
if (_pin_reset != -1) {
pinMode(_pin_reset, OUTPUT);
digitalWrite(_pin_reset, GPS_RESET_FORCE);
}
if (_pin_en != -1) {
pinMode(_pin_en, OUTPUT);
digitalWrite(_pin_en, LOW);
}
}
void begin() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
}
if (_pin_en != -1) {
digitalWrite(_pin_en, HIGH);
}
}
void reset() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
delay(100);
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
}
}
void stop() override {
if (_pin_en != -1) {
digitalWrite(_pin_en, LOW);
}
}
long getLatitude() override { return nmea.getLatitude(); }
long getLongitude() override { return nmea.getLongitude(); }
bool isValid() override { return nmea.isValid(); }
long getTimestamp() override {
DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond());
return dt.unixtime();
}
void loop() override {
while (_gps_serial->available()) {
char c = _gps_serial->read();
#ifdef GPS_NMEA_DEBUG
Serial.print(c);
#endif
nmea.process(c);
}
}
};

View File

@@ -7,7 +7,7 @@ class DisplayDriver {
protected:
DisplayDriver(int w, int h) { _w = w; _h = h; }
public:
enum Color { DARK, LIGHT };
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
int width() const { return _w; }
int height() const { return _h; }
@@ -15,6 +15,7 @@ public:
virtual bool isOn() = 0;
virtual void turnOn() = 0;
virtual void turnOff() = 0;
virtual void clear() = 0;
virtual void startFrame(Color bkg = DARK) = 0;
virtual void setTextSize(int sz) = 0;
virtual void setColor(Color c) = 0;
@@ -23,5 +24,6 @@ public:
virtual void fillRect(int x, int y, int w, int h) = 0;
virtual void drawRect(int x, int y, int w, int h) = 0;
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
virtual uint16_t getTextWidth(const char* str) = 0;
virtual void endFrame() = 0;
};

View File

@@ -0,0 +1,96 @@
#include "GxEPDDisplay.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 3
#endif
#ifdef TECHO_ZOOM
#define SCALE_X (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
#define SCALE_Y (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
#else
#define SCALE_X 1.5625f // 200 / 128
#define SCALE_Y 1.5625f // 200 / 128
#endif
bool GxEPDDisplay::begin() {
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
SPI1.begin();
display.init(115200, true, 2, false);
display.setRotation(DISPLAY_ROTATION);
#ifdef TECHO_ZOOM
display.setFont(&FreeMono9pt7b);
#endif
display.setPartialWindow(0, 0, display.width(), display.height());
display.fillScreen(GxEPD_WHITE);
display.display(true);
#if DISP_BACKLIGHT
pinMode(DISP_BACKLIGHT, OUTPUT);
#endif
_init = true;
return true;
}
void GxEPDDisplay::turnOn() {
if (!_init) begin();
#if DISP_BACKLIGHT
digitalWrite(DISP_BACKLIGHT, HIGH);
_isOn = true;
#endif
}
void GxEPDDisplay::turnOff() {
#if DISP_BACKLIGHT
digitalWrite(DISP_BACKLIGHT, LOW);
#endif
_isOn = false;
}
void GxEPDDisplay::clear() {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
}
void GxEPDDisplay::startFrame(Color bkg) {
display.fillScreen(GxEPD_WHITE);
}
void GxEPDDisplay::setTextSize(int sz) {
display.setTextSize(sz);
}
void GxEPDDisplay::setColor(Color c) {
display.setTextColor(GxEPD_BLACK);
}
void GxEPDDisplay::setCursor(int x, int y) {
display.setCursor(x*SCALE_X, (y+10)*SCALE_Y);
}
void GxEPDDisplay::print(const char* str) {
display.print(str);
}
void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
}
void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
}
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x*SCALE_X, (y*SCALE_Y) + 10, bits, w, h, GxEPD_BLACK);
}
uint16_t GxEPDDisplay::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / SCALE_X;
}
void GxEPDDisplay::endFrame() {
display.display(true);
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <SPI.h>
#include <Wire.h>
#define ENABLE_GxEPD2_GFX 0
#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <GxEPD2_4C.h>
#include <GxEPD2_7C.h>
#include <Fonts/FreeMono9pt7b.h>
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1
#include <epd/GxEPD2_150_BN.h> // 1.54" b/w
#include "DisplayDriver.h"
//GxEPD2_BW<GxEPD2_150_BN, 200> display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1
class GxEPDDisplay : public DisplayDriver {
GxEPD2_BW<GxEPD2_150_BN, 200> display;
bool _init = false;
bool _isOn = false;
public:
// there is a margin in y...
GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {
}
bool begin();
bool isOn() override {return _isOn;};
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;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,395 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef OLEDDISPLAY_h
#define OLEDDISPLAY_h
#include <cstdarg>
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#include <mbed.h>
#define delay(x) wait_ms(x)
#define yield() void()
/*
* This is a little Arduino String emulation to keep the OLEDDisplay
* library code in common between Arduino and mbed-os
*/
class String {
public:
String(const char *s) { _str = s; };
int length() { return strlen(_str); };
const char *c_str() { return _str; };
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
memcpy(buf, _str + index, std::min(bufsize, strlen(_str)));
};
private:
const char *_str;
};
#else
#error "Unkown operating system"
#endif
#include "OLEDDisplayFonts.h"
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ )
#ifndef DEBUG_OLEDDISPLAY
#define DEBUG_OLEDDISPLAY(...)
#endif
// Use DOUBLE BUFFERING by default
#ifndef OLEDDISPLAY_REDUCE_MEMORY
#define OLEDDISPLAY_DOUBLE_BUFFER
#endif
// Header Values
#define JUMPTABLE_BYTES 4
#define JUMPTABLE_LSB 1
#define JUMPTABLE_SIZE 2
#define JUMPTABLE_WIDTH 3
#define JUMPTABLE_START 4
#define WIDTH_POS 0
#define HEIGHT_POS 1
#define FIRST_CHAR_POS 2
#define CHAR_NUM_POS 3
// Display commands
#define CHARGEPUMP 0x8D
#define COLUMNADDR 0x21
#define COMSCANDEC 0xC8
#define COMSCANINC 0xC0
#define DISPLAYALLON 0xA5
#define DISPLAYALLON_RESUME 0xA4
#define DISPLAYOFF 0xAE
#define DISPLAYON 0xAF
#define EXTERNALVCC 0x1
#define INVERTDISPLAY 0xA7
#define MEMORYMODE 0x20
#define NORMALDISPLAY 0xA6
#define PAGEADDR 0x22
#define SEGREMAP 0xA0
#define SETCOMPINS 0xDA
#define SETCONTRAST 0x81
#define SETDISPLAYCLOCKDIV 0xD5
#define SETDISPLAYOFFSET 0xD3
#define SETHIGHCOLUMN 0x10
#define SETLOWCOLUMN 0x00
#define SETMULTIPLEX 0xA8
#define SETPRECHARGE 0xD9
#define SETSEGMENTREMAP 0xA1
#define SETSTARTLINE 0x40
#define SETVCOMDETECT 0xDB
#define SWITCHCAPVCC 0x2
#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif
enum OLEDDISPLAY_COLOR {
BLACK = 0,
WHITE = 1,
INVERSE = 2
};
enum OLEDDISPLAY_TEXT_ALIGNMENT {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_RIGHT = 1,
TEXT_ALIGN_CENTER = 2,
TEXT_ALIGN_CENTER_BOTH = 3
};
enum OLEDDISPLAY_GEOMETRY {
GEOMETRY_128_64 = 0,
GEOMETRY_128_32 = 1,
GEOMETRY_64_48 = 2,
GEOMETRY_64_32 = 3,
GEOMETRY_RAWMODE = 4,
GEOMETRY_128_128 = 5
};
enum HW_I2C {
I2C_ONE,
I2C_TWO
};
typedef char (*FontTableLookupFunction)(const uint8_t ch);
char DefaultFontTableLookup(const uint8_t ch);
#ifdef ARDUINO
class OLEDDisplay : public Print {
#elif __MBED__
class OLEDDisplay : public Stream {
#else
#error "Unkown operating system"
#endif
public:
OLEDDisplay();
virtual ~OLEDDisplay();
uint16_t width(void) const { return displayWidth; };
uint16_t height(void) const { return displayHeight; };
// Use this to resume after a deep sleep without resetting the display (what init() would do).
// Returns true if connection to the display was established and the buffer allocated, false otherwise.
bool allocateBuffer();
// Allocates the buffer and initializes the driver & display. Resets the display!
// Returns false if buffer allocation failed, true otherwise.
bool init();
// Free the memory used by the display
void end();
// Cycle through the initialization
void resetDisplay(void);
/* Drawing functions */
// Sets the color of all pixel operations
void setColor(OLEDDISPLAY_COLOR color);
// Returns the current color.
OLEDDISPLAY_COLOR getColor();
// Draw a pixel at given position
void setPixel(int16_t x, int16_t y);
// Draw a pixel at given position and color
void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color);
// Clear a pixel at given position FIXME: INVERSE is untested with this function
void clearPixel(int16_t x, int16_t y);
// Draw a line from position 0 to position 1
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
// Draw the border of a rectangle at the given location
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Fill the rectangle
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Draw the border of a circle
void drawCircle(int16_t x, int16_t y, int16_t radius);
// Draw all Quadrants specified in the quads bit mask
void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads);
// Fill circle
void fillCircle(int16_t x, int16_t y, int16_t radius);
// Draw an empty triangle i.e. only the outline
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
// Draw a solid triangle i.e. filled
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
// Draw a line horizontally
void drawHorizontalLine(int16_t x, int16_t y, int16_t length);
// Draw a line vertically
void drawVerticalLine(int16_t x, int16_t y, int16_t length);
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
// a unsigned byte value between 0 and 100
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress);
// Draw a bitmap in the internal image format
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image);
// Draw a XBM
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm);
// Draw icon 16x16 xbm format
void drawIco16x16(int16_t x, int16_t y, const uint8_t *ico, bool inverse = false);
/* Text functions */
// Draws a string at the given location, returns how many chars have been written
uint16_t drawString(int16_t x, int16_t y, const String &text);
// Draws a formatted string (like printf) at the given location
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... );
// Draws a String with a maximum width at the given location.
// If the given String is wider than the specified width
// The text will be wrapped to the next line at a space or dash
// returns 0 if everything fits on the screen or the numbers of characters in the
// first line if not
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
// Returns the width of the const char* with the current
// font settings
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false);
// Convencience method for the const char version
uint16_t getStringWidth(const String &text);
// Specifies relative to which anchor point
// the text is rendered. Available constants:
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment);
// Sets the current font. Available default fonts
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
void setFont(const uint8_t *fontData);
// Set the function that will convert utf-8 to font table index
void setFontTableLookupFunction(FontTableLookupFunction function);
/* Display functions */
// Turn the display on
void displayOn(void);
// Turn the display offs
void displayOff(void);
// Inverted display mode
void invertDisplay(void);
// Normal display mode
void normalDisplay(void);
// Set display contrast
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
// normal brightness & contrast: contrast = 100
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64);
// Convenience method to access
virtual void setBrightness(uint8_t);
// Reset display rotation or mirroring
void resetOrientation();
// Turn the display upside down
void flipScreenVertically();
// Mirror the display (to be used in a mirror or as a projector)
void mirrorScreen();
// Write the buffer to the display memory
virtual void display(void) = 0;
// Clear the local pixel buffer
void clear(void);
// Log buffer implementation
// This will define the lines and characters you can
// print to the screen. When you exeed the buffer size (lines * chars)
// the output may be truncated due to the size constraint.
bool setLogBuffer(uint16_t lines, uint16_t chars);
// Draw the log buffer at position (x, y)
void drawLogBuffer(uint16_t x, uint16_t y);
// Get screen geometry
uint16_t getWidth(void);
uint16_t getHeight(void);
// Implement needed function to be compatible with Print class
size_t write(uint8_t c);
size_t write(const char* s);
// Implement needed function to be compatible with Stream class
#ifdef __MBED__
int _putc(int c);
int _getc() { return -1; };
#endif
uint8_t *buffer;
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint8_t *buffer_back;
#endif
// Set the correct height, width and buffer for the geometry
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0);
protected:
OLEDDISPLAY_GEOMETRY geometry;
uint16_t displayWidth;
uint16_t displayHeight;
uint16_t displayBufferSize;
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment;
OLEDDISPLAY_COLOR color;
const uint8_t *fontData;
// State values for logBuffer
uint16_t logBufferSize;
uint16_t logBufferFilled;
uint16_t logBufferLine;
uint16_t logBufferMaxLines;
char *logBuffer;
// the header size of the buffer used, e.g. for the SPI command header
int BufferOffset;
virtual int getBufferOffset(void) = 0;
// Send a command to the display (low level function)
virtual void sendCommand(uint8_t com) {(void)com;};
// Connect to the display
virtual bool connect() { return false; };
// Send all the init commands
virtual void sendInitCommands();
// converts utf8 characters to extended ascii
char* utf8ascii(const String &s);
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline));
uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8);
FontTableLookupFunction fontTableLookupFunction;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
#ifndef OLEDDISPLAYFONTS_h
#define OLEDDISPLAYFONTS_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define PROGMEM
#endif
extern const uint8_t ArialMT_Plain_10[] PROGMEM;
extern const uint8_t ArialMT_Plain_16[] PROGMEM;
extern const uint8_t ArialMT_Plain_24[] PROGMEM;
#endif

View File

@@ -20,6 +20,11 @@ void SSD1306Display::turnOff() {
_isOn = false;
}
void SSD1306Display::clear() {
display.clearDisplay();
display.display();
}
void SSD1306Display::startFrame(Color bkg) {
display.clearDisplay(); // TODO: apply 'bkg'
_color = SSD1306_WHITE;
@@ -33,7 +38,7 @@ void SSD1306Display::setTextSize(int sz) {
}
void SSD1306Display::setColor(Color c) {
_color = (c == LIGHT) ? SSD1306_WHITE : SSD1306_BLACK;
_color = (c != 0) ? SSD1306_WHITE : SSD1306_BLACK;
display.setTextColor(_color);
}
@@ -57,6 +62,13 @@ void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE);
}
uint16_t SSD1306Display::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
void SSD1306Display::endFrame() {
display.display();
}

View File

@@ -27,6 +27,7 @@ public:
bool isOn() override { return _isOn; }
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;
@@ -35,5 +36,6 @@ public:
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;
};

View File

@@ -0,0 +1,134 @@
#ifdef ST7735
#include "ST7735Display.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 2
#endif
#define SCALE_X 1.25f // 160 / 128
#define SCALE_Y 1.25f // 80 / 64
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
return true;
/*
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
*/
}
bool ST7735Display::begin() {
if (!_isOn) {
if (_peripher_power) _peripher_power->claim();
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, HIGH);
display.initR(INITR_MINI160x80_PLUGIN);
display.setRotation(DISPLAY_ROTATION);
display.setSPISpeed(40000000);
display.fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(2);
display.cp437(true); // Use full 256 char 'Code Page 437' font
_isOn = true;
}
return true;
}
void ST7735Display::turnOn() {
ST7735Display::begin();
}
void ST7735Display::turnOff() {
if (_isOn) {
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, LOW);
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
_isOn = false;
if (_peripher_power) _peripher_power->release();
}
}
void ST7735Display::clear() {
//Serial.println("DBG: display.Clear");
display.fillScreen(ST77XX_BLACK);
}
void ST7735Display::startFrame(Color bkg) {
display.fillScreen(0x00);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(1); // This one affects size of Please wait... message
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void ST7735Display::setTextSize(int sz) {
display.setTextSize(sz);
}
void ST7735Display::setColor(Color c) {
switch (c) {
case DisplayDriver::DARK :
_color = ST77XX_BLACK;
break;
case DisplayDriver::LIGHT :
_color = ST77XX_WHITE;
break;
case DisplayDriver::RED :
_color = ST77XX_RED;
break;
case DisplayDriver::GREEN :
_color = ST77XX_GREEN;
break;
case DisplayDriver::BLUE :
_color = ST77XX_BLUE;
break;
case DisplayDriver::YELLOW :
_color = ST77XX_YELLOW;
break;
case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE;
break;
default:
_color = ST77XX_WHITE;
break;
}
display.setTextColor(_color);
}
void ST7735Display::setCursor(int x, int y) {
display.setCursor(x*SCALE_X, y*SCALE_Y);
}
void ST7735Display::print(const char* str) {
display.print(str);
}
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);
}
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);
}
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);
}
uint16_t ST7735Display::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / SCALE_X;
}
void ST7735Display::endFrame() {
// display.display();
}
#endif

View File

@@ -0,0 +1,49 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <helpers/RefCountedDigitalPin.h>
class ST7735Display : public DisplayDriver {
Adafruit_ST7735 display;
bool _isOn;
uint16_t _color;
RefCountedDigitalPin* _peripher_power;
bool i2c_probe(TwoWire& wire, uint8_t addr);
public:
#ifdef USE_PIN_TFT
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
_peripher_power(peripher_power)
{
_isOn = false;
}
#else
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
_peripher_power(peripher_power)
{
_isOn = false;
}
#endif
bool begin();
bool isOn() override { return _isOn; }
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;
};

View File

@@ -0,0 +1,157 @@
#ifdef ST7789
#include "ST7789Display.h"
#ifndef X_OFFSET
#define X_OFFSET 0 // No offset needed for landscape
#endif
#ifndef Y_OFFSET
#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff
#endif
#define SCALE_X 1.875f // 240 / 128
#define SCALE_Y 2.109375f // 135 / 64
bool ST7789Display::begin() {
if(!_isOn) {
pinMode(PIN_TFT_VDD_CTL, OUTPUT);
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
digitalWrite(PIN_TFT_VDD_CTL, LOW);
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
digitalWrite(PIN_TFT_RST, HIGH);
display.init();
display.landscapeScreen();
display.displayOn();
setCursor(0,0);
_isOn = true;
}
return true;
}
void ST7789Display::turnOn() {
ST7789Display::begin();
}
void ST7789Display::turnOff() {
digitalWrite(PIN_TFT_VDD_CTL, HIGH);
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, LOW);
_isOn = false;
}
void ST7789Display::clear() {
display.clear();
}
void ST7789Display::startFrame(Color bkg) {
display.clear();
}
void ST7789Display::setTextSize(int sz) {
switch(sz) {
case 1 :
display.setFont(ArialMT_Plain_16);
break;
case 2 :
display.setFont(ArialMT_Plain_24);
break;
default:
display.setFont(ArialMT_Plain_16);
}
}
void ST7789Display::setColor(Color c) {
switch (c) {
case DisplayDriver::DARK :
_color = ST77XX_BLACK;
break;
case DisplayDriver::LIGHT :
_color = ST77XX_WHITE;
break;
case DisplayDriver::RED :
_color = ST77XX_RED;
break;
case DisplayDriver::GREEN :
_color = ST77XX_GREEN;
break;
case DisplayDriver::BLUE :
_color = ST77XX_BLUE;
break;
case DisplayDriver::YELLOW :
_color = ST77XX_YELLOW;
break;
case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE;
break;
default:
_color = ST77XX_WHITE;
break;
}
display.setRGB(_color);
}
void ST7789Display::setCursor(int x, int y) {
_x = x*SCALE_X + X_OFFSET;
_y = y*SCALE_Y + Y_OFFSET;
}
void ST7789Display::print(const char* str) {
display.drawString(_x, _y, str);
}
void ST7789Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
}
void ST7789Display::drawRect(int x, int y, int w, int h) {
display.drawRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
}
void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
// Calculate the base position in display coordinates
uint16_t startX = x * SCALE_X + X_OFFSET;
uint16_t startY = y * SCALE_Y + Y_OFFSET;
// Width in bytes for bitmap processing
uint16_t widthInBytes = (w + 7) / 8;
// Process the bitmap row by row
for (uint16_t by = 0; by < h; by++) {
// Calculate the target y-coordinates for this logical row
int y1 = startY + (int)(by * SCALE_Y);
int y2 = startY + (int)((by + 1) * SCALE_Y);
int block_h = y2 - y1;
// Scan across the row bit by bit
for (uint16_t bx = 0; bx < w; bx++) {
// Calculate the target x-coordinates for this logical column
int x1 = startX + (int)(bx * SCALE_X);
int x2 = startX + (int)((bx + 1) * SCALE_X);
int block_w = x2 - x1;
// Get the current bit
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
uint8_t bitMask = 0x80 >> (bx & 7);
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
// If the bit is set, draw a block of pixels
if (bitSet) {
// Draw the block as a filled rectangle
display.fillRect(x1, y1, block_w, block_h);
}
}
}
}
uint16_t ST7789Display::getTextWidth(const char* str) {
return display.getStringWidth(str) / SCALE_X;
}
void ST7789Display::endFrame() {
display.display();
}
#endif

View File

@@ -0,0 +1,35 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <ST7789Spi.h>
class ST7789Display : public DisplayDriver {
ST7789Spi display;
bool _isOn;
uint16_t _color;
int _x=0, _y=0;
bool i2c_probe(TwoWire& wire, uint8_t addr);
public:
ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
bool begin();
bool isOn() override { return _isOn; }
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;
};

464
src/helpers/ui/ST7789Spi.h Normal file
View File

@@ -0,0 +1,464 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2024 by Heltec AutoMation
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef ST7789Spi_h
#define ST7789Spi_h
#include "OLEDDisplay.h"
#include <SPI.h>
#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
#define LED_A_ON LOW
#ifdef ESP_PLATFORM
#undef LED_A_ON
#define LED_A_ON HIGH
#define rtos_free free
#define rtos_malloc malloc
//SPIClass SPI1(HSPI);
#endif
class ST7789Spi : public OLEDDisplay {
private:
uint8_t _rst;
uint8_t _dc;
uint8_t _cs;
uint8_t _ledA;
int _miso;
int _mosi;
int _clk;
SPIClass * _spi;
SPISettings _spiSettings;
uint16_t _RGB=0xFFFF;
uint8_t _buffheight;
public:
/* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */
ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=135,int mosi=-1,int miso=-1,int clk=-1) {
this->_spi = spiClass;
this->_rst = _rst;
this->_dc = _dc;
this->_cs = _cs;
this->_mosi=mosi;
this->_miso=miso;
this->_clk=clk;
//this->_ledA = _ledA;
_spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
setGeometry(g,width,height);
}
bool connect(){
this->_buffheight=displayHeight / 8;
this->_buffheight+=displayHeight % 8 ? 1:0;
pinMode(_cs, OUTPUT);
pinMode(_dc, OUTPUT);
//pinMode(_ledA, OUTPUT);
if (_cs != (uint8_t) -1) {
pinMode(_cs, OUTPUT);
}
pinMode(_rst, OUTPUT);
#ifdef ESP_PLATFORM
_spi->begin(_clk,_miso,_mosi,-1);
#else
_spi->begin();
#endif
_spi->setClockDivider (SPI_CLOCK_DIV2);
// Pulse Reset low for 10ms
digitalWrite(_rst, HIGH);
delay(1);
digitalWrite(_rst, LOW);
delay(10);
digitalWrite(_rst, HIGH);
_spi->begin ();
//digitalWrite(_ledA, LED_A_ON);
return true;
}
void display(void) {
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint16_t minBoundY = UINT16_MAX;
uint16_t maxBoundY = 0;
uint16_t minBoundX = UINT16_MAX;
uint16_t maxBoundX = 0;
uint16_t x, y;
// Calculate the Y bounding box of changes
// and copy buffer[pos] to buffer_back[pos];
for (y = 0; y < _buffheight; y++) {
for (x = 0; x < displayWidth; x++) {
//Serial.printf("x %d y %d\r\n",x,y);
uint16_t pos = x + y * displayWidth;
if (buffer[pos] != buffer_back[pos]) {
minBoundY = min(minBoundY, y);
maxBoundY = max(maxBoundY, y);
minBoundX = min(minBoundX, x);
maxBoundX = max(maxBoundX, x);
}
buffer_back[pos] = buffer[pos];
}
yield();
}
// If the minBoundY wasn't updated
// we can savely assume that buffer_back[pos] == buffer[pos]
// holdes true for all values of pos
if (minBoundY == UINT16_MAX) return;
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
for (y = minBoundY; y <= maxBoundY; y++)
{
for(int temp = 0; temp<8;temp++)
{
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
//setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1);
uint32_t const pixbufcount = maxBoundX-minBoundX+1;
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
for (x = minBoundX; x <= maxBoundX; x++)
{
pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
}
#ifdef ESP_PLATFORM
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
#else
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
#endif
rtos_free(pixbuf);
}
}
_spi->endTransaction();
set_CS(HIGH);
#else
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
uint8_t x, y;
for (y = 0; y < _buffheight; y++)
{
for(int temp = 0; temp<8;temp++)
{
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
setAddrWindow(y*8+temp,0,1,displayWidth);
uint32_t const pixbufcount = displayWidth;
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
for (x = 0; x < displayWidth; x++)
{
pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
}
#ifdef ESP_PLATFORM
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
#else
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
#endif
rtos_free(pixbuf);
}
}
_spi->endTransaction();
set_CS(HIGH);
#endif
}
virtual void resetOrientation() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void flipScreenVertically() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void mirrorScreen() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void landscapeScreen() {
// For landscape mode rotated 180 degrees with correct text direction
// MV swaps rows/columns for landscape orientation
// Adding MX (instead of MY) flips X axis and rotates 180 degrees
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
void setRGB(uint16_t c)
{
this->_RGB=0x00|c>>8|c<<8&0xFF00;
}
void displayOn(void) {
//sendCommand(DISPLAYON);
}
void displayOff(void) {
//sendCommand(DISPLAYOFF);
}
void drawBitmap(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) {
int16_t widthInXbm = (width + 7) / 8;
uint8_t data = 0;
for(int16_t y = 0; y < height; y++) {
for(int16_t x = 0; x < width; x++ ) {
if (x & 7) {
data <<= 1; // Move a bit
} else { // Read new data every 8 bit
data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm);
}
// if there is a bit draw it
if (data & 0x80) {
setPixel(xMove + x, yMove + y);
}
}
}
}
//#define ST77XX_MADCTL_MY 0x80
//#define ST77XX_MADCTL_MX 0x40
//#define ST77XX_MADCTL_MV 0x20
//#define ST77XX_MADCTL_ML 0x10
protected:
// Send all the init commands
virtual void sendInitCommands()
{
sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay
delay(150);
sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay
delay(10);
sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color
WriteData(0x55);
delay(10);
// Initialize with landscape orientation rotated 180 degrees
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions)
WriteData(madctl);
delay(10);
// Set column address range for landscape orientation (240 pixels wide)
sendCommand(ST77XX_CASET); // 5: Column addr set
WriteData(0x00);
WriteData(0x00); // XSTART = 0
WriteData(0x00);
WriteData(240); // XEND = 240
// Set row address range for landscape orientation (135 pixels tall)
sendCommand(ST77XX_RASET); // 6: Row addr set
WriteData(0x00);
WriteData(0x00); // YSTART = 0
WriteData(0x00);
WriteData(135); // YEND = 135
sendCommand(ST77XX_SLPOUT); // 7: hack
delay(10);
sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay
delay(10);
sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay
delay(10);
sendCommand(ST77XX_INVON); // 10: invert
delay(10);
// Use landscape mode instead of portrait
landscapeScreen();
setRGB(ST77XX_GREEN);
}
private:
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
x += (320-displayWidth)/2;
y += (240-displayHeight)/2;
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
}
int getBufferOffset(void) {
return 0;
}
inline void set_CS(bool level) {
if (_cs != (uint8_t) -1) {
digitalWrite(_cs, level);
}
};
inline void sendCommand(uint8_t com) __attribute__((always_inline)){
set_CS(HIGH);
digitalWrite(_dc, LOW);
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(com);
_spi->endTransaction();
set_CS(HIGH);
digitalWrite(_dc, HIGH);
}
inline void WriteData(uint8_t data) __attribute__((always_inline)){
digitalWrite(_cs, LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(data);
_spi->endTransaction();
digitalWrite(_cs, HIGH);
}
void SPI_WRITE32(uint32_t l)
{
_spi->transfer(l >> 24);
_spi->transfer(l >> 16);
_spi->transfer(l >> 8);
_spi->transfer(l);
}
void writeCommand(uint8_t cmd) {
digitalWrite(_dc, LOW);
_spi->transfer(cmd);
digitalWrite(_dc, HIGH);
}
// Private functions
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) {
this->geometry = g;
switch (g) {
case GEOMETRY_128_128:
this->displayWidth = 128;
this->displayHeight = 128;
break;
case GEOMETRY_128_64:
this->displayWidth = 128;
this->displayHeight = 64;
break;
case GEOMETRY_128_32:
this->displayWidth = 128;
this->displayHeight = 32;
break;
case GEOMETRY_64_48:
this->displayWidth = 64;
this->displayHeight = 48;
break;
case GEOMETRY_64_32:
this->displayWidth = 64;
this->displayHeight = 32;
break;
case GEOMETRY_RAWMODE:
this->displayWidth = width > 0 ? width : 128;
this->displayHeight = height > 0 ? height : 64;
break;
}
uint8_t tmp=displayHeight % 8;
uint8_t _buffheight=displayHeight / 8;
if(tmp!=0)
_buffheight++;
this->displayBufferSize = displayWidth * _buffheight ;
}
};
#endif

View File

@@ -0,0 +1,68 @@
[Generic_E22]
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =
${esp32_base.build_flags}
-I variants/generic-e22
-D GENERIC_E22
-D P_LORA_TX_LED=2
-D PIN_VBAT_READ=35
-D P_LORA_DIO_1=33
-D P_LORA_NSS=18
-D P_LORA_RESET=RADIOLIB_NC
-D P_LORA_BUSY=32
-D P_LORA_SCLK=5
-D P_LORA_MOSI=27
-D P_LORA_MISO=19
-D SX126X_TXEN=13
-D SX126X_RXEN=14
-D PIN_BOARD_SDA=21
-D PIN_BOARD_SCL=22
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/generic-e22>
lib_deps =
${esp32_base.lib_deps}
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:Generic_E22_sx1262_repeater]
extends = Generic_E22
build_src_filter = ${Generic_E22.build_src_filter}
+<../examples/simple_repeater/main.cpp>
build_flags =
${Generic_E22.build_flags}
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
-D ADVERT_NAME='"E22 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Generic_E22.lib_deps}
${esp32_ota.lib_deps}
[env:Generic_E22_sx1268_repeater]
extends = Generic_E22
build_src_filter = ${Generic_E22.build_src_filter}
+<../examples/simple_repeater/main.cpp>
build_flags =
${Generic_E22.build_flags}
-D RADIO_CLASS=CustomSX1268
-D WRAPPER_CLASS=CustomSX1268Wrapper
-D LORA_TX_POWER=22
-D ADVERT_NAME='"E22 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Generic_E22.lib_deps}
${esp32_ota.lib_deps}

View File

@@ -0,0 +1,80 @@
#include <Arduino.h>
#include "target.h"
ESP32Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5
#endif
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
#else
float tcxo = 1.6f;
#endif
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
return false; // fail
}
radio.setCRC(1);
#if defined(SX126X_RXEN) && defined(SX126X_TXEN)
radio.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif
#ifdef SX126X_CURRENT_LIMIT
radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
#endif
#ifdef SX126X_DIO2_AS_RF_SWITCH
radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
#endif
#ifdef SX126X_RX_BOOSTED_GAIN
radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
return true; // success
}
uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}
void radio_set_tx_power(uint8_t dbm) {
radio.setOutputPower(dbm);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}

View File

@@ -0,0 +1,21 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/ESP32Board.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/CustomSX1268Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern ESP32Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(uint8_t dbm);
mesh::LocalIdentity radio_new_identity();

View File

@@ -0,0 +1,44 @@
// For OLED LCD
#define I2C_SDA 21
#define I2C_SCL 22
// For GPS, 'undef's not needed
#define GPS_TX_PIN 15
#define GPS_RX_PIN 12
#define PIN_GPS_EN 4
#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here
#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO35_CHANNEL
#define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k)
#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards
#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975).
#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets)
// Radio
#define USE_SX1262 // E22-900M30S uses SX1262
#define USE_SX1268 // E22-400M30S uses SX1268
#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA)
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V
#define SX126X_CS 18 // EBYTE module's NSS pin
#define SX126X_SCK 5 // EBYTE module's SCK pin
#define SX126X_MOSI 27 // EBYTE module's MOSI pin
#define SX126X_MISO 19 // EBYTE module's MISO pin
#define SX126X_RESET 23 // EBYTE module's NRST pin
#define SX126X_BUSY 32 // EBYTE module's BUSY pin
#define SX126X_DIO1 33 // EBYTE module's DIO1 pin
// The E22's TXEN pin is connected to MCU pin, E22's RXEN pin is connected to MCU pin (allows for ramping up PA before transmission
// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent
// a short if they are both connected at the same time and there's a slight non-neglibible delay and/or voltage difference between
// DIO2 and TXEN).
#define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU
#define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU
#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure
#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure
#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure
#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure
#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure

View File

@@ -19,7 +19,7 @@ build_flags =
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${esp32_base.build_src_filter}
+<helpers/esp32/ESPNowRadio.cpp>
+<helpers/esp32/ESPNOWRadio.cpp>
+<../variants/generic_espnow>
[env:Generic_ESPNOW_terminal_chat]
@@ -39,9 +39,10 @@ extends = Generic_ESPNOW
build_flags =
${Generic_ESPNOW.build_flags}
-D ADVERT_NAME='"ESPNOW Repeater"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
build_src_filter = ${Generic_ESPNOW.build_src_filter}
+<../examples/simple_repeater/main.cpp>
lib_deps =
@@ -71,8 +72,8 @@ extends = Generic_ESPNOW
build_flags =
${Generic_ESPNOW.build_flags}
-D ADVERT_NAME='"Heltec Room"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
build_src_filter = ${Generic_ESPNOW.build_src_filter}
@@ -80,4 +81,3 @@ build_src_filter = ${Generic_ESPNOW.build_src_filter}
lib_deps =
${Generic_ESPNOW.lib_deps}
${esp32_ota.lib_deps}

View File

@@ -7,11 +7,13 @@ ESP32Board board;
ESPNOWRadio radio_driver;
ESP32RTCClock rtc_clock;
SensorManager sensors;
bool radio_init() {
rtc_clock.begin();
// NOTE: radio_driver.begin() is called by Dispatcher::begin(), so not needed here
radio_driver.init();
return true; // success
}
@@ -27,7 +29,16 @@ void radio_set_tx_power(uint8_t dbm) {
radio_driver.setTxPower(dbm);
}
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
class ESP_RNG : public mesh::RNG {
public:
void random(uint8_t* dest, size_t sz) override {
esp_fill_random(dest, sz);
}
};
mesh::LocalIdentity radio_new_identity() {
StdRNG rng; // TODO: need stronger True-RNG here
ESP_RNG rng;
return mesh::LocalIdentity(&rng); // create new random identity
}

View File

@@ -2,10 +2,12 @@
#include <helpers/ESP32Board.h>
#include <helpers/esp32/ESPNOWRadio.h>
#include <helpers/SensorManager.h>
extern ESP32Board board;
extern ESPNOWRadio radio_driver;
extern ESP32RTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();

View File

@@ -0,0 +1,61 @@
[Heltec_tracker_base]
extends = esp32_base
board = esp32-s3-devkitc-1
build_flags =
${esp32_base.build_flags}
-I variants/heltec_tracker
-D HELTEC_LORA_V3
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
-D P_LORA_TX_LED=18
-D PIN_BOARD_SDA=45
-D PIN_BOARD_SCL=46
-D PIN_USER_BTN=0
-D PIN_ADC_CTRL=2
-D PIN_TFT_SDA=42 ; SDIN
-D PIN_TFT_SCL=41 ; SCLK
-D PIN_TFT_DC=40 ; RS (register select)
-D PIN_TFT_RST=39 ; RES
-D PIN_TFT_CS=38
-D USE_PIN_TFT=1
-D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS
-D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground)
-D PIN_GPS_RX=33
-D PIN_GPS_TX=34
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
-D SX126X_RX_BOOSTED_GAIN=1
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/heltec_tracker>
lib_deps =
${esp32_base.lib_deps}
[env:Heltec_Wireless_Tracker_companion_radio_ble]
extends = Heltec_tracker_base
build_flags =
${Heltec_tracker_base.build_flags}
-I src/helpers/ui
; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging
-D ST7735
-D DISPLAY_ROTATION=1
-D DISPLAY_CLASS=ST7735Display
-D MAX_CONTACTS=100
-D MAX_GROUP_CHANNELS=8
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
; -D BLE_DEBUG_LOGGING=1
; -D ENABLE_PRIVATE_KEY_IMPORT=1
; -D ENABLE_PRIVATE_KEY_EXPORT=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_tracker_base.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio>
+<helpers/ui/ST7735Display.cpp>
lib_deps =
${Heltec_tracker_base.lib_deps}
densaugeo/base64 @ ~1.4.0
stevemarple/MicroNMEA @ ^2.0.6
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0

View File

@@ -0,0 +1,147 @@
#include <Arduino.h>
#include "target.h"
#include <helpers/sensors/MicroNMEALocationProvider.h>
HeltecV3Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
HWTSensorManager sensors = HWTSensorManager(nmea);
#ifndef LORA_CR
#define LORA_CR 5
#endif
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
#else
float tcxo = 1.6f;
#endif
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
return false; // fail
}
radio.setCRC(1);
#ifdef SX126X_CURRENT_LIMIT
radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
#endif
#ifdef SX126X_DIO2_AS_RF_SWITCH
radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
#endif
#ifdef SX126X_RX_BOOSTED_GAIN
radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
return true; // success
}
uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}
void radio_set_tx_power(uint8_t dbm) {
radio.setOutputPower(dbm);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}
void HWTSensorManager::start_gps() {
if (!gps_active) {
board.periph_power.claim();
gps_active = true;
Serial1.println("$CFGSYS,h35155*68");
}
}
void HWTSensorManager::stop_gps() {
if (gps_active) {
gps_active = false;
board.periph_power.release();
}
}
bool HWTSensorManager::begin() {
// init GPS port
Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
return true;
}
bool HWTSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission?
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f);
}
return true;
}
void HWTSensorManager::loop() {
static long next_gps_update = 0;
_location->loop();
if (millis() > next_gps_update) {
if (gps_active && _location->isValid()) {
node_lat = ((double)_location->getLatitude())/1000000.;
node_lon = ((double)_location->getLongitude())/1000000.;
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
}
next_gps_update = millis() + 1000;
}
}
int HWTSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
const char* HWTSensorManager::getSettingName(int i) const {
return i == 0 ? "gps" : NULL;
}
const char* HWTSensorManager::getSettingValue(int i) const {
if (i == 0) {
return gps_active ? "1" : "0";
}
return NULL;
}
bool HWTSensorManager::setSettingValue(const char* name, const char* value) {
if (strcmp(name, "gps") == 0) {
if (strcmp(value, "0") == 0) {
stop_gps();
} else {
start_gps();
}
return true;
}
return false; // not supported
}

View File

@@ -0,0 +1,38 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/HeltecV3Board.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/LocationProvider.h>
class HWTSensorManager : public SensorManager {
bool gps_active = false;
LocationProvider * _location;
void start_gps();
void stop_gps();
public:
HWTSensorManager(LocationProvider &location): _location(&location) { }
bool begin() override;
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
void loop() override;
int getNumSettings() const override;
const char* getSettingName(int i) const override;
const char* getSettingValue(int i) const override;
bool setSettingValue(const char* name, const char* value) override;
};
extern HeltecV3Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern HWTSensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(uint8_t dbm);
mesh::LocalIdentity radio_new_identity();

View File

@@ -25,14 +25,15 @@ build_flags =
${Heltec_lora32_v2.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Repeater"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
+<../examples/simple_repeater>
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
lib_deps =
${Heltec_lora32_v2.lib_deps}
${esp32_ota.lib_deps}
@@ -43,14 +44,14 @@ build_flags =
${Heltec_lora32_v2.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Room"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${Heltec_lora32_v2.lib_deps}
@@ -81,7 +82,7 @@ build_flags =
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${Heltec_lora32_v2.lib_deps}
@@ -102,7 +103,7 @@ build_flags =
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${Heltec_lora32_v2.lib_deps}

View File

@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5

View File

@@ -6,10 +6,12 @@
#include <helpers/HeltecV2Board.h>
#include <helpers/CustomSX1276Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern HeltecV2Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();

View File

@@ -12,6 +12,7 @@ build_flags =
-D PIN_BOARD_SDA=17
-D PIN_BOARD_SCL=18
-D PIN_USER_BTN=0
-D PIN_VEXT_EN=36
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
@@ -28,13 +29,14 @@ build_flags =
${Heltec_lora32_v3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Repeater"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MESH_PACKET_LOGGING=1
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${Heltec_lora32_v3.lib_deps}
@@ -46,14 +48,14 @@ build_flags =
${Heltec_lora32_v3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Room"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${Heltec_lora32_v3.lib_deps}
@@ -85,7 +87,7 @@ build_flags =
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${Heltec_lora32_v3.lib_deps}
@@ -105,7 +107,7 @@ build_flags =
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio>
lib_deps =
@@ -127,7 +129,7 @@ build_flags =
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio>
lib_deps =
@@ -139,10 +141,11 @@ extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
-D ADVERT_NAME='"Heltec Repeater"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MESH_PACKET_LOGGING=1
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<../examples/simple_repeater>
@@ -157,8 +160,8 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
build_flags =
${Heltec_lora32_v3.build_flags}
-D ADVERT_NAME='"Heltec Room"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1

View File

@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5

View File

@@ -6,10 +6,12 @@
#include <helpers/HeltecV3Board.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern HeltecV3Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();

View File

@@ -39,13 +39,14 @@ build_flags =
${LilyGo_T3S3_sx1262.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"T3S3-1262 Repeater"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_T3S3_sx1262.lib_deps}
@@ -71,14 +72,14 @@ build_flags =
${LilyGo_T3S3_sx1262.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"T3S3-1262 Room"'
-D ADVERT_LAT=-37.0
-D ADVERT_LON=145.0
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${LilyGo_T3S3_sx1262.lib_deps}
@@ -96,7 +97,7 @@ build_flags =
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${LilyGo_T3S3_sx1262.lib_deps}
@@ -117,7 +118,7 @@ build_flags =
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${LilyGo_T3S3_sx1262.lib_deps}

View File

@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5

View File

@@ -6,10 +6,12 @@
#include <helpers/ESP32Board.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern ESP32Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();

View File

@@ -27,20 +27,39 @@ build_flags =
${LilyGo_TBeam.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D MAX_CONTACTS=100
-D MAX_GROUP_CHANNELS=1
-D MAX_GROUP_CHANNELS=8
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D MESH_PACKET_LOGGING=1
-D MESH_DEBUG=1
-D RADIOLIB_DEBUG_BASIC=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
; -D RADIOLIB_DEBUG_BASIC=1
; -D ENABLE_PRIVATE_KEY_IMPORT=1
; -D ENABLE_PRIVATE_KEY_EXPORT=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TBeam.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${LilyGo_TBeam.lib_deps}
densaugeo/base64 @ ~1.4.0
densaugeo/base64 @ ~1.4.0
[env:Tbeam_repeater]
extends = LilyGo_TBeam
build_flags =
${LilyGo_TBeam.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Tbeam Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TBeam.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}

View File

@@ -5,15 +5,16 @@ TBeamBoard board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
#endif
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5

View File

@@ -6,10 +6,12 @@
#include <helpers/TBeamBoard.h>
#include <helpers/CustomSX1276Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern TBeamBoard board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();

View File

@@ -0,0 +1,69 @@
[LilyGo_TBeam_SX1262]
extends = esp32_base
board = ttgo-t-beam
build_flags =
${esp32_base.build_flags}
-I variants/lilygo_tbeam_SX1262
-D LILYGO_TBEAM_SX1262
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D SX126X_RX_BOOSTED_GAIN=1
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
-D P_LORA_TX_LED=4
-D PIN_BOARD_SDA=21
-D PIN_BOARD_SCL=22
-D PIN_USER_BTN=38
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/lilygo_tbeam_SX1262>
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
lib_deps =
${esp32_base.lib_deps}
lewisxhe/XPowersLib@^0.2.7
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:Tbeam_SX1262_companion_radio_ble]
extends = LilyGo_TBeam_SX1262
board_build.upload.maximum_ram_size=2000000
build_flags =
${LilyGo_TBeam_SX1262.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D MAX_CONTACTS=100
-D MAX_GROUP_CHANNELS=8
-D BLE_PIN_CODE=123456
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
; -D RADIOLIB_DEBUG_BASIC=1
; -D ENABLE_PRIVATE_KEY_IMPORT=1
; -D ENABLE_PRIVATE_KEY_EXPORT=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/companion_radio>
lib_deps =
${LilyGo_TBeam_SX1262.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Tbeam_SX1262_repeater]
extends = LilyGo_TBeam_SX1262
build_flags =
${LilyGo_TBeam_SX1262.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Tbeam SX1262 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=8
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TBeam_SX1262.lib_deps}
${esp32_ota.lib_deps}

View File

@@ -0,0 +1,66 @@
#include <Arduino.h>
#include "target.h"
TBeamBoardSX1262 board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5
#endif
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
#else
float tcxo = 1.6f;
#endif
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
return false; // fail
}
radio.setCRC(1);
return true; // success
}
uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}
void radio_set_tx_power(uint8_t dbm) {
radio.setOutputPower(dbm);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}

View File

@@ -0,0 +1,20 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/RadioLibWrappers.h>
#include <helpers/TBeamBoardSX1262.h>
#include <helpers/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
extern TBeamBoardSX1262 board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(uint8_t dbm);
mesh::LocalIdentity radio_new_identity();

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