Compare commits

..

67 Commits

Author SHA1 Message Date
mverch67
1f47d4b179 set TCXO to 2.4V 2025-02-11 16:46:00 +01:00
Ben Meadors
add75f073a Littlefs per device 2025-02-10 15:14:08 -06:00
Austin
a00e272afe meshtasticd: Add X11 480x480 preset (#6020) 2025-02-10 14:29:45 -06:00
Manuel
48b00188fb Fix T-Deck SD card detection (#6023)
* increase T-Deck PSRAM to avoid too early out-of-memory when messages fill up the storage

* fix SDCard for T-Deck; allow SPI frequency config
2025-02-10 07:41:37 -06:00
mverch67
62058b650a update device-ui lib 2025-02-10 14:10:43 +01:00
mverch67
147416045a increase T-Deck PSRAM to avoid too early out-of-memory when messages fill up the storage 2025-02-10 13:40:35 +01:00
mverch67
72e991104e Merge branch '2.6' of https://github.com/meshtastic/firmware into 2.6 2025-02-09 22:01:59 +01:00
Manuel
26a0612e37 fixed Indicator touch issue (causing IO expander issues), added more RAM (#6013) 2025-02-08 18:06:03 +01:00
mverch67
6cfa5d8f29 update lib 2025-02-08 17:49:32 +01:00
mverch67
2e21e49144 fixed Indicator touch issue (causing IO expander issues), added more RAM 2025-02-08 17:49:16 +01:00
Tom Fifield
aad9e352b7 Merge branch 'master' into 2.6 2025-02-08 20:04:12 +08:00
ChangYanChu
d70a9392af improve UTF-8 string handling in JSONValue (#6011)
```text
feat(json): improve UTF-8 string handling in JSONValue

- Add proper UTF-8 multi-byte character sequence handling
- Add boundary checks for UTF-8 sequences
- Keep original code structure and flow
- Add detailed comments for UTF-8 processing logic

This change improves the robustness of JSON string handling
while maintaining compatibility with existing code.
2025-02-08 20:03:44 +08:00
Ben Meadors
368d811ea6 Exclude unphone tft for now. Something is wonky 2025-02-07 20:07:54 -06:00
Ben Meadors
2996a9616f Add -tft environments to the ci matrix 2025-02-07 19:07:38 -06:00
rcarteraz
99e47cf73c Merge pull request #6007 from meshtastic/update-indicator-speed-again
Update Indicator upload_speed... again
2025-02-07 15:43:16 -07:00
rcarteraz
102c328436 Merge branch '2.6' into update-indicator-speed-again 2025-02-07 15:41:21 -07:00
Ben Meadors
0cd4224033 Un-extra 2025-02-07 16:35:38 -06:00
rcarteraz
20743ae2c4 Merge branch '2.6' into update-indicator-speed-again 2025-02-07 14:09:05 -07:00
rcarteraz
51c5d8ce93 tested higher speed and it works 2025-02-07 14:02:11 -07:00
rcarteraz
b229d351cf Update platformio.ini (#6006) 2025-02-07 14:32:09 -06:00
Ben Meadors
350b82bc08 Version this 2025-02-07 13:32:48 -06:00
Manuel
d21b272680 fancy T-Deck / SenseCAP Indicator / unPhone / PICOmputer-S3 TFT screen (#3259)
* lib update: light theme

* fix merge issue

* lib update: home buttons + button try-fix

* lib update: icon color fix

* lib update: fix instability/crash on notification

* update lib: timezone

* timezone label

* lib update: fix set owner

* fix spiLock in RadioLibInterface

* add picomputer tft build

* picomputer build

* fix compiler error std::find()

* fix merge

* lib update: theme runtime config

* lib update: packet logger + T-Deck Plus

* lib update: mesh detector

* lib update: fix brightness & trackball crash

* try-fix less paranoia

* sensecap indicator updates

* lib update: indicator fix

* lib update: statistic & some fixes

* lib-update: other T-Deck touch driver

* use custom touch driver for Indicator

* lower tft task prio

* prepare LVGL ST7789 driver

* lib update: try-fix audio

* Drop received packets from self

* Additional decoded packet ignores

* Honor flip & color for Heltec T114 and T190 (#4786)

* Honor TFT_MESH color if defined for Heltec T114 or T190

* Temporary: point lib_deps at fork of Heltec's ST7789 library
For demo only, until ST7789 is merged

* Update lib_deps; tidy preprocessor logic

* Download debian files after firmware zip

* set title for protobufs bump PR (#4792)

* set title for version bump PR (#4791)

* Enable Dependabot

* chore: trunk fmt

* fix dependabot syntax (#4795)

* fix dependabot syntax

* Update dependabot.yml

* Update dependabot.yml

* Bump peter-evans/create-pull-request from 6 to 7 in /.github/workflows (#4797)

* Bump docker/build-push-action from 5 to 6 in /.github/workflows (#4800)

* Actions: Semgrep Images have moved from returntocorp to semgrep (#4774)

https://hub.docker.com/r/returntocorp/semgrep notes: "We've moved!
 Official Docker images for Semgrep now available at semgrep/semgrep."

Patch updates our CI workflow for these images.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Bump meshtestic from `31ee3d9` to `37245b3` (#4799)

Bumps [meshtestic](https://github.com/meshtastic/meshTestic) from `31ee3d9` to `37245b3`.
- [Commits](31ee3d90c8...37245b3d61)

---
updated-dependencies:
- dependency-name: meshtestic
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* [create-pull-request] automated change (#4789)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Bump pnpm/action-setup from 2 to 4 in /.github/workflows (#4798)

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 4.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2...v4)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Raspberry Pico2 - needs protos

* Re-order doDeepSleep (#4802)

Make sure PMU sleep takes place before I2C ends

* [create-pull-request] automated change

* heltec-wireless-bridge
requires Proto PR first

* feat: trigger class update when protobufs are changed

* meshtastic/ is a test suite; protobufs/ contains protobufs;

* Update platform-native to pick up portduino crash fix (#4807)

* Hopefully extract and commit to meshtastic.github.io

* CI fixes

* [Board] DIY "t-energy-s3_e22" (#4782)

* New variant "t-energy-s3_e22"

- Lilygo T-Energy-S3
- NanoVHF "Mesh-v1.06-TTGO-T18" board
- Ebyte E22 Series

* add board_level = extra

* Update variant.h

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>

* Consolidate variant build steps (#4806)

* poc: consolidate variant build steps

* use build-variant action

* only checkout once and clean up after run

* Revert "Consolidate variant build steps (#4806)" (#4816)

This reverts commit 9f8d86cb25.

* Make Ublox code more readable (#4727)

* Simplify Ublox code

Ublox comes in a myriad of versions and settings. Presently our
configuration code does a lot of branching based on versions being
or not being present.

This patch adds version detection earlier in the piece and branches
on the set gnssModel instead to create separate setup methods for Ublox 6,
Ublox 7/8/9, and Ublox10.

Additionally, adds a macro to make the code much shorter and more
readable.

* Make trunk happy

* Make trunk happy

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Consider the LoRa header when checking packet length

* Minor fix (#4666)

* Minor fixes

It turns out setting a map value with the index notation causes
an lookup that can be avoided with emplace. Apply this to one line in
the StoreForward module.

Fix also Cppcheck-determined highly minor performance increase by
passing gpiochipname as a const reference :)

The amount of cycles used on this laptop while learning about these
callouts from cppcheck is unlikely to ever be more than the cycles
saved by the fixes ;)

* Update PortduinoGlue.cpp

* Revert "Update classes on protobufs update" (#4824)

* Revert "Update classes on protobufs update"

* remove quotes to fix trunk.

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>

* Implement optional second I2C bus for NRF52840
Enabled at compile-time if WIRE_INFERFACES_COUNT defined as 2

* Add I2C bus to Heltec T114 header pins
SDA: P0.13
SCL: P0.16

Uses bus 1, leaving bus 0 routed to the unpopulated footprint for the RTC (general future-proofing)

* Tidier macros

* Swap SDA and SCL
SDA=P0.16, SCL=P0.13

* Refactor and consolidate time window logic (#4826)

* Refactor and consolidate windowing logic

* Trunk

* Fixes

* More

* Fix braces and remove unused now variables.

There was a brace in src/mesh/RadioLibInterface.cpp that was breaking
compile on some architectures.

Additionally, there were some brace errors in
src/modules/Telemetry/AirQualityTelemetry.cpp
src/modules/Telemetry/EnvironmentTelemetry.cpp
src/mesh/wifi/WiFiAPClient.cpp

Move throttle include in WifiAPClient.cpp to top.

Add Default.h to sleep.cpp

rest of files just remove unused now variables.

* Remove a couple more meows

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>

* Rename message length headers and set payload max to 255 (#4827)

* Rename message length headers and set payload max to 255

* Add MESHTASTIC_PKC_OVERHEAD

* compare to MESHTASTIC_HEADER_LENGTH

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>

* Check for null before printing debug (#4835)

* fix merge

* try-fix crash

* lib update: fix neighbors

* fix GPIO0 mode after I2S audio

* lib update: audio fix

* lib update: fixes and improvements

* extra

* added ILI9342 (from master)

* device-ui persistency

* review update

* fix request, add handled

* fix merge issue

* fix merge issue

* remove newline

* remove newlines from debug log

* playing with locks; but needs more testing

* diy mesh-tab initial files

* board definition for mesh-tab (not yet used)

* use DISPLAY_SET_RESOLUTION to avoid hw dependency in code

* no telemetry for Indicator

* 16MB partition for Indicator

* 8MB partition for Indicator

* stability: add SPI lock before saving via littleFS

* dummy for config transfer (#5154)

* update indicator (due to compile and linker errors)

* remove faulty partition line

* fix missing include

* update indicator board

* update mesh-tab ILI9143 TFT

* fix naming

* mesh-tab targets

* try: disable duplicate locks

* fix nodeDB erase loop when free mem returns invalid value (0, -1).

* upgrade toolchain for nrf52 to gcc 9.3.1

* try-fix (workaround) T-Deck audio crash

* update mesh-tab tft configs

* set T-Deck audio to unused 48 (mem mclk)

* swap mclk to gpio 21

* update meshtab voltage divider

* update mesh-tab ini

* Fixed the issue that indicator device uploads via rp2040 serial port in some cases.

* Fixed the issue that the touch I2C address definition was not effective.

* Fixed the issue that the wifi configuration saved to RAM did not take effect.

* rotation fix; added ST7789 3.2" display

* dreamcatcher: assign GPIO44 to audio mclk

* mesh-tab touch updates

* add mesh-tab powersave as default

* fix DIO1 wakeup

* mesh-tab: enable alert message menu

* Streamline board definitions for first tech preview. (#5390)

* Streamline board definitions for first tech preview. TBD: Indicator Support

* add point-of-checkin

* use board/unphone.json

---------

Co-authored-by: mverch67 <manuel.verch@gmx.de>

* fix native targets

* add RadioLib debugging options for (T-Deck)

* fix T-Deck build

* fix native tft targets for rpi

* remove wrong debug defines

* t-deck-tft button is handled in device-ui

* disable default lightsleep for indicator

* Windows Support - Trunk and Platformio (#5397)

* Add support for GPG

* Add usb device support

* Add trunk.io to devcontainer

* Trunk things

* trunk fmt

* formatting

* fix trivy/DS002, checkov/CKV_DOCKER_3

* hide docker extension popup

* fix trivy/DS026, checkov/CKV_DOCKER_2

* fix radioLib warnings for T-Deck target

* wake screen with button only

* use custom touch driver

* define wake button for unphone

* use board definition for mesh-tab

* mesh-tab rotation upside-down

* update platform native

* use MESH_TAB hardware model definition

* radioLib update (fix crash/assert)

* reference seeed indicator fix commit arduino-esp32

* Remove unneeded file change :)

* disable serial module and tcp socket api for standalone devices (#5591)

* disable serial module and tcp socket api for standalone devices
* just disable webserver, leave wifi available
* disable socket api

* mesh-tab: lower I2C touch frequency

* log error when packet queue is full

* add more locking for shared SPI devices (#5595)

* add more locking for shared SPI devices
* call initSPI before the lock is used
* remove old one
* don't double lock
* Add missing unlock
* More missing unlocks
* Add locks to SafeFile, remove from `readcb`, introduce some LockGuards
* fix lock in setupSDCard()
* pull radiolib trunk with SPI-CS fixes
* change ContentHandler to Constructor type locks, where applicable

---------

Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: GUVWAF <thijs@havinga.eu>
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>

* T-Deck: revert back to lovyanGFX touch driver

* T-Deck: increase allocated PSRAM by 50%

* mesh-tab: streamline target definitions

* update RadioLib 7.1.2

* mesh-tab: fix touch rotation 4.0 inch display

* Mesh-Tab platformio: 4.0inch: increase SPI frequency to max

* mesh-tab: fix rotation for 3.5 IPS capacitive display

* mesh-tab: fix rotation for 3.2 IPS capacitive display

* restructure device-ui library into sub-directories

* preparations for generic DisplayDriverFactory

* T-Deck: increase LVGL memory size

* update lib

* trunk fmt

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: todd-herbert <herbert.todd@gmail.com>
Co-authored-by: Jason Murray <15822260+scruplelesswizard@users.noreply.github.com>
Co-authored-by: Jason Murray <jason@chaosaffe.io>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Austin <vidplace7@gmail.com>
Co-authored-by: virgil <virgil.wang.cj@gmail.com>
Co-authored-by: Mark Trevor Birss <markbirss@gmail.com>
Co-authored-by: Kalle Lilja <15094562+ThatKalle@users.noreply.github.com>
Co-authored-by: GUVWAF <thijs@havinga.eu>
2025-02-07 13:05:17 -06:00
mverch67
fe5d251393 fix "native" compiler errors/warnings NodeDB.h 2025-02-07 18:09:46 +01:00
Ben Meadors
66a98fb062 2.6 <- Next hop router (#6005)
* Initial version of NextHopRouter

* Set original hop limit in header flags

* Short-circuit to FloodingRouter for broadcasts

* If packet traveled 1 hop, set `relay_node` as `next_hop` for the original transmitter

* Set last byte to 0xFF if it ended at 0x00
As per an idea of @S5NC

* Also update next-hop based on received DM for us

* temp

* Add 1 retransmission for intermediate hops when using NextHopRouter

* Add next_hop and relayed_by in PacketHistory for setting next-hop and handle flooding fallback

* Update protos, store multiple relayers

* Remove next-hop update logic from NeighborInfoModule

* Fix retransmissions

* Improve ACKs for repeated packets and responses

* Stop retransmission even if there's not relay node

* Revert perhapsRebroadcast()

* Remove relayer if we cancel a transmission

* Better checking for fallback to flooding

* Fix newlines in traceroute print logs

* Stop retransmission for original packet

* Use relayID

* Also when want_ack is set, we should try to retransmit

* Fix cppcheck error

* Fix 'router' not in scope error

* Fix another cppcheck error

* Check for hop_limit and also update next hop when `hop_start == hop_limit` on ACK
Also check for broadcast in `getNextHop()`

* Formatting and correct NUM_RETRANSMISSIONS

* Update protos

* Start retransmissions in NextHopRouter if ReliableRouter didn't do it

* Handle repeated/fallback to flooding packets properly
First check if it's not still in the TxQueue

* Guard against clients setting `next_hop`/`relay_node`

* Don't cancel relay if we were the assigned next-hop

* Replies (e.g. tapback emoji) are also a valid confirmation of receipt

---------

Co-authored-by: GUVWAF <thijs@havinga.eu>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
2025-02-07 06:29:36 -06:00
Ben Meadors
d65d9305d3 Merge branch 'master' into 2.6 2025-02-07 06:03:13 -06:00
dylanli
4e8c4f0d55 T1000-E hardware updates and GPS positioning accuracy optimisation (#6003)
* T1000-E button setting update

* T1000-E GNSS lock error fix

* T1000-E GNSS improve detection success

---------

Co-authored-by: WayenWeng <jinyuan.weng@seeed.cc>
2025-02-07 10:02:56 +02:00
github-actions[bot]
34e5cf0d96 [create-pull-request] automated change (#6002)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-06 20:26:27 -06:00
Ben Meadors
b28db07409 Back to 80 2025-02-06 20:01:18 -06:00
Ben Meadors
f753caf15d Update ref 2025-02-06 20:00:56 -06:00
lizthedeveloper
4a6a0efcfd log the nonce value at DEBUG instead of INFO (#6001)
you're leaking the nonce to stdout, if your logs are routed to a folder, this logs the nonce every time, leading to replay attack surface area being higher. Changed to debug.
2025-02-07 08:29:48 +08:00
github-actions[bot]
0c1838dde7 [create-pull-request] automated change (#6000)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-06 14:55:39 -06:00
Ben Meadors
b183febd3c Merge remote-tracking branch 'origin/master' into 2.6 2025-02-06 14:20:46 -06:00
github-actions[bot]
cb0519dd9c [create-pull-request] automated change (#5989)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-06 14:11:32 -06:00
Manuel
9db51a72a4 Fix T-Deck/T-Watch no BT (#5998)
fixes #5997
2025-02-06 14:11:17 -06:00
Tom
64def246ee Corrected some misinformation (#5995)
Change the module text too soon , before it had chance to reach a final conclusion.

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
2025-02-06 11:36:04 +08:00
Austin
1c8eb7ece3 meshtasticd: Fix web download location (#5993) 2025-02-05 15:19:22 -06:00
Austin
447533aae5 meshtasticd-debian: Remove existing deb builds (#5792)
Replaced with OpenSUSE Build Service
https://build.opensuse.org/project/show/network:Meshtastic
2025-02-04 07:38:54 -05:00
github-actions[bot]
1b457bcfbb [create-pull-request] automated change (#5985)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-03 20:47:51 -06:00
Thomas Göttgens
ed07cc067a Merge branch 'master' of https://github.com/meshtastic/firmware 2025-02-03 16:48:23 +01:00
Thomas Göttgens
a3a295488c add firmware build script for use with docker 2025-02-03 16:48:10 +01:00
Thomas Göttgens
5c17afb2ac Clean up some legacy macro definitions (#5983) 2025-02-03 09:36:05 -06:00
Tom Fifield
8cacdb65d6 Fix INA226 Sensor Voltage Readings (#5972)
They were off by a factor of 1000 due to the difference between
Volts and MilliVolts, as reported by @morcant .

Fixes https://github.com/meshtastic/firmware/issues/5969
2025-02-03 08:39:42 -06:00
Tom
3a34f8beaf E80 promicro update (#5967)
* add readme and update rfswitch

* Updated readme to include all data from Ebyte

* Added results from switch testing & notes thereon

* fixed picture

* Whoops!

Forgot to uncomment some settings from test.

* Update readme.md

* Delete variants/diy/nrf52_promicro_diy_tcxo/E80_RSSI_per_case.png

* Add webp image to appease trunk

* Update readme.md

* Trunky trunk trunk

* Clang and the trunk is done
2025-02-03 20:16:35 +08:00
GUVWAF
d740934278 Don't rate-limit position requests for Lost and Found role (#5981) 2025-02-03 19:24:47 +08:00
Woutvstk
b370717dcd Add bearing to other node on device screen in text (#5968)
* Merge branch 'store-and-forward' of https://github.com/Woutvstk/meshtastic_firmware into store-and-forward

* also show bearing to a waypoint in text on screen
2025-02-03 13:43:32 +08:00
Chloe Bethel
d9534cfc9d Remove unused usages of #include <iostream> to save Flash (#5978)
Saves ~100KB on wio-e5, which was previously at 99% Flash usage
2025-02-03 11:31:54 +08:00
Austin
4c0e0b8471 Portduino: Set Web SSL Cert / Key paths from yaml (#5961) 2025-02-01 16:58:58 +08:00
Ben Meadors
b5cad2b65e Fix negative decimal value detection in userPrefs (#5963) 2025-01-29 20:52:24 -06:00
Jason P
cd8592ef4a Fixes #5766 Updated MQTT privateCidrRanges to add Tailscale (#5957) 2025-01-29 06:14:43 -06:00
Austin
78da8f6fc4 Portduino: Allow limiting TX Power from yaml (#5954) 2025-01-29 12:51:26 +01:00
Tom Fifield
6a12760c3d Fix off-by-one error with log writes (#5959)
As reported by @jstockdale, when writing coloured logs we were
writing the full string, including a null terminator. This caused
issues for programs consuming our logs. The fix as identified is
not to write the null.

Fixes https://github.com/meshtastic/firmware/issues/5945
2025-01-28 19:57:52 -06:00
Thomas Göttgens
30a31a3a13 Oem logo (#5939)
* reinstate oemlogo, add to userPrefs.jsonc

* disable from default build
2025-01-28 08:38:22 -06:00
Manuel
2d42e1b2bc fix: TCXO_OPTIONAL featuring SenseCAP Indicator (V1/V2) (#5948)
* fix TCXO_OPTIONAL

* fix LOG_WARN

* fix lora.begin() returns -707

* trunk fmt
2025-01-27 14:00:12 -06:00
GUVWAF
4747e73f37 Space out periodic broadcasts of modules automatically (#5931)
* Space out periodic broadcasts of modules automatically

* Add warning for function usage

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-01-26 13:59:59 -06:00
Aleksey Vasilenko
10d553087c Add missing build_unflags (#5941)
Fixes 'undefined reference to app_main' build error for
my_esp32s3_diy_eink and my_esp32s3_diy_oled variants.
2025-01-26 16:54:26 +08:00
Ben Meadors
7649e70585 Revert "No focus on new messages if auto-carousel is off (#5881)" (#5936)
This reverts commit 0f981153eb.
2025-01-25 12:01:25 -06:00
GUVWAF
a14346bc4f Rate limit position replies to three minutes (#5932) 2025-01-25 09:24:24 -06:00
github-actions[bot]
fd56995764 [create-pull-request] automated change (#5928)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-01-25 07:53:24 -06:00
Ben Meadors
87601f4760 File system persistence fixes 2025-01-12 19:03:21 -06:00
GUVWAF
0e3c419652 Explicitly set CAD symbols, improve slot time calculation and adjust CW size accordingly (#5772) 2025-01-12 17:52:35 +01:00
Ben Meadors
891bf643e2 Remove logging statement no longer needed 2025-01-12 09:13:14 -06:00
Ben Meadors
143cdf4572 Make NodeDatabase (and file) independent of DeviceState (#5813)
* Make NodeDatabase (and file) independent of DeviceState

* 70
2025-01-11 09:02:05 -06:00
Ben Meadors
d440dbd522 Hello world support for UDP broadcasts over the LAN on ESP32 (#5779)
* UDP local area network meshing on ESP32

* Logs

* Comment

* Update UdpMulticastThread.h

* Changes

* Only use router->send
2025-01-11 09:01:49 -06:00
Ben Meadors
7e063c1dda Merge branch 'master' into 2.6 2025-01-10 06:27:18 -06:00
github-actions[bot]
988d8cd1ab [create-pull-request] automated change (#5789)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-01-07 20:44:52 -06:00
Ben Meadors
c98c682cff Merge branch 'master' into 2.6 2025-01-07 20:39:51 -06:00
Ben Meadors
8023fec0ec 2.6 protos 2025-01-07 19:58:00 -06:00
117 changed files with 2054 additions and 1046 deletions

View File

@@ -1,38 +0,0 @@
name: Build Native
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-native:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Setup native build
id: base
uses: ./.github/actions/setup-native
- name: Build Native
run: bin/build-native.sh
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-native-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/meshtasticd_linux_x86_64
bin/config-dist.yaml

View File

@@ -1,52 +0,0 @@
name: Build Raspbian
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-raspbian:
runs-on: [self-hosted, linux, ARM64]
steps:
- name: Install libbluetooth
shell: bash
run: |
sudo apt-get update -y --fix-missing
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Raspbian
run: bin/build-native.sh
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/meshtasticd_linux_aarch64
bin/config-dist.yaml

View File

@@ -1,52 +0,0 @@
name: Build Raspbian Arm
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-raspbian-armv7l:
runs-on: [self-hosted, linux, ARM]
steps:
- name: Install libbluetooth
shell: bash
run: |
sudo apt-get update -y --fix-missing
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Raspbian
run: bin/build-native.sh
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
overwrite: true
path: |
release/meshtasticd_linux_armv7l
bin/config-dist.yaml

View File

@@ -128,15 +128,6 @@ jobs:
with:
board: ${{ matrix.board }}
package-raspbian:
uses: ./.github/workflows/package_raspbian.yml
package-raspbian-armv7l:
uses: ./.github/workflows/package_raspbian_armv7l.yml
package-native:
uses: ./.github/workflows/package_amd64.yml
build-debian-src:
uses: ./.github/workflows/build_debian_src.yml
with:
@@ -158,7 +149,7 @@ jobs:
docker-alpine-amd64:
uses: ./.github/workflows/docker_build.yml
with:
distro: debian
distro: alpine
platform: linux/amd64
runs-on: ubuntu-24.04
push: false
@@ -288,14 +279,7 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' }}
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
needs:
[
gather-artifacts,
package-raspbian,
package-raspbian-armv7l,
package-native,
build-debian-src,
]
needs: [gather-artifacts, build-debian-src]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -324,13 +308,6 @@ jobs:
body: |
Autogenerated by github action, developer should edit as required before publishing...
- name: Download deb files
uses: actions/download-artifact@v4
with:
pattern: meshtasticd_${{ steps.version.outputs.long }}_*.deb
merge-multiple: true
path: ./output
- name: Download source deb
uses: actions/download-artifact@v4
with:
@@ -346,11 +323,8 @@ jobs:
- name: Display structure of downloaded files
run: ls -lR
- name: Add deb files to release
- name: Add source deb to release
run: |
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,90 +0,0 @@
name: Package Native
on:
workflow_call:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
build-native:
uses: ./.github/workflows/build_native.yml
package-native:
runs-on: ubuntu-22.04
needs: build-native
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: firmware-native-${{ steps.version.outputs.long }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/etc/meshtasticd/config.d
mkdir -p .debpkg/etc/meshtasticd/available.d
mkdir -p .debpkg/usr/lib/systemd/system/
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
shopt -s dotglob nullglob
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
gunzip .debpkg/usr/share/meshtasticd/web/ -r
cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
chmod +x .debpkg/DEBIAN/preinst
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
chmod +x .debpkg/DEBIAN/postinst
- uses: jiro4989/build-deb-action@v3
with:
package: meshtasticd
package_root: .debpkg
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
arch: amd64
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
with:
name: meshtasticd_${{ steps.version.outputs.long }}_amd64.deb
overwrite: true
path: |
./*.deb

View File

@@ -1,90 +0,0 @@
name: Package Raspbian
on:
workflow_call:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
build-raspbian:
uses: ./.github/workflows/build_raspbian.yml
package-raspbian:
runs-on: ubuntu-22.04
needs: build-raspbian
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.long }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/etc/meshtasticd/config.d
mkdir -p .debpkg/etc/meshtasticd/available.d
mkdir -p .debpkg/usr/lib/systemd/system/
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
shopt -s dotglob nullglob
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
gunzip .debpkg/usr/share/meshtasticd/web/ -r
cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
chmod +x .debpkg/DEBIAN/preinst
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
chmod +x .debpkg/DEBIAN/postinst
- uses: jiro4989/build-deb-action@v3
with:
package: meshtasticd
package_root: .debpkg
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
arch: arm64
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
with:
name: meshtasticd_${{ steps.version.outputs.long }}_arm64.deb
overwrite: true
path: |
./*.deb

View File

@@ -1,90 +0,0 @@
name: Package Raspbian
on:
workflow_call:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
build-raspbian_armv7l:
uses: ./.github/workflows/build_raspbian_armv7l.yml
package-raspbian_armv7l:
runs-on: ubuntu-22.04
needs: build-raspbian_armv7l
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: firmware-raspbian-armv7l-${{ steps.version.outputs.long }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/etc/meshtasticd/config.d
mkdir -p .debpkg/etc/meshtasticd/available.d
mkdir -p .debpkg/usr/lib/systemd/system/
tar -xf build.tar -C .debpkg/usr/share/meshtasticd/web
shopt -s dotglob nullglob
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then mv .debpkg/usr/share/meshtasticd/web/build/* .debpkg/usr/share/meshtasticd/web/; fi
if [ -d .debpkg/usr/share/meshtasticd/web/build ]; then rmdir .debpkg/usr/share/meshtasticd/web/build; fi
if [ -d .debpkg/usr/share/meshtasticd/web/.DS_Store ]; then rm -f .debpkg/usr/share/meshtasticd/web/.DS_Store; fi
gunzip .debpkg/usr/share/meshtasticd/web/ -r
cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/ -r
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
# Transition /usr/share/doc/meshtasticd to /usr/share/meshtasticd
echo "rm -rf /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/preinst
chmod +x .debpkg/DEBIAN/preinst
echo "ln -sf /usr/share/meshtasticd /usr/share/doc/meshtasticd" > .debpkg/DEBIAN/postinst
chmod +x .debpkg/DEBIAN/postinst
- uses: jiro4989/build-deb-action@v3
with:
package: meshtasticd
package_root: .debpkg
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.long }} # refs/tags/v*.*.*
arch: armhf
depends: libyaml-cpp0.7, openssl, libulfius2.7, libi2c0
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
with:
name: meshtasticd_${{ steps.version.outputs.long }}_armhf.deb
overwrite: true
path: |
./*.deb

View File

@@ -37,9 +37,9 @@ jobs:
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
secrets: inherit
# hook-copr:
# uses: ./.github/workflows/hook_copr.yml
# with:
# copr_project: |-
# ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
# secrets: inherit
hook-copr:
uses: ./.github/workflows/hook_copr.yml
with:
copr_project: |-
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
secrets: inherit

3
.gitmodules vendored
View File

@@ -1,6 +1,9 @@
[submodule "protobufs"]
path = protobufs
url = https://github.com/meshtastic/protobufs.git
[submodule "lib/device-ui"]
path = lib/device-ui
url = https://github.com/meshtastic/device-ui.git
[submodule "meshtestic"]
path = meshtestic
url = https://github.com/meshtastic/meshTestic

View File

@@ -38,7 +38,8 @@ USER root
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d
&& mkdir -p /etc/meshtasticd/config.d \
&& mkdir -p /etc/meshtasticd/ssl
# Fetch compiled binary from the builder
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/

View File

@@ -29,7 +29,8 @@ USER root
RUN apk add libstdc++ libgpiod yaml-cpp libusb i2c-tools \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d
&& mkdir -p /etc/meshtasticd/config.d \
&& mkdir -p /etc/meshtasticd/ssl
COPY --from=builder /tmp/firmware/release/meshtasticd /usr/sbin/
WORKDIR /var/lib/meshtasticd

View File

@@ -37,6 +37,7 @@ build_flags =
-DLIBPAX_ARDUINO
-DLIBPAX_WIFI
-DLIBPAX_BLE
-DHAS_UDP_MULTICAST=1
;-DDEBUG_HEAP
lib_deps =

View File

@@ -18,6 +18,7 @@ build_src_filter =
lib_ignore =
BluetoothOTA
lvgl
lib_deps =
${arduino_base.lib_deps}

View File

@@ -35,11 +35,11 @@ cp $SRCBIN $OUTDIR/$basename-update.bin
echo "Building Filesystem for ESP32 targets"
pio run --environment $1 -t buildfs
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$VERSION.bin
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
# Remove webserver files from the filesystem and rebuild
ls -l data/static # Diagnostic list of files
rm -rf data/static
pio run --environment $1 -t buildfs
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$VERSION.bin
cp .pio/build/$1/littlefs.bin $OUTDIR/littlefs-$1-$VERSION.bin
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
cp bin/device-update.* $OUTDIR

18
bin/build-firmware.sh Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
export PIP_BREAK_SYSTEM_PACKAGES=1
if (echo $2 | grep -q "esp32"); then
bin/build-esp32.sh $1
elif (echo $2 | grep -q "nrf52"); then
bin/build-nrf52.sh $1
elif (echo $2 | grep -q "stm32"); then
bin/build-stm32.sh $1
elif (echo $2 | grep -q "rpi2040"); then
bin/build-rpi2040.sh $1
else
echo "Unknown target $2"
exit 1
fi

View File

@@ -78,6 +78,8 @@ Lora:
# TXen: x # TX and RX enable pins
# RXen: x
# SX126X_MAX_POWER: 8 # Limit the output power to 8 dBm, useful for amped nodes
# spiSpeed: 2000000
### Set default/fallback gpio chip to use in /dev/. Defaults to 0.
@@ -182,10 +184,12 @@ Logging:
Webserver:
# Port: 443 # Port for Webserver & Webservices
# RootPath: /usr/share/meshtasticd/web # Root Dir of WebServer
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
General:
MaxNodes: 200
MaxMessageQueue: 100
ConfigDirectory: /etc/meshtasticd/config.d/
# MACAddress: AA:BB:CC:DD:EE:FF
# MACAddressSource: eth0
# MACAddressSource: eth0

View File

@@ -0,0 +1,4 @@
Display:
Panel: X11
Width: 480
Height: 480

View File

@@ -7,3 +7,6 @@ Lora:
TXen: 13
RXen: 12
DIO3_TCXO_VOLTAGE: true
# Only for E22-900M33S:
# Limit the output power to 8 dBm
# SX126X_MAX_POWER: 8

View File

@@ -35,6 +35,9 @@ for subdir, dirs, files in os.walk(rootdir):
outlist.append(section)
else:
outlist.append(section)
# Add the TFT variants if the base variant is selected
elif section.replace("-tft", "") in outlist and config[config[c].name].get("board_level") != "extra":
outlist.append(section)
if "board_check" in config[config[c].name]:
if (config[config[c].name]["board_check"] == "true") & (
"check" in options
@@ -43,4 +46,4 @@ for subdir, dirs, files in os.walk(rootdir):
if ("quick" in options) & (len(outlist) > 3):
print(json.dumps(random.sample(outlist, 3)))
else:
print(json.dumps(outlist))
print(json.dumps(outlist))

View File

@@ -102,7 +102,7 @@ pref_flags = []
for pref in userPrefs:
if userPrefs[pref].startswith("{"):
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
elif userPrefs[pref].replace(".", "").isdigit():
elif userPrefs[pref].lstrip("-").replace(".", "").isdigit():
pref_flags.append("-D" + pref + "=" + userPrefs[pref])
elif userPrefs[pref] == "true" or userPrefs[pref] == "false":
pref_flags.append("-D" + pref + "=" + userPrefs[pref])

6
debian/changelog vendored
View File

@@ -1,7 +1,9 @@
meshtasticd (2.5.20.0) UNRELEASED; urgency=medium
meshtasticd (2.5.22.0) UNRELEASED; urgency=medium
* Initial packaging
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Wed, 15 Jan 2025 14:08:54 +0000
-- Austin Lane <github-actions[bot]@users.noreply.github.com> Wed, 05 Feb 2025 01:10:33 +0000

View File

@@ -11,7 +11,7 @@ platformio pkg install -e native -t platformio/tool-scons@4.40502.0
tar -cf pio.tar pio/
rm -rf pio
# Download the latest meshtastic/web release build.tar to `web.tar`
curl -L https://github.com/meshtastic/web/releases/download/latest/build.tar -o web.tar
curl -L https://github.com/meshtastic/web/releases/latest/download/build.tar -o web.tar
package=$(dpkg-parsechangelog --show-field Source)

View File

@@ -1,4 +1,5 @@
etc/meshtasticd
etc/meshtasticd/config.d
etc/meshtasticd/available.d
usr/share/meshtasticd/web
usr/share/meshtasticd/web
etc/meshtasticd/ssl

1
lib/device-ui Submodule

Submodule lib/device-ui added at eb37529599

View File

@@ -21,7 +21,7 @@ Summary: Meshtastic daemon for communicating with Meshtastic devices
License: GPL-3.0
URL: https://github.com/meshtastic/firmware
Source0: {{{ git_dir_pack }}}
Source1: https://github.com/meshtastic/web/releases/download/latest/build.tar
Source1: https://github.com/meshtastic/web/releases/latest/download/build.tar
BuildRequires: systemd-rpm-macros
BuildRequires: python3-devel
@@ -72,6 +72,8 @@ install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.s
# Install the web files under /usr/share/meshtasticd/web
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
# Install default SSL storage directory (for web)
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
%files
%license LICENSE
@@ -86,6 +88,7 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
%dir %{_datadir}/meshtasticd
%dir %{_datadir}/meshtasticd/web
%{_datadir}/meshtasticd/web/*
%dir %{_sysconfdir}/meshtasticd/ssl
%changelog
%autochangelog

View File

@@ -23,6 +23,10 @@ SPIClass SPI1(HSPI);
#define SDHandler SPI
#endif
#ifndef SD_SPI_FREQUENCY
#define SD_SPI_FREQUENCY 4000000U
#endif
#endif // HAS_SDCARD
#if defined(ARCH_STM32WL)
@@ -361,8 +365,7 @@ void setupSDCard()
#ifdef HAS_SDCARD
concurrency::LockGuard g(spiLock);
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler)) {
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
LOG_DEBUG("No SD_MMC card detected");
return;
}

View File

@@ -79,17 +79,17 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
}
if (color && logLevel != nullptr) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 6);
Print::write("\u001b[34m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
Print::write("\u001b[32m", 6);
Print::write("\u001b[32m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
Print::write("\u001b[33m", 6);
Print::write("\u001b[33m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
Print::write("\u001b[31m", 6);
Print::write("\u001b[31m", 5);
}
len = Print::write(printBuf, len);
if (color && logLevel != nullptr) {
Print::write("\u001b[0m", 5);
Print::write("\u001b[0m", 4);
}
return len;
}
@@ -107,15 +107,15 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
// include the header
if (color) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 6);
Print::write("\u001b[34m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
Print::write("\u001b[32m", 6);
Print::write("\u001b[32m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
Print::write("\u001b[33m", 6);
Print::write("\u001b[33m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
Print::write("\u001b[31m", 6);
Print::write("\u001b[31m", 5);
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
Print::write("\u001b[35m", 6);
Print::write("\u001b[35m", 5);
}
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
@@ -393,4 +393,4 @@ std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
break;
}
return std::string(formatted.get());
}
}

View File

@@ -449,7 +449,22 @@ bool GPS::setup()
if (!didSerialInit) {
int msglen = 0;
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
#ifdef TRACKER_T1000_E
// add power up/down strategy, improve ag3335 detection success
digitalWrite(PIN_GPS_EN, LOW);
delay(500);
digitalWrite(GPS_VRTC_EN, LOW);
delay(1000);
digitalWrite(GPS_VRTC_EN, HIGH);
delay(500);
digitalWrite(PIN_GPS_EN, HIGH);
delay(1000);
#endif
#ifdef TRACKER_T1000_E
if (probeTries < 5) {
#else
if (probeTries < 2) {
#endif
LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
@@ -460,7 +475,11 @@ bool GPS::setup()
}
}
// Rare Serial Speeds
#ifdef TRACKER_T1000_E
if (probeTries == 5) {
#else
if (probeTries == 2) {
#endif
LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]);
gnssModel = probe(rareSerialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
@@ -772,6 +791,9 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
setPowerPMU(true); // Power (PMU): on
writePinStandby(false); // Standby (pin): awake (not standby)
setPowerUBLOX(true); // Standby (UBLOX): awake
#ifdef GNSS_AIROHA
lastFixStartMsec = 0;
#endif
break;
case GPS_SOFTSLEEP:

View File

@@ -238,7 +238,7 @@ void EInkDynamicDisplay::checkRateLimiting()
// Skip update: too soon for BACKGROUND
if (frameFlags == BACKGROUND) {
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_BACKGROUND_SEC * 1000)) {
if (Throttle::isWithinTimespanMs(previousRunMs, 30000)) {
refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FULL;
return;
@@ -251,7 +251,7 @@ void EInkDynamicDisplay::checkRateLimiting()
// Skip update: too soon for RESPONSIVE
if (frameFlags & RESPONSIVE) {
if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000)) {
if (Throttle::isWithinTimespanMs(previousRunMs, 1000)) {
refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FAST;
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x", frameFlags);

View File

@@ -123,7 +123,7 @@ static bool heartbeat = false;
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
/// Check if the display can render a string (detect special chars; emoji)
// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
#if defined(OLED_PL) || defined(OLED_UA) || defined(OLED_RU) || defined(OLED_CS)
@@ -162,11 +162,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
#ifdef USERPREFS_SPLASH_TITLE
const char *title = USERPREFS_SPLASH_TITLE;
#else
const char *title = "meshtastic.org";
#endif
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
@@ -185,6 +181,56 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
#ifdef USERPREFS_OEM_TEXT
static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
switch (USERPREFS_OEM_FONT_SIZE) {
case 0:
display->setFont(FONT_SMALL);
break;
case 2:
display->setFont(FONT_LARGE);
break;
default:
display->setFont(FONT_MEDIUM);
break;
}
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *title = USERPREFS_OEM_TEXT;
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
// Draw version and shortname in upper right
char buf[25];
snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(x + SCREEN_WIDTH, y + 0, buf);
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// Draw region in upper left
const char *region = myRegion ? myRegion->name : NULL;
drawOEMIconScreen(region, display, state, x, y);
}
#endif
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
{
uint16_t x_offset = display->width() / 2;
@@ -1400,9 +1446,9 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
static char distStr[20];
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data
strncpy(distStr, "? mi", sizeof(distStr)); // might not have location data
} else {
strncpy(distStr, "? km", sizeof(distStr));
strncpy(distStr, "? km", sizeof(distStr));
}
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
@@ -1435,18 +1481,6 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
float d =
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
else
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
}
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
@@ -1454,6 +1488,23 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2*PI : bearingToOther;
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
else
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET, bearingToOtherDegrees);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
else
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
}
}
}
if (!hasNodeHeading) {
@@ -1658,6 +1709,10 @@ void Screen::setup()
// Set the utf8 conversion function
dispdev->setFontTableLookupFunction(customFontTableLookup);
#ifdef USERPREFS_OEM_TEXT
logo_timeout *= 2; // Double the time if we have a custom logo
#endif
// Add frames.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
@@ -1803,6 +1858,22 @@ int32_t Screen::runOnce()
showingBootScreen = false;
}
#ifdef USERPREFS_OEM_TEXT
static bool showingOEMBootScreen = true;
if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
LOG_INFO("Switch to OEM screen...");
// Change frames.
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
ui->setFrames(bootOEMFrames, bootOEMFrameCount);
ui->update();
#ifndef USE_EINK
ui->update();
#endif
showingOEMBootScreen = false;
}
#endif
#ifndef DISABLE_WELCOME_UNSET
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
setWelcomeFrames();
@@ -2662,13 +2733,14 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
{
// If auto carousel is disabled -> return 0 and skip new messages handling
if (config.display.auto_screen_carousel_secs == 0)
return 0;
// Handle focus change based on message type
if (showingNormalScreen) {
setFrames(packet->from == 0 ? FOCUS_PRESERVE : FOCUS_TEXTMESSAGE);
// Outgoing message
if (packet->from == 0)
setFrames(FOCUS_PRESERVE); // Return to same frame (quietly hiding the rx text message frame)
// Incoming message
else
setFrames(FOCUS_TEXTMESSAGE); // Focus on the new message
}
return 0;
@@ -2755,4 +2827,4 @@ int Screen::handleAdminMessage(const meshtastic_AdminMessage *arg)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN
#endif // HAS_SCREEN

View File

@@ -115,6 +115,23 @@ AccelerometerThread *accelerometerThread = nullptr;
AudioThread *audioThread = nullptr;
#endif
#if HAS_TFT
#include "api/PacketAPI.h"
#include "comms/PacketClient.h"
#include "comms/PacketServer.h"
#include "graphics/DeviceScreen.h"
#include "graphics/driver/DisplayDriverConfig.h"
void tft_task_handler(void *);
DeviceScreen *deviceScreen = nullptr;
#endif
#ifdef HAS_UDP_MULTICAST
#include "mesh/udp/UdpMulticastThread.h"
UdpMulticastThread *udpThread = nullptr;
#endif
#if defined(TCXO_OPTIONAL)
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this here so it can be changed further down.
#endif
@@ -253,6 +270,15 @@ void setup()
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
pinMode(KB_POWERON, OUTPUT);
digitalWrite(KB_POWERON, HIGH);
// T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
// We need to initialize all CS pins in advance otherwise there will be SPI communication issues
// e.g. when detecting the SD card
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
delay(100);
#endif
@@ -648,9 +674,9 @@ void setup()
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
// If we're taking on the repeater role, use flood router and turn off 3V3_S rail because peripherals are not needed
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new FloodingRouter();
router = new NextHopRouter();
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
@@ -732,11 +758,92 @@ void setup()
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
SPI.setFrequency(4000000);
#endif
#if HAS_TFT
#ifdef PORTDUINO
if (settingsMap[displayPanel] != no_screen) {
DisplayDriverConfig displayConfig;
static char *panels[] = {"NOSCREEN", "X11", "ST7789", "ST7735", "ST7735S", "ST7796",
"ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"};
static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"};
#ifdef USE_X11
if (settingsMap[displayPanel] == x11) {
if (settingsMap[displayWidth] && settingsMap[displayHeight])
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)settingsMap[displayWidth],
(uint16_t)settingsMap[displayHeight]);
else
displayConfig.device(DisplayDriverConfig::device_t::X11);
} else
#endif
{
displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT)
.panel(DisplayDriverConfig::panel_config_t{.type = panels[settingsMap[displayPanel]],
.panel_width = (uint16_t)settingsMap[displayWidth],
.panel_height = (uint16_t)settingsMap[displayHeight],
.rotation = (bool)settingsMap[displayRotate],
.pin_cs = (int16_t)settingsMap[displayCS],
.pin_rst = (int16_t)settingsMap[displayReset],
.offset_x = (uint16_t)settingsMap[displayOffsetX],
.offset_y = (uint16_t)settingsMap[displayOffsetY],
.offset_rotation = (uint8_t)settingsMap[displayOffsetRotate],
.invert = settingsMap[displayInvert] ? true : false,
.rgb_order = (bool)settingsMap[displayRGBOrder],
.dlen_16bit = settingsMap[displayPanel] == ili9486 ||
settingsMap[displayPanel] == ili9488})
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)settingsMap[displayBusFrequency],
.freq_read = 16000000,
.spi{.pin_dc = (int8_t)settingsMap[displayDC],
.use_lock = true,
.spi_host = (uint16_t)settingsMap[displayspidev]}})
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = settingsStrings[keyboardDevice],
.pointerDevice = settingsStrings[pointerDevice]})
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)settingsMap[displayBacklight],
.pwm_channel = (int8_t)settingsMap[displayBacklightPWMChannel],
.invert = (bool)settingsMap[displayBacklightInvert]});
if (settingsMap[touchscreenI2CAddr] == -1) {
displayConfig.touch(
DisplayDriverConfig::touch_config_t{.type = touch[settingsMap[touchscreenModule]],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
.spi{
.spi_host = (int8_t)settingsMap[touchscreenspidev],
},
.pin_cs = (int16_t)settingsMap[touchscreenCS]});
} else {
displayConfig.touch(DisplayDriverConfig::touch_config_t{
.type = touch[settingsMap[touchscreenModule]],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
.x_min = 0,
.x_max =
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayWidth] : settingsMap[displayHeight]) -
1),
.y_min = 0,
.y_max =
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayHeight] : settingsMap[displayWidth]) -
1),
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
.i2c{.i2c_addr = (uint8_t)settingsMap[touchscreenI2CAddr]}});
}
}
deviceScreen = &DeviceScreen::create(&displayConfig);
PacketAPI::create(PacketServer::init());
deviceScreen->init(new PacketClient);
} else {
LOG_INFO("Running without TFT display!");
}
#else
deviceScreen = &DeviceScreen::create();
PacketAPI::create(PacketServer::init());
deviceScreen->init(new PacketClient);
#endif
#endif
// Initialize the screen first so we can show the logo while we start up everything else.
#if HAS_SCREEN
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#endif
// setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
@@ -785,6 +892,11 @@ void setup()
LOG_DEBUG("Start audio thread");
audioThread = new AudioThread();
#endif
#ifdef HAS_UDP_MULTICAST
LOG_DEBUG("Start multicast thread");
udpThread = new UdpMulticastThread();
#endif
service = new MeshService();
service->init();
@@ -928,13 +1040,16 @@ void setup()
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
#ifdef SX126X_DIO3_TCXO_VOLTAGE
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
#endif
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio");
delete rIf;
rIf = NULL;
delete sxIf;
} else {
LOG_INFO("SX1262 init success");
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
@@ -942,29 +1057,28 @@ void setup()
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// Try using the specified TCXO voltage
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1262 radio with TCXO, Vref %f V", tcxoVoltage);
delete rIf;
rIf = NULL;
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
// try using the specified TCXO voltage
auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE);
if (!sxIf->init()) {
LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
delete sxIf;
} else {
LOG_WARN("SX1262 init success, TCXO, Vref %f V", tcxoVoltage);
LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE);
rIf = sxIf;
radioType = SX1262_RADIO;
}
}
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instead
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
LOG_WARN("No SX1262 radio with XTAL, Vref %f V", tcxoVoltage);
LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V");
delete rIf;
rIf = NULL;
tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search
} else {
LOG_INFO("SX1262 init success, XTAL, Vref %f V", tcxoVoltage);
LOG_INFO("SX1262 init success, XTAL, Vref 0.0V");
radioType = SX1262_RADIO;
}
}
@@ -1124,7 +1238,19 @@ void setup()
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
powerFSMthread = new PowerFSMThread();
#if HAS_TFT
#ifdef HAS_FREE_RTOS
xTaskCreatePinnedToCore(tft_task_handler, "tft", 8192, NULL, 1, NULL, 0);
#endif
#else
setCPUFast(false); // 80MHz is fine for our slow peripherals
#endif
#ifdef ARDUINO_ARCH_ESP32
LOG_DEBUG("Free heap : %7d bytes", ESP.getFreeHeap());
LOG_DEBUG("Free PSRAM : %7d bytes", ESP.getFreePsram());
#endif
}
#endif
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
@@ -1221,4 +1347,22 @@ void loop()
mainDelay.delay(delayMsec);
}
}
#if HAS_TFT
void tft_task_handler(void *param = nullptr)
{
while (true) {
if (deviceScreen) {
spiLock->lock();
deviceScreen->task_handler();
spiLock->unlock();
}
#ifdef HAS_FREE_RTOS
vTaskDelay(5 / portTICK_PERIOD_MS);
#else
delay(5);
#endif
}
}
#endif
#endif

View File

@@ -49,6 +49,11 @@ extern Adafruit_DRV2605 drv;
extern AudioThread *audioThread;
#endif
#ifdef HAS_UDP_MULTICAST
#include "mesh/udp/UdpMulticastThread.h"
extern UdpMulticastThread *udpThread;
#endif
// Global Screen singleton.
extern graphics::Screen *screen;

View File

@@ -74,7 +74,7 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
auth = bytesOut + numBytes;
memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
LOG_INFO("Random nonce value: %d", extraNonceTmp);
LOG_DEBUG("Random nonce value: %d", extraNonceTmp);
if (remotePublic.size == 0) {
LOG_DEBUG("Node %d or their public_key not found", toNode);
return false;

View File

@@ -13,7 +13,8 @@ FloodingRouter::FloodingRouter() {}
ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
{
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
wasSeenRecently(p); // FIXME, move this to a sniffSent method
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
wasSeenRecently(p); // FIXME, move this to a sniffSent method
return Router::send(p);
}
@@ -23,26 +24,17 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
/* If the original transmitter is doing retransmissions (hopStart equals hopLimit) for a reliable transmission, e.g., when
the ACK got lost, we will handle the packet again to make sure it gets an ACK to its packet. */
the ACK got lost, we will handle the packet again to make sure it gets an implicit ACK. */
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
if (isRepeated) {
LOG_DEBUG("Repeated reliable tx");
if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) {
// FIXME - channel index should be used, but the packet is still encrypted here
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, 0, 0);
}
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
perhapsRebroadcast(p);
} else {
perhapsCancelDupe(p);
}
return true;
@@ -51,13 +43,27 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
if (Router::cancelSending(p->from, p->id))
txRelayCanceled++;
}
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
}
bool FloodingRouter::isRebroadcaster()
{
return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
}
bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
if (p->id != 0) {
@@ -72,13 +78,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
tosend->hop_limit = 2;
}
#endif
tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case
LOG_INFO("Rebroadcast received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
return true;
} else {
LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
@@ -86,13 +91,12 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
LOG_DEBUG("Ignore 0 id broadcast");
}
}
return false;
}
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
// do not flood direct message that is ACKed or replied to
LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast");

View File

@@ -1,6 +1,5 @@
#pragma once
#include "PacketHistory.h"
#include "Router.h"
/**
@@ -26,14 +25,11 @@
Any entries in recentBroadcasts that are older than X seconds (longer than the
max time a flood can take) will be discarded.
*/
class FloodingRouter : public Router, protected PacketHistory
class FloodingRouter : public Router
{
private:
bool isRebroadcaster();
/** Check if we should rebroadcast this packet, and do so if needed
* @return true if rebroadcasted */
bool perhapsRebroadcast(const meshtastic_MeshPacket *p);
/* Check if we should rebroadcast this packet, and do so if needed */
void perhapsRebroadcast(const meshtastic_MeshPacket *p);
public:
/**
@@ -62,4 +58,10 @@ class FloodingRouter : public Router, protected PacketHistory
* Look for broadcasts we need to rebroadcast
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/* Call when receiving a duplicate packet to check whether we should cancel a packet in the Tx queue */
void perhapsCancelDupe(const meshtastic_MeshPacket *p);
// Return true if we are a rebroadcaster
bool isRebroadcaster();
};

View File

@@ -20,12 +20,18 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
#if ARCH_PORTDUINO
#define LR1110_MAX_POWER settingsMap[lr1110_max_power]
#endif
#ifndef LR1110_MAX_POWER
#define LR1110_MAX_POWER 22
#endif
// the 2.4G part maxes at 13dBm
#if ARCH_PORTDUINO
#define LR1120_MAX_POWER settingsMap[lr1120_max_power]
#endif
#ifndef LR1120_MAX_POWER
#define LR1120_MAX_POWER 13
#endif
@@ -256,10 +262,17 @@ template <typename T> void LR11x0Interface<T>::startReceive()
template <typename T> bool LR11x0Interface<T>::isChannelActive()
{
// check if we can detect a LoRa preamble on the current channel
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD,
.detPeak = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
.detMin = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
.exitMode = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result;
setStandby();
result = lora.scanChannel();
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
return true;

View File

@@ -10,6 +10,7 @@
std::vector<MeshModule *> *MeshModule::modules;
const meshtastic_MeshPacket *MeshModule::currentRequest;
uint8_t MeshModule::numPeriodicModules = 0;
/**
* If any of the current chain of modules has already sent a reply, it will be here. This is useful to allow
@@ -35,6 +36,15 @@ MeshModule::~MeshModule()
modules->erase(it);
}
// ⚠️ **Only call once** to set the initial delay before a module starts broadcasting periodically
int32_t MeshModule::setStartDelay()
{
int32_t startDelay = MESHMODULE_MIN_BROADCAST_DELAY_MS + numPeriodicModules * MESHMODULE_BROADCAST_SPACING_MS;
numPeriodicModules++;
return startDelay;
}
meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
uint8_t hopLimit)
{

View File

@@ -9,6 +9,9 @@
#include <OLEDDisplayUi.h>
#endif
#define MESHMODULE_MIN_BROADCAST_DELAY_MS 30 * 1000 // Min. delay after boot before sending first broadcast by any module
#define MESHMODULE_BROADCAST_SPACING_MS 15 * 1000 // Initial spacing between broadcasts of different modules
/** handleReceived return enumeration
*
* Use ProcessMessage::CONTINUE to allows other modules to process a message.
@@ -119,6 +122,12 @@ class MeshModule
*/
static const meshtastic_MeshPacket *currentRequest;
// We keep track of the number of modules that send a periodic broadcast to schedule them spaced out over time
static uint8_t numPeriodicModules;
// Set the start delay for module that broadcasts periodically
int32_t setStartDelay();
/**
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
*/

View File

@@ -117,6 +117,19 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id, bool t
return NULL;
}
/* Attempt to find a packet from this queue. Return true if it was found. */
bool MeshPacketQueue::find(NodeNum from, PacketId id)
{
for (auto it = queue.begin(); it != queue.end(); it++) {
auto p = (*it);
if (getFrom(p) == from && p->id == id) {
return true;
}
}
return false;
}
/**
* Attempt to find a lower-priority packet in the queue and replace it with the provided one.
* @return True if the replacement succeeded, false otherwise

View File

@@ -37,4 +37,7 @@ class MeshPacketQueue
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
meshtastic_MeshPacket *remove(NodeNum from, PacketId id, bool tx_normal = true, bool tx_late = true);
/* Attempt to find a packet from this queue. Return true if it was found. */
bool find(NodeNum from, PacketId id);
};

View File

@@ -173,7 +173,9 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
return;
}
#endif
p.from = 0; // We don't let phones assign nodenums to their sent messages
p.from = 0; // We don't let clients assign nodenums to their sent messages
p.next_hop = NO_NEXT_HOP_PREFERENCE; // We don't let clients assign next_hop to their sent messages
p.relay_node = NO_RELAY_NODE; // We don't let clients assign relay_node to their sent messages
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one

View File

@@ -40,6 +40,11 @@ enum RxSource {
/// We normally just use max 3 hops for sending reliable messages
#define HOP_RELIABLE 3
// For old firmware or when falling back to flooding, there is no next-hop preference
#define NO_NEXT_HOP_PREFERENCE 0
// For old firmware there is no relay node set
#define NO_RELAY_NODE 0
typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool

272
src/mesh/NextHopRouter.cpp Normal file
View File

@@ -0,0 +1,272 @@
#include "NextHopRouter.h"
NextHopRouter::NextHopRouter() {}
PendingPacket::PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions)
{
packet = p;
this->numRetransmissions = numRetransmissions - 1; // We subtract one, because we assume the user just did the first send
}
/**
* Send a packet
*/
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
{
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
wasSeenRecently(p); // FIXME, move this to a sniffSent method
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
// If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is
// not 0 or want_ack is set, start retransmissions
if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack))
startRetransmission(packetPool.allocCopy(*p)); // start retransmission for relayed packet
return Router::send(p);
}
bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
bool wasFallback = false;
bool weWereNextHop = false;
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
printPacket("Ignore dupe incoming msg", p);
rxDupe++;
stopRetransmission(p->from, p->id);
// If it was a fallback to flooding, try to relay again
if (wasFallback) {
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
// Check if it's still in the Tx queue, if not, we have to relay it again
if (!findInTxQueue(p->from, p->id))
perhapsRelay(p);
} else {
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
} else if (!weWereNextHop) {
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
}
}
return true;
}
return Router::shouldFilterReceived(p);
}
void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
NodeNum ourNodeNum = getNodeNum();
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(ourNodeNum);
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
if (isAckorReply) {
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
if (p->from != 0) {
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
if (origTx) {
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
// the destination
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
origTx->next_hop = p->relay_node;
}
}
}
}
if (!isToUs(p)) {
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
// stop retransmission for the original packet
stopRetransmission(p->to, p->decoded.request_id); // for original packet, from = to and id = request_id
}
}
perhapsRelay(p);
// handle the packet as normal
Router::sniffReceived(p, c);
}
/* Check if we should be relaying this packet if so, do so. */
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
{
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
LOG_INFO("Relaying received message coming from %x", p->relay_node);
tosend->hop_limit--; // bump down the hop count
NextHopRouter::send(tosend);
return true;
} else {
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
}
}
return false;
}
/**
* Get the next hop for a destination, given the relay node
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
{
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
if (isBroadcast(to))
return NO_NEXT_HOP_PREFERENCE;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
if (node && node->next_hop) {
// We are careful not to return the relay node as the next hop
if (node->next_hop != relay_node) {
// LOG_DEBUG("Next hop for 0x%x is 0x%x", to, node->next_hop);
return node->next_hop;
} else
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
}
return NO_NEXT_HOP_PREFERENCE;
}
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
return &old->second;
} else
return NULL;
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
if (old) {
auto p = old->packet;
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
// now free the pooled copy for retransmission too
packetPool.release(p);
}
auto numErased = pending.erase(key);
assert(numErased == 1);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *NextHopRouter::startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p, numReTx);
stopRetransmission(getFrom(p), p->id);
setNextTx(&rec);
pending[id] = rec;
return &pending[id];
}
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
int32_t NextHopRouter::doRetransmissions()
{
uint32_t now = millis();
int32_t d = INT32_MAX;
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second;
bool stillValid = true; // assume we'll keep this record around
// FIXME, handle 51 day rolloever here!!!
if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) {
if (isFromUs(p.packet)) {
LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
p.packet->id);
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
}
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
stopRetransmission(it->first);
stillValid = false; // just deleted it
} else {
LOG_DEBUG("Sending retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
if (!isBroadcast(p.packet->to)) {
if (p.numRetransmissions == 1) {
// Last retransmission, reset next_hop (fallback to FloodingRouter)
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
// Also reset it in the nodeDB
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
if (sentTo) {
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
}
FloodingRouter::send(packetPool.allocCopy(*p.packet));
} else {
NextHopRouter::send(packetPool.allocCopy(*p.packet));
}
} else {
// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
}
// Queue again
--p.numRetransmissions;
setNextTx(&p);
}
}
if (stillValid) {
// Update our desired sleep delay
int32_t t = p.nextTxMsec - now;
d = min(t, d);
}
}
return d;
}
void NextHopRouter::setNextTx(PendingPacket *pending)
{
assert(iface);
auto d = iface->getRetransmissionMsec(pending->packet);
pending->nextTxMsec = millis() + d;
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
}

151
src/mesh/NextHopRouter.h Normal file
View File

@@ -0,0 +1,151 @@
#pragma once
#include "FloodingRouter.h"
#include <unordered_map>
/**
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
* to that message
*/
struct GlobalPacketId {
NodeNum node;
PacketId id;
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
{
node = getFrom(p);
id = p->id;
}
GlobalPacketId(NodeNum _from, PacketId _id)
{
node = _from;
id = _id;
}
};
/**
* A packet queued for retransmission
*/
struct PendingPacket {
meshtastic_MeshPacket *packet;
/** The next time we should try to retransmit this packet */
uint32_t nextTxMsec = 0;
/** Starts at NUM_RETRANSMISSIONS -1 and counts down. Once zero it will be removed from the list */
uint8_t numRetransmissions = 0;
PendingPacket() {}
explicit PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions);
};
class GlobalPacketIdHashFunction
{
public:
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
};
/*
Router for direct messages, which only relays if it is the next hop for a packet. The next hop is set by the current
relayer of a packet, which bases this on information from a previous successful delivery to the destination via flooding.
Namely, in the PacketHistory, we keep track of (up to 3) relayers of a packet. When the ACK is delivered back to us via a node
that also relayed the original packet, we use that node as next hop for the destination from then on. This makes sure that only
when theres a two-way connection, we assign a next hop. Both the ReliableRouter and NextHopRouter will do retransmissions (the
NextHopRouter only 1 time). For the final retry, if no one actually relayed the packet, it will reset the next hop in order to
fall back to the FloodingRouter again. Note that thus also intermediate hops will do a single retransmission if the intended
next-hop didnt relay, in order to fix changes in the middle of the route.
*/
class NextHopRouter : public FloodingRouter
{
public:
/**
* Constructor
*
*/
NextHopRouter();
/**
* Send a packet
* @return an error code
*/
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/** Do our retransmission handling */
virtual int32_t runOnce() override
{
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
doRetransmissions();
int32_t r = FloodingRouter::runOnce();
// Also after calling runOnce there might be new packets to retransmit
auto d = doRetransmissions();
return min(d, r);
}
// The number of retransmissions intermediate nodes will do (actually 1 less than this)
constexpr static uint8_t NUM_INTERMEDIATE_RETX = 2;
// The number of retransmissions the original sender will do
constexpr static uint8_t NUM_RELIABLE_RETX = 3;
protected:
/**
* Pending retransmissions
*/
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
/**
* Should this incoming filter be dropped?
*
* Called immediately on reception, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
/**
* Look for packets we need to relay
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/**
* Try to find the pending packet record for this ID (or NULL if not found)
*/
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
PendingPacket *findPendingPacket(GlobalPacketId p);
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx = NUM_INTERMEDIATE_RETX);
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
* @return true if we found and removed a transmission with this ID
*/
bool stopRetransmission(NodeNum from, PacketId id);
bool stopRetransmission(GlobalPacketId p);
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending);
private:
/**
* Get the next hop for a destination, given the relay node
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
*/
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
/** Check if we should be relaying this packet if so, do so.
* @return true if we did relay */
bool perhapsRelay(const meshtastic_MeshPacket *p);
};

View File

@@ -23,7 +23,6 @@
#include "modules/NeighborInfoModule.h"
#include <ErriezCRC32.h>
#include <algorithm>
#include <iostream>
#include <pb_decode.h>
#include <pb_encode.h>
#include <vector>
@@ -57,6 +56,7 @@ NodeDB *nodeDB = nullptr;
// we have plenty of ram so statically alloc this tempbuf (for now)
EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate;
meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node;
meshtastic_NodeDatabase nodeDatabase;
meshtastic_LocalConfig config;
meshtastic_DeviceUIConfig uiconfig{.screen_brightness = 153, .screen_timeout = 30};
meshtastic_LocalModuleConfig moduleConfig;
@@ -144,7 +144,7 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_
#endif
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
if (ostream) {
std::vector<meshtastic_NodeInfoLite> const *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
@@ -193,6 +193,7 @@ NodeDB::NodeDB()
cleanupMeshDB();
uint32_t devicestateCRC = crc32Buffer(&devicestate, sizeof(devicestate));
uint32_t nodeDatabaseCRC = crc32Buffer(&nodeDatabase, sizeof(nodeDatabase));
uint32_t configCRC = crc32Buffer(&config, sizeof(config));
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
@@ -247,15 +248,15 @@ NodeDB::NodeDB()
// Ensure macaddr is set to our macaddr as it will be copied in our info below
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// Include our owner in the node db under our nodenum
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
if (!config.has_security) {
config.has_security = true;
config.security = meshtastic_Config_SecurityConfig_init_default;
config.security.serial_enabled = config.device.serial_enabled;
config.security.is_managed = config.device.is_managed;
}
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
@@ -282,10 +283,18 @@ NodeDB::NodeDB()
crypto->setDHPrivateKey(config.security.private_key.bytes);
}
#endif
// Include our owner in the node db under our nodenum
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
info->user = TypeConversions::ConvertToUserLite(owner);
info->has_user = true;
// If node database has not been saved for the first time, save it now
#ifdef FSCom
if (!FSCom.exists(nodeDatabaseFileName)) {
saveNodeDatabaseToDisk();
}
#endif
#ifdef ARCH_ESP32
Preferences preferences;
preferences.begin("meshtastic", false);
@@ -297,6 +306,9 @@ NodeDB::NodeDB()
resetRadioConfig(); // If bogus settings got saved, then fix them
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
// Uncomment below to always enable UDP broadcasts
// config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST;
// If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value
// of 30 minutes or more
if (channels.isDefaultChannel(channels.getPrimaryIndex())) {
@@ -318,6 +330,8 @@ NodeDB::NodeDB()
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
saveWhat |= SEGMENT_DEVICESTATE;
if (nodeDatabaseCRC != crc32Buffer(&nodeDatabase, sizeof(nodeDatabase)))
saveWhat |= SEGMENT_NODEDATABASE;
if (configCRC != crc32Buffer(&config, sizeof(config)))
saveWhat |= SEGMENT_CONFIG;
if (channelFileCRC != crc32Buffer(&channelFile, sizeof(channelFile)))
@@ -408,7 +422,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
rebootAtMsec = millis() + (5 * 1000);
}
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && defined(HAS_TFT)
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3)) && HAS_TFT
// as long as PhoneAPI shares BT and TFT app switch BT off
config.bluetooth.enabled = false;
if (moduleConfig.external_notification.nag_timeout == 60)
@@ -432,6 +446,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
#endif
spiLock->unlock();
// second, install default state (this will deal with the duplicate mac address issue)
installDefaultNodeDatabase();
installDefaultDeviceState();
installDefaultConfig(!eraseBleBonds); // Also preserve the private key if we're not erasing BLE bonds
installDefaultModuleConfig();
@@ -456,6 +471,15 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
return true;
}
void NodeDB::installDefaultNodeDatabase()
{
LOG_DEBUG("Install default NodeDatabase");
nodeDatabase.version = DEVICESTATE_CUR_VER;
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
numMeshNodes = 0;
meshNodes = &nodeDatabase.nodes;
}
void NodeDB::installDefaultConfig(bool preserveKey = false)
{
uint8_t private_key_temp[32];
@@ -783,9 +807,10 @@ void NodeDB::resetNodes()
if (!config.position.fixed_position)
clearLocalPosition();
numMeshNodes = 1;
std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite());
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
devicestate.has_rx_text_message = false;
devicestate.has_rx_waypoint = false;
saveNodeDatabaseToDisk();
saveDeviceStateToDisk();
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
neighborInfoModule->resetNeighbors();
@@ -801,10 +826,10 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
removed++;
}
numMeshNodes -= removed;
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1,
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
meshtastic_NodeInfoLite());
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
saveDeviceStateToDisk();
saveNodeDatabaseToDisk();
}
void NodeDB::clearLocalPosition()
@@ -833,7 +858,7 @@ void NodeDB::cleanupMeshDB()
}
}
numMeshNodes -= removed;
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed,
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
meshtastic_NodeInfoLite());
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
}
@@ -843,13 +868,9 @@ void NodeDB::installDefaultDeviceState()
LOG_INFO("Install default DeviceState");
// memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
numMeshNodes = 0;
meshNodes = &devicestate.node_db_lite;
// init our devicestate with valid flags so protobuf writing/reading will work
devicestate.has_my_node = true;
devicestate.has_owner = true;
// devicestate.node_db_lite_count = 0;
devicestate.version = DEVICESTATE_CUR_VER;
devicestate.receive_queue_count = 0; // Not yet implemented FIXME
devicestate.has_rx_waypoint = false;
@@ -903,12 +924,6 @@ void NodeDB::pickNewNodeNum()
myNodeInfo.my_node_num = nodeNum;
}
static const char *prefFileName = "/prefs/db.proto";
static const char *configFileName = "/prefs/config.proto";
static const char *uiconfigFileName = "/prefs/uiconfig.proto";
static const char *moduleConfigFileName = "/prefs/module.proto";
static const char *channelFileName = "/prefs/channels.proto";
/** Load a protobuf from a file, return LoadFileResult */
LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
void *dest_struct)
@@ -944,20 +959,44 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
void NodeDB::loadFromDisk()
{
devicestate.version =
0; // Mark the current device state as completely unusable, so that if we fail reading the entire file from
// Mark the current device state as completely unusable, so that if we fail reading the entire file from
// disk we will still factoryReset to restore things.
devicestate.version = 0;
#ifdef ARCH_ESP32
spiLock->lock();
// If the legacy deviceState exists, start over with a factory reset
if (FSCom.exists("/static/static"))
rmDir("/static/static"); // Remove bad static web files bundle from initial 2.5.13 release
spiLock->unlock();
#endif
#ifdef FSCom
spiLock->lock();
if (FSCom.exists(legacyPrefFileName)) {
rmDir("/prefs");
}
spiLock->unlock();
#endif
auto state = loadProto(nodeDatabaseFileName, getMaxNodesAllocatedSize(), sizeof(meshtastic_NodeDatabase),
&meshtastic_NodeDatabase_msg, &nodeDatabase);
if (nodeDatabase.version < DEVICESTATE_MIN_VER) {
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
installDefaultNodeDatabase();
} else {
meshNodes = &nodeDatabase.nodes;
numMeshNodes = nodeDatabase.nodes.size();
LOG_INFO("Loaded saved nodedatabase version %d, with nodes count: %d", nodeDatabase.version, nodeDatabase.nodes.size());
}
if (numMeshNodes > MAX_NUM_NODES) {
LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES);
numMeshNodes = MAX_NUM_NODES;
}
meshNodes->resize(MAX_NUM_NODES);
// static DeviceState scratch; We no longer read into a tempbuf because this structure is 15KB of valuable RAM
auto state = loadProto(prefFileName, sizeof(meshtastic_DeviceState) + MAX_NUM_NODES_FS * meshtastic_NodeInfoLite_size,
sizeof(meshtastic_DeviceState), &meshtastic_DeviceState_msg, &devicestate);
state = loadProto(deviceStateFileName, meshtastic_DeviceState_size, sizeof(meshtastic_DeviceState),
&meshtastic_DeviceState_msg, &devicestate);
// See https://github.com/meshtastic/firmware/issues/4184#issuecomment-2269390786
// It is very important to try and use the saved prefs even if we fail to read meshtastic_DeviceState. Because most of our
@@ -971,15 +1010,8 @@ void NodeDB::loadFromDisk()
LOG_WARN("Devicestate %d is old, discard", devicestate.version);
installDefaultDeviceState();
} else {
LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d", devicestate.version, devicestate.node_db_lite.size());
meshNodes = &devicestate.node_db_lite;
numMeshNodes = devicestate.node_db_lite.size();
LOG_INFO("Loaded saved devicestate version %d", devicestate.version);
}
if (numMeshNodes > MAX_NUM_NODES) {
LOG_WARN("Node count %d exceeds MAX_NUM_NODES %d, truncating", numMeshNodes, MAX_NUM_NODES);
numMeshNodes = MAX_NUM_NODES;
}
meshNodes->resize(MAX_NUM_NODES);
state = loadProto(configFileName, meshtastic_LocalConfig_size, sizeof(meshtastic_LocalConfig), &meshtastic_LocalConfig_msg,
&config);
@@ -1145,15 +1177,24 @@ bool NodeDB::saveDeviceStateToDisk()
#endif
// Note: if MAX_NUM_NODES=100 and meshtastic_NodeInfoLite_size=166, so will be approximately 17KB
// Because so huge we _must_ not use fullAtomic, because the filesystem is probably too small to hold two copies of this
size_t deviceStateSize;
pb_get_encoded_size(&deviceStateSize, meshtastic_DeviceState_fields, &devicestate);
return saveProto(prefFileName, deviceStateSize, &meshtastic_DeviceState_msg, &devicestate, false);
return saveProto(deviceStateFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate, true);
}
bool NodeDB::saveNodeDatabaseToDisk()
{
#ifdef FSCom
spiLock->lock();
FSCom.mkdir("/prefs");
spiLock->unlock();
#endif
size_t nodeDatabaseSize;
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &nodeDatabase);
return saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false);
}
bool NodeDB::saveToDiskNoRetry(int saveWhat)
{
bool success = true;
#ifdef FSCom
spiLock->lock();
FSCom.mkdir("/prefs");
@@ -1198,11 +1239,16 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
success &= saveDeviceStateToDisk();
}
if (saveWhat & SEGMENT_NODEDATABASE) {
success &= saveNodeDatabaseToDisk();
}
return success;
}
bool NodeDB::saveToDisk(int saveWhat)
{
LOG_DEBUG("Save to disk %d", saveWhat);
bool success = saveToDiskNoRetry(saveWhat);
if (!success) {
@@ -1383,8 +1429,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
// We just changed something about a User,
// store our DB unless we just did so less than a minute ago
if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) {
saveToDisk(SEGMENT_DEVICESTATE);
saveToDisk(SEGMENT_NODEDATABASE);
lastNodeDbSave = millis();
} else {
LOG_DEBUG("Defer NodeDB saveToDisk for now");
@@ -1398,6 +1445,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
{
// if (mp.from == getNodeNum()) {
// LOG_DEBUG("Ignore update from self");
// return;
// }
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);

View File

@@ -4,6 +4,7 @@
#include <Arduino.h>
#include <algorithm>
#include <assert.h>
#include <pb_encode.h>
#include <vector>
#include "MeshTypes.h"
@@ -12,6 +13,10 @@
#include "mesh-pb-constants.h"
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
#if ARCH_PORTDUINO
#include "PortduinoGlue.h"
#endif
/*
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
#define here.
@@ -21,11 +26,13 @@ DeviceState versions used to be defined in the .proto file but really only this
#define SEGMENT_MODULECONFIG 2
#define SEGMENT_DEVICESTATE 4
#define SEGMENT_CHANNELS 8
#define SEGMENT_NODEDATABASE 16
#define DEVICESTATE_CUR_VER 23
#define DEVICESTATE_MIN_VER 22
#define DEVICESTATE_CUR_VER 24
#define DEVICESTATE_MIN_VER 24
extern meshtastic_DeviceState devicestate;
extern meshtastic_NodeDatabase nodeDatabase;
extern meshtastic_ChannelFile channelFile;
extern meshtastic_MyNodeInfo &myNodeInfo;
extern meshtastic_LocalConfig config;
@@ -34,6 +41,14 @@ extern meshtastic_LocalModuleConfig moduleConfig;
extern meshtastic_User &owner;
extern meshtastic_Position localPosition;
static constexpr const char *deviceStateFileName = "/prefs/device.proto";
static constexpr const char *legacyPrefFileName = "/prefs/db.proto";
static constexpr const char *nodeDatabaseFileName = "/prefs/nodes.proto";
static constexpr const char *configFileName = "/prefs/config.proto";
static constexpr const char *uiconfigFileName = "/prefs/uiconfig.proto";
static constexpr const char *moduleConfigFileName = "/prefs/module.proto";
static constexpr const char *channelFileName = "/prefs/channels.proto";
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
@@ -75,7 +90,8 @@ class NodeDB
/// write to flash
/// @return true if the save was successful
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
SEGMENT_NODEDATABASE);
/** Reinit radio config if needed, because either:
* a) sometimes a buggy android app might send us bogus settings or
@@ -104,6 +120,9 @@ class NodeDB
/// @return our node number
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
// bool handleWantNodeNum(NodeNum n);
@@ -148,6 +167,15 @@ class NodeDB
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
size_t getNumMeshNodes() { return numMeshNodes; }
size_t getMaxNodesAllocatedSize()
{
meshtastic_NodeDatabase emptyNodeDatabase;
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
size_t nodeDatabaseSize;
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
}
// returns true if the maximum number of nodes is reached or we are running low on memory
bool isFull();
@@ -188,8 +216,8 @@ class NodeDB
void cleanupMeshDB();
/// Reinit device state from scratch (not loading from disk)
void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(bool preserveKey),
installDefaultModuleConfig();
void installDefaultDeviceState(), installDefaultNodeDatabase(), installDefaultChannels(),
installDefaultConfig(bool preserveKey), installDefaultModuleConfig();
/// write to flash
/// @return true if the save was successful
@@ -197,6 +225,7 @@ class NodeDB
bool saveChannelsToDisk();
bool saveDeviceStateToDisk();
bool saveNodeDatabaseToDisk();
};
extern NodeDB *nodeDB;

View File

@@ -16,7 +16,7 @@ PacketHistory::PacketHistory()
/**
* Update recentBroadcasts and return true if we have already seen this packet
*/
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate)
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate, bool *wasFallback, bool *weWereNextHop)
{
if (p->id == 0) {
LOG_DEBUG("Ignore message with zero id");
@@ -27,6 +27,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
r.id = p->id;
r.sender = getFrom(p);
r.rxTimeMsec = millis();
r.next_hop = p->next_hop;
r.relayed_by[0] = p->relay_node;
// LOG_INFO("Add relayed_by 0x%x for id=0x%x", p->relay_node, r.id);
auto found = recentPackets.find(r);
bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
@@ -40,14 +43,36 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
if (seenRecently) {
LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x", p->from, p->to, p->id);
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum());
if (wasFallback) {
// If it was seen with a next-hop not set to us and now it's NO_NEXT_HOP_PREFERENCE, and the relayer relayed already
// before, it's a fallback to flooding. If we didn't already relay and the next-hop neither, we might need to handle
// it now.
if (found->sender != nodeDB->getNodeNum() && found->next_hop != NO_NEXT_HOP_PREFERENCE &&
found->next_hop != ourRelayID && p->next_hop == NO_NEXT_HOP_PREFERENCE && wasRelayer(p->relay_node, found) &&
!wasRelayer(ourRelayID, found) && !wasRelayer(found->next_hop, found)) {
*wasFallback = true;
}
}
// Check if we were the next hop for this packet
if (weWereNextHop) {
*weWereNextHop = found->next_hop == ourRelayID;
}
}
if (withUpdate) {
if (found != recentPackets.end()) { // delete existing to updated timestamp (re-insert)
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update timestamp - so re-insert..)
if (found != recentPackets.end()) { // delete existing to updated timestamp and relayed_by (re-insert)
// Add the existing relayed_by to the new record
for (uint8_t i = 0; i < NUM_RELAYERS - 1; i++) {
if (found->relayed_by[i])
r.relayed_by[i + 1] = found->relayed_by[i];
}
r.next_hop = found->next_hop; // keep the original next_hop (such that we check whether we were originally asked)
recentPackets.erase(found); // as unsorted_set::iterator is const (can't update - so re-insert..)
}
recentPackets.insert(r);
printPacket("Add packet record", p);
LOG_DEBUG("Add packet record fr=0x%x, id=0x%x", p->from, p->id);
}
// Capacity is reerved, so only purge expired packets if recentPackets fills past 90% capacity
@@ -75,4 +100,59 @@ void PacketHistory::clearExpiredRecentPackets()
}
LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)", recentPackets.size());
}
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
if (relayer == 0)
return false;
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
auto found = recentPackets.find(r);
if (found == recentPackets.end()) {
return false;
}
return wasRelayer(relayer, found);
}
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool PacketHistory::wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r)
{
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (r->relayed_by[i] == relayer) {
return true;
}
}
return false;
}
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
{
PacketRecord r = {.sender = sender, .id = id, .rxTimeMsec = 0, .next_hop = 0};
auto found = recentPackets.find(r);
if (found == recentPackets.end()) {
return;
}
// Make a copy of the found record
r.next_hop = found->next_hop;
r.rxTimeMsec = found->rxTimeMsec;
// Only add the relayers that are not the one we want to remove
uint8_t j = 0;
for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
if (found->relayed_by[i] != relayer) {
r.relayed_by[j] = found->relayed_by[i];
j++;
}
}
recentPackets.erase(found);
recentPackets.insert(r);
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include "Router.h"
#include "NodeDB.h"
#include <unordered_set>
/// We clear our old flood record 10 minutes after we see the last of it
@@ -10,13 +10,18 @@
#define FLOOD_EXPIRE_TIME (10 * 60 * 1000L)
#endif
#define NUM_RELAYERS \
3 // Number of relayer we keep track of. Use 3 to be efficient with memory alignment of PacketRecord to 16 bytes
/**
* A record of a recent message broadcast
*/
struct PacketRecord {
NodeNum sender;
PacketId id;
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
uint32_t rxTimeMsec; // Unix time in msecs - the time we received it
uint8_t next_hop; // The next hop asked for this packet
uint8_t relayed_by[NUM_RELAYERS]; // Array of nodes that relayed this packet
bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; }
};
@@ -44,6 +49,20 @@ class PacketHistory
* Update recentBroadcasts and return true if we have already seen this packet
*
* @param withUpdate if true and not found we add an entry to recentPackets
* @param wasFallback if not nullptr, packet will be checked for fallback to flooding and value will be set to true if so
* @param weWereNextHop if not nullptr, packet will be checked for us being the next hop and value will be set to true if so
*/
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true);
};
bool wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate = true, bool *wasFallback = nullptr,
bool *weWereNextHop = nullptr);
/* Check if a certain node was a relayer of a packet in the history given an ID and sender
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not */
bool wasRelayer(const uint8_t relayer, std::unordered_set<PacketRecord, PacketRecordHashFunction>::iterator r);
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
};

View File

@@ -12,6 +12,7 @@
#include "PhoneAPI.h"
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "Router.h"
#include "SPILock.h"
#include "TypeConversions.h"
#include "main.h"

View File

@@ -9,6 +9,9 @@
#include "PortduinoGlue.h"
#endif
#if ARCH_PORTDUINO
#define RF95_MAX_POWER settingsMap[rf95_max_power]
#endif
#ifndef RF95_MAX_POWER
#define RF95_MAX_POWER 20
#endif
@@ -337,4 +340,4 @@ bool RF95Interface::sleep()
return true;
}
#endif
#endif

View File

@@ -261,7 +261,7 @@ uint8_t RadioInterface::getCWsize(float snr)
const uint32_t SNR_MIN = -20;
// The maximum value for a LoRa SNR
const uint32_t SNR_MAX = 15;
const uint32_t SNR_MAX = 10;
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
}
@@ -340,6 +340,10 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
out += DEBUG_PORT.mt_sprintf(" via MQTT");
if (p->hop_start != 0)
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
if (p->next_hop != 0)
out += DEBUG_PORT.mt_sprintf(" nextHop=0x%x", p->next_hop);
if (p->relay_node != 0)
out += DEBUG_PORT.mt_sprintf(" relay=0x%x", p->relay_node);
if (p->priority != 0)
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
@@ -566,7 +570,7 @@ void RadioInterface::applyModemConfig()
saveChannelNum(channel_num);
saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec(bw, sf);
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = getPacketTime((uint32_t)0);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
@@ -581,6 +585,25 @@ void RadioInterface::applyModemConfig()
LOG_INFO("Slot time: %u msec", slotTimeMsec);
}
/** Slottime is the time to detect a transmission has started, consisting of:
- CAD duration;
- roundtrip air propagation time (assuming max. 30km between nodes);
- Tx/Rx turnaround time (maximum of SX126x and SX127x);
- MAC processing time (measured on T-beam) */
uint32_t RadioInterface::computeSlotTimeMsec()
{
float sumPropagationTurnaroundMACTime = 0.2 + 0.4 + 7; // in milliseconds
float symbolTime = pow(2, sf) / bw; // in milliseconds
if (myRegion->wideLora) {
// CAD duration derived from AN1200.22 of SX1280
return (NUM_SYM_CAD_24GHZ + (2 * sf + 3) / 32) * symbolTime + sumPropagationTurnaroundMACTime;
} else {
// CAD duration for SX127x is max. 2.25 symbols, for SX126x it is number of symbols + 0.5 symbol
return max(2.25, NUM_SYM_CAD + 0.5) * symbolTime + sumPropagationTurnaroundMACTime;
}
}
/**
* Some regulatory regions limit xmit power.
* This function should be called by subclasses after setting their desired power. It might lower it
@@ -620,8 +643,8 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
radioBuffer.header.to = p->to;
radioBuffer.header.id = p->id;
radioBuffer.header.channel = p->channel;
radioBuffer.header.next_hop = 0; // *** For future use ***
radioBuffer.header.relay_node = 0; // *** For future use ***
radioBuffer.header.next_hop = p->next_hop;
radioBuffer.header.relay_node = p->relay_node;
if (p->hop_limit > HOP_MAX) {
LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
p->hop_limit = HOP_RELIABLE;

View File

@@ -38,10 +38,10 @@ typedef struct {
/** The channel hash - used as a hint for the decoder to limit which channels we consider */
uint8_t channel;
// ***For future use*** Last byte of the NodeNum of the next-hop for this packet
// Last byte of the NodeNum of the next-hop for this packet
uint8_t next_hop;
// ***For future use*** Last byte of the NodeNum of the node that will relay/relayed this packet
// Last byte of the NodeNum of the node that will relay/relayed this packet
uint8_t relay_node;
} PacketHeader;
@@ -83,24 +83,22 @@ class RadioInterface
float bw = 125;
uint8_t sf = 9;
uint8_t cr = 5;
/** Slottime is the minimum time to wait, consisting of:
- CAD duration (maximum of SX126x and SX127x);
- roundtrip air propagation time (assuming max. 30km between nodes);
- Tx/Rx turnaround time (maximum of SX126x and SX127x);
- MAC processing time (measured on T-beam) */
uint32_t slotTimeMsec = computeSlotTimeMsec(bw, sf);
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 2; // minimum CWsize
const uint8_t CWmax = 7; // maximum CWsize
const uint8_t CWmin = 3; // minimum CWsize
const uint8_t CWmax = 8; // maximum CWsize
meshtastic_MeshPacket *sendingPacket = NULL; // The packet we are currently sending
uint32_t lastTxStart = 0L;
uint32_t computeSlotTimeMsec(float bw, float sf) { return 8.5 * pow(2, sf) / bw + 0.2 + 0.4 + 7; }
uint32_t computeSlotTimeMsec();
/**
* A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need
@@ -155,6 +153,9 @@ class RadioInterface
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) { return false; }
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) { return false; }
// methods from radiohead
/// Initialise the Driver transport hardware and software.

View File

@@ -222,6 +222,12 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
return result;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
{
return txQueue.find(from, id);
}
/** radio helper thread callback.
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
@@ -445,6 +451,9 @@ void RadioLibInterface::handleReceiveInterrupt()
mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK);
mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
// If hop_start is not set, next_hop and relay_node are invalid (firmware <2.3)
mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.next_hop;
mp->relay_node = mp->hop_start == 0 ? NO_RELAY_NODE : radioBuffer.header.relay_node;
addReceiveMetadata(mp);

View File

@@ -135,6 +135,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) override;
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
private:
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
* doing the transmit */

View File

@@ -23,7 +23,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
}
auto copy = packetPool.allocCopy(*p);
startRetransmission(copy);
startRetransmission(copy, NUM_RELIABLE_RETX);
}
/* If we have pending retransmissions, add the airtime of this packet to it, because during that time we cannot receive an
@@ -35,7 +35,7 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p)
}
}
return FloodingRouter::send(p);
return isBroadcast(p->to) ? FloodingRouter::send(p) : NextHopRouter::send(p);
}
bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
@@ -73,7 +73,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
i->second.nextTxMsec += iface->getPacketTime(p);
}
return FloodingRouter::shouldFilterReceived(p);
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
}
/**
@@ -138,126 +138,5 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
}
// handle the packet as normal
FloodingRouter::sniffReceived(p, c);
}
#define NUM_RETRANSMISSIONS 3
PendingPacket::PendingPacket(meshtastic_MeshPacket *p)
{
packet = p;
numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
}
PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
{
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
if (old != pending.end()) {
return &old->second;
} else
return NULL;
}
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*/
bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
{
auto key = GlobalPacketId(from, id);
return stopRetransmission(key);
}
bool ReliableRouter::stopRetransmission(GlobalPacketId key)
{
auto old = findPendingPacket(key);
if (old) {
auto p = old->packet;
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id);
}
// now free the pooled copy for retransmission too
packetPool.release(p);
auto numErased = pending.erase(key);
assert(numErased == 1);
return true;
} else
return false;
}
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *ReliableRouter::startRetransmission(meshtastic_MeshPacket *p)
{
auto id = GlobalPacketId(p);
auto rec = PendingPacket(p);
stopRetransmission(getFrom(p), p->id);
setNextTx(&rec);
pending[id] = rec;
return &pending[id];
}
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*/
int32_t ReliableRouter::doRetransmissions()
{
uint32_t now = millis();
int32_t d = INT32_MAX;
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
++nextIt; // we use this odd pattern because we might be deleting it...
auto &p = it->second;
bool stillValid = true; // assume we'll keep this record around
// FIXME, handle 51 day rollover here!!!
if (p.nextTxMsec <= now) {
if (p.numRetransmissions == 0) {
LOG_DEBUG("Reliable send failed, return a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
p.packet->id);
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
stopRetransmission(it->first);
stillValid = false; // just deleted it
} else {
LOG_DEBUG("Send reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
p.packet->id, p.numRetransmissions);
// Note: we call the superclass version because we don't want to have our version of send() add a new
// retransmission record
FloodingRouter::send(packetPool.allocCopy(*p.packet));
// Queue again
--p.numRetransmissions;
setNextTx(&p);
}
}
if (stillValid) {
// Update our desired sleep delay
int32_t t = p.nextTxMsec - now;
d = min(t, d);
}
}
return d;
}
void ReliableRouter::setNextTx(PendingPacket *pending)
{
assert(iface);
auto d = iface->getRetransmissionMsec(pending->packet);
pending->nextTxMsec = millis() + d;
LOG_DEBUG("Set next retransmission in %u msecs: ", d);
printPacket("", pending->packet);
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
}

View File

@@ -1,61 +1,12 @@
#pragma once
#include "FloodingRouter.h"
#include <unordered_map>
/**
* An identifier for a globally unique message - a pair of the sending nodenum and the packet id assigned
* to that message
*/
struct GlobalPacketId {
NodeNum node;
PacketId id;
bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; }
explicit GlobalPacketId(const meshtastic_MeshPacket *p)
{
node = getFrom(p);
id = p->id;
}
GlobalPacketId(NodeNum _from, PacketId _id)
{
node = _from;
id = _id;
}
};
/**
* A packet queued for retransmission
*/
struct PendingPacket {
meshtastic_MeshPacket *packet;
/** The next time we should try to retransmit this packet */
uint32_t nextTxMsec = 0;
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
uint8_t numRetransmissions = 0;
PendingPacket() {}
explicit PendingPacket(meshtastic_MeshPacket *p);
};
class GlobalPacketIdHashFunction
{
public:
size_t operator()(const GlobalPacketId &p) const { return (std::hash<NodeNum>()(p.node)) ^ (std::hash<PacketId>()(p.id)); }
};
#include "NextHopRouter.h"
/**
* This is a mixin that extends Router with the ability to do (one hop only) reliable message sends.
*/
class ReliableRouter : public FloodingRouter
class ReliableRouter : public NextHopRouter
{
private:
std::unordered_map<GlobalPacketId, PendingPacket, GlobalPacketIdHashFunction> pending;
public:
/**
* Constructor
@@ -70,54 +21,14 @@ class ReliableRouter : public FloodingRouter
*/
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
/** Do our retransmission handling */
virtual int32_t runOnce() override
{
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
auto d = doRetransmissions();
int32_t r = FloodingRouter::runOnce();
return min(d, r);
}
protected:
/**
* Look for acks/naks or someone retransmitting us
*/
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
/**
* Try to find the pending packet record for this ID (or NULL if not found)
*/
PendingPacket *findPendingPacket(NodeNum from, PacketId id) { return findPendingPacket(GlobalPacketId(from, id)); }
PendingPacket *findPendingPacket(GlobalPacketId p);
/**
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
*/
PendingPacket *startRetransmission(meshtastic_MeshPacket *p);
private:
/**
* Stop any retransmissions we are doing of the specified node/packet ID pair
*
* @return true if we found and removed a transmission with this ID
*/
bool stopRetransmission(NodeNum from, PacketId id);
bool stopRetransmission(GlobalPacketId p);
/**
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
*
* @return the number of msecs until our next retransmission or MAXINT if none scheduled
*/
int32_t doRetransmissions();
void setNextTx(PendingPacket *pending);
};
};

View File

@@ -249,6 +249,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// the lora we need to make sure we have replaced it with our local address
p->from = getFrom(p);
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us
// If we are the original transmitter, set the hop limit with which we start
if (isFromUs(p))
p->hop_start = p->hop_limit;
@@ -274,6 +275,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
}
#if HAS_UDP_MULTICAST
if (udpThread && config.network.enabled_protocols & meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST) {
udpThread->onSend(const_cast<meshtastic_MeshPacket *>(p));
}
#endif
#if !MESHTASTIC_EXCLUDE_MQTT
// Only publish to MQTT if we're the original transmitter of the packet
if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
@@ -290,7 +296,18 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool Router::cancelSending(NodeNum from, PacketId id)
{
return iface ? iface->cancelSending(from, id) : false;
if (iface && iface->cancelSending(from, id)) {
// We are not a relayer of this packet anymore
removeRelayer(nodeDB->getLastByteOfNodeNum(nodeDB->getNodeNum()), id, from);
return true;
}
return false;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool Router::findInTxQueue(NodeNum from, PacketId id)
{
return iface->findInTxQueue(from, id);
}
/**

View File

@@ -4,6 +4,7 @@
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PacketHistory.h"
#include "PointerQueue.h"
#include "RadioInterface.h"
#include "concurrency/OSThread.h"
@@ -11,7 +12,7 @@
/**
* A mesh aware router that supports multiple interfaces.
*/
class Router : protected concurrency::OSThread
class Router : protected concurrency::OSThread, protected PacketHistory
{
private:
/// Packets which have just arrived from the radio, ready to be processed by this service and possibly
@@ -50,6 +51,9 @@ class Router : protected concurrency::OSThread
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool cancelSending(NodeNum from, PacketId id);
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool findInTxQueue(NodeNum from, PacketId id);
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
* The returned packet is guaranteed to have a unique packet ID already assigned
*/

View File

@@ -11,6 +11,9 @@
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and SX126x power config forgotten)
#if ARCH_PORTDUINO
#define SX126X_MAX_POWER settingsMap[sx126x_max_power]
#endif
#ifndef SX126X_MAX_POWER
#define SX126X_MAX_POWER 22
#endif
@@ -50,22 +53,13 @@ template <typename T> bool SX126xInterface<T>::init()
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH);
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT);
}
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
float tcxoVoltage =
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.h#L471C26-L471C104
// (DIO3 is free to be used as an IRQ)
#elif !defined(TCXO_OPTIONAL)
float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE;
// (DIO3 is not free to be used as an IRQ)
#endif
if (tcxoVoltage == 0)
if (tcxoVoltage == 0.0)
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
else
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
@@ -83,7 +77,7 @@ template <typename T> bool SX126xInterface<T>::init()
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x`
LOG_INFO("SX126x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
LOG_INFO("Frequency set to %f", getFreq());
@@ -300,10 +294,17 @@ template <typename T> void SX126xInterface<T>::startReceive()
template <typename T> bool SX126xInterface<T>::isChannelActive()
{
// check if we can detect a LoRa preamble on the current channel
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD,
.detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
.detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
.exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result;
setStandby();
result = lora.scanChannel();
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
return true;
if (result != RADIOLIB_CHANNEL_FREE)
@@ -342,4 +343,4 @@ template <typename T> bool SX126xInterface<T>::sleep()
return true;
}
#endif
#endif

View File

@@ -28,8 +28,11 @@ template <class T> class SX126xInterface : public RadioLibInterface
bool isIRQPending() override { return lora.getIrqFlags() != 0; }
void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; }
protected:
float currentLimit = 140; // Higher OCP limit for SX126x PA
float tcxoVoltage = 0.0;
/**
* Specific module instance

View File

@@ -10,6 +10,9 @@
#endif
// Particular boards might define a different max power based on what their hardware can do
#if ARCH_PORTDUINO
#define SX128X_MAX_POWER settingsMap[sx128x_max_power]
#endif
#ifndef SX128X_MAX_POWER
#define SX128X_MAX_POWER 13
#endif
@@ -275,10 +278,17 @@ template <typename T> void SX128xInterface<T>::startReceive()
template <typename T> bool SX128xInterface<T>::isChannelActive()
{
// check if we can detect a LoRa preamble on the current channel
ChannelScanConfig_t cfg = {.cad = {.symNum = NUM_SYM_CAD_24GHZ,
.detPeak = 0,
.detMin = 0,
.exitMode = 0,
.timeout = 0,
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
int16_t result;
setStandby();
result = lora.scanChannel();
result = lora.scanChannel(cfg);
if (result == RADIOLIB_LORA_DETECTED)
return true;
if (result != RADIOLIB_CHANNEL_FREE)
@@ -315,4 +325,4 @@ template <typename T> bool SX128xInterface<T>::sleep()
return true;
}
#endif
#endif

104
src/mesh/api/PacketAPI.cpp Normal file
View File

@@ -0,0 +1,104 @@
#ifdef USE_PACKET_API
#include "api/PacketAPI.h"
#include "MeshService.h"
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "modules/NodeInfoModule.h"
PacketAPI *packetAPI = nullptr;
PacketAPI *PacketAPI::create(PacketServer *_server)
{
if (!packetAPI) {
packetAPI = new PacketAPI(_server);
}
return packetAPI;
}
PacketAPI::PacketAPI(PacketServer *_server) : concurrency::OSThread("PacketAPI"), isConnected(false), server(_server) {}
int32_t PacketAPI::runOnce()
{
bool success = sendPacket();
success |= receivePacket();
return success ? 10 : 50;
}
bool PacketAPI::receivePacket(void)
{
bool data_received = false;
while (server->hasData()) {
isConnected = true;
data_received = true;
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
lastContactMsec = millis();
meshtastic_ToRadio *mr;
auto p = server->receivePacket()->move();
int id = p->getPacketId();
LOG_DEBUG("Received packet id=%u", id);
mr = (meshtastic_ToRadio *)&static_cast<DataPacket<meshtastic_ToRadio> *>(p.get())->getData();
switch (mr->which_payload_variant) {
case meshtastic_ToRadio_packet_tag: {
meshtastic_MeshPacket *mp = &mr->packet;
printPacket("PACKET FROM QUEUE", mp);
service->handleToRadio(*mp);
break;
}
case meshtastic_ToRadio_want_config_id_tag: {
uint32_t config_nonce = mr->want_config_id;
LOG_INFO("Screen wants config, nonce=%u", config_nonce);
handleStartConfig();
break;
}
case meshtastic_ToRadio_heartbeat_tag:
if (mr->heartbeat.dummy_field == 1) {
if (nodeInfoModule) {
LOG_INFO("Broadcasting nodeinfo ping");
nodeInfoModule->sendOurNodeInfo(NODENUM_BROADCAST, true, 0, true);
}
} else {
LOG_DEBUG("Got client heartbeat");
}
break;
default:
LOG_ERROR("Error: unhandled meshtastic_ToRadio variant: %d", mr->which_payload_variant);
break;
}
}
return data_received;
}
bool PacketAPI::sendPacket(void)
{
// fill dummy buffer; we don't use it, we directly send the fromRadio structure
uint32_t len = getFromRadio(txBuf);
if (len != 0) {
static uint32_t id = 0;
fromRadioScratch.id = ++id;
// TODO: think about redesign or drop class MeshPacketServer
// if (typeid(*server) == typeid(MeshPacketServer))
// return dynamic_cast<MeshPacketServer*>(server)->sendPacket(fromRadioScratch);
// else
bool result = server->sendPacket(DataPacket<meshtastic_FromRadio>(id, fromRadioScratch));
if (!result) {
LOG_ERROR("send queue full");
}
return result;
} else
return false;
}
/**
* return true if we got (once!) contact from our client and the server send queue is not full
*/
bool PacketAPI::checkIsConnected()
{
isConnected |= server->hasData();
return isConnected && server->available();
}
#endif

36
src/mesh/api/PacketAPI.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include "PhoneAPI.h"
#include "comms/PacketServer.h"
#include "concurrency/OSThread.h"
/**
* A version of the phone API used for inter task communication based on protobuf packets, e.g.
* between two tasks running on CPU0 and CPU1, respectively.
*
*/
class PacketAPI : public PhoneAPI, public concurrency::OSThread
{
public:
static PacketAPI *create(PacketServer *_server);
virtual ~PacketAPI(){};
virtual int32_t runOnce();
protected:
PacketAPI(PacketServer *_server);
// Check the current underlying physical queue to see if the client is fetching packets
bool checkIsConnected() override;
void onNowHasData(uint32_t fromRadioNum) override {}
void onConnectionChanged(bool connected) override {}
private:
bool receivePacket(void);
bool sendPacket(void);
bool isConnected;
PacketServer *server;
uint8_t txBuf[MAX_TO_FROM_RADIO_SIZE] = {0}; // dummy buf to obey PhoneAPI
};
extern PacketAPI *packetAPI;

View File

@@ -468,6 +468,9 @@ typedef struct _meshtastic_Config_DisplayConfig {
bool wake_on_tap_or_motion;
/* Indicates how to rotate or invert the compass output to accurate display on the display. */
meshtastic_Config_DisplayConfig_CompassOrientation compass_orientation;
/* If false (default), the device will display the time in 24-hour format on screen.
If true, the device will display the time in 12-hour format on screen. */
bool use_12h_clock;
} meshtastic_Config_DisplayConfig;
/* Lora Config */
@@ -690,7 +693,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -701,7 +704,7 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0}
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
@@ -765,6 +768,7 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_heading_bold_tag 9
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
#define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -907,7 +911,8 @@ X(a, STATIC, SINGULAR, UENUM, oled, 7) \
X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11)
X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \
X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12)
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
@@ -985,7 +990,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
#define meshtastic_Config_BluetoothConfig_size 10
#define meshtastic_Config_DeviceConfig_size 98
#define meshtastic_Config_DisplayConfig_size 30
#define meshtastic_Config_DisplayConfig_size 32
#define meshtastic_Config_LoRaConfig_size 85
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
#define meshtastic_Config_NetworkConfig_size 202

View File

@@ -18,6 +18,9 @@ PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO)
PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2)
PB_BIND(meshtastic_NodeDatabase, meshtastic_NodeDatabase, AUTO)
PB_BIND(meshtastic_ChannelFile, meshtastic_ChannelFile, 2)

View File

@@ -132,10 +132,17 @@ typedef struct _meshtastic_DeviceState {
/* The mesh's nodes with their available gpio pins for RemoteHardware module */
pb_size_t node_remote_hardware_pins_count;
meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12];
/* New lite version of NodeDB to decrease memory footprint */
std::vector<meshtastic_NodeInfoLite> node_db_lite;
} meshtastic_DeviceState;
typedef struct _meshtastic_NodeDatabase {
/* A version integer used to invalidate old save files when we make
incompatible changes This integer is set at build time and is private to
NodeDB.cpp in the device code. */
uint32_t version;
/* New lite version of NodeDB to decrease memory footprint */
std::vector<meshtastic_NodeInfoLite> nodes;
} meshtastic_NodeDatabase;
/* The on-disk saved channels */
typedef struct _meshtastic_ChannelFile {
/* The channels our node knows about */
@@ -156,12 +163,14 @@ extern "C" {
#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
#define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}}
#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0}
#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}, {0}}
#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_NodeDatabase_init_default {0, {0}}
#define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
#define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}}
#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0}
#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}, {0}}
#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_NodeDatabase_init_zero {0, {0}}
#define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
/* Field tags (for use in manual encoding/decoding) */
@@ -198,7 +207,8 @@ extern "C" {
#define meshtastic_DeviceState_did_gps_reset_tag 11
#define meshtastic_DeviceState_rx_waypoint_tag 12
#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13
#define meshtastic_DeviceState_node_db_lite_tag 14
#define meshtastic_NodeDatabase_version_tag 1
#define meshtastic_NodeDatabase_nodes_tag 2
#define meshtastic_ChannelFile_channels_tag 1
#define meshtastic_ChannelFile_version_tag 2
@@ -251,10 +261,8 @@ X(a, STATIC, SINGULAR, UINT32, version, 8) \
X(a, STATIC, SINGULAR, BOOL, no_save, 9) \
X(a, STATIC, SINGULAR, BOOL, did_gps_reset, 11) \
X(a, STATIC, OPTIONAL, MESSAGE, rx_waypoint, 12) \
X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13) \
X(a, CALLBACK, REPEATED, MESSAGE, node_db_lite, 14)
extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
#define meshtastic_DeviceState_CALLBACK meshtastic_DeviceState_callback
X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13)
#define meshtastic_DeviceState_CALLBACK NULL
#define meshtastic_DeviceState_DEFAULT NULL
#define meshtastic_DeviceState_my_node_MSGTYPE meshtastic_MyNodeInfo
#define meshtastic_DeviceState_owner_MSGTYPE meshtastic_User
@@ -262,7 +270,14 @@ extern bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t
#define meshtastic_DeviceState_rx_text_message_MSGTYPE meshtastic_MeshPacket
#define meshtastic_DeviceState_rx_waypoint_MSGTYPE meshtastic_MeshPacket
#define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin
#define meshtastic_DeviceState_node_db_lite_MSGTYPE meshtastic_NodeInfoLite
#define meshtastic_NodeDatabase_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, version, 1) \
X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2)
extern bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
#define meshtastic_NodeDatabase_CALLBACK meshtastic_NodeDatabase_callback
#define meshtastic_NodeDatabase_DEFAULT NULL
#define meshtastic_NodeDatabase_nodes_MSGTYPE meshtastic_NodeInfoLite
#define meshtastic_ChannelFile_FIELDLIST(X, a) \
X(a, STATIC, REPEATED, MESSAGE, channels, 1) \
@@ -275,6 +290,7 @@ extern const pb_msgdesc_t meshtastic_PositionLite_msg;
extern const pb_msgdesc_t meshtastic_UserLite_msg;
extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg;
extern const pb_msgdesc_t meshtastic_DeviceState_msg;
extern const pb_msgdesc_t meshtastic_NodeDatabase_msg;
extern const pb_msgdesc_t meshtastic_ChannelFile_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
@@ -282,12 +298,14 @@ extern const pb_msgdesc_t meshtastic_ChannelFile_msg;
#define meshtastic_UserLite_fields &meshtastic_UserLite_msg
#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg
#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg
#define meshtastic_NodeDatabase_fields &meshtastic_NodeDatabase_msg
#define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_DeviceState_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_ChannelFile_size
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_DeviceState_size
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1720
#define meshtastic_NodeInfoLite_size 188
#define meshtastic_PositionLite_size 28
#define meshtastic_UserLite_size 96

View File

@@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define meshtastic_LocalConfig_size 741
#define meshtastic_LocalConfig_size 743
#define meshtastic_LocalModuleConfig_size 699
#ifdef __cplusplus

View File

@@ -1775,4 +1775,4 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
} /* extern "C" */
#endif
#endif
#endif

View File

@@ -23,8 +23,6 @@
#define MAX_NUM_NODES 100
#endif
#define MAX_NUM_NODES_FS 100
/// Max number of channels allowed
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))

View File

@@ -65,6 +65,9 @@ mail: marchammermann@googlemail.com
#define DEFAULT_REALM "default_realm"
#define PREFIX ""
#define KEY_PATH settingsStrings[websslkeypath].c_str()
#define CERT_PATH settingsStrings[websslcertpath].c_str()
struct _file_config configWeb;
// We need to specify some content-type mapping, so the resources get delivered with the
@@ -384,13 +387,13 @@ char *read_file_into_string(const char *filename)
int PiWebServerThread::CheckSSLandLoad()
{
// read certificate
cert_pem = read_file_into_string("certificate.pem");
cert_pem = read_file_into_string(CERT_PATH);
if (cert_pem == NULL) {
LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing");
return 1;
}
// read private key
key_pem = read_file_into_string("private_key.pem");
key_pem = read_file_into_string(KEY_PATH);
if (key_pem == NULL) {
LOG_ERROR("ERROR file private_key can't be loaded or is missing");
return 2;
@@ -415,8 +418,8 @@ int PiWebServerThread::CreateSSLCertificate()
return 2;
}
// Ope file to write private key file
FILE *pkey_file = fopen("private_key.pem", "wb");
// Open file to write private key file
FILE *pkey_file = fopen(KEY_PATH, "wb");
if (!pkey_file) {
LOG_ERROR("Error opening private key file");
return 3;
@@ -426,18 +429,19 @@ int PiWebServerThread::CreateSSLCertificate()
fclose(pkey_file);
// open Certificate file
FILE *x509_file = fopen("certificate.pem", "wb");
FILE *x509_file = fopen(CERT_PATH, "wb");
if (!x509_file) {
LOG_ERROR("Error opening cert");
return 4;
}
// write cirtificate
// write certificate
PEM_write_X509(x509_file, x509);
fclose(x509_file);
EVP_PKEY_free(pkey);
LOG_INFO("Create SSL Key %s successful", KEY_PATH);
X509_free(x509);
LOG_INFO("Create SSL Cert -certificate.pem- succesfull ");
LOG_INFO("Create SSL Cert %s successful", CERT_PATH);
return 0;
}

View File

@@ -0,0 +1,70 @@
#pragma once
#if HAS_UDP_MULTICAST
#include "configuration.h"
#include "main.h"
#include "mesh/Router.h"
#include <AsyncUDP.h>
#include <WiFi.h>
#define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server
#define UDP_MULTICAST_THREAD_INTERVAL_MS 15000
class UdpMulticastThread : public concurrency::OSThread
{
public:
UdpMulticastThread() : OSThread("UdpMulticast") { udpIpAddress = IPAddress(224, 0, 0, 69); }
void start()
{
if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT)) {
LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str());
udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); });
} else {
LOG_DEBUG("Failed to listen on UDP");
}
}
void onReceive(AsyncUDPPacket packet)
{
size_t packetLength = packet.length();
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
meshtastic_MeshPacket mp;
uint8_t bytes[meshtastic_MeshPacket_size]; // Allocate buffer for the data
size_t packetSize = packet.readBytes(bytes, packet.length());
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetSize);
bool isPacketDecoded = pb_decode_from_bytes(bytes, packetLength, &meshtastic_MeshPacket_msg, &mp);
if (isPacketDecoded && router) {
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
// Unset received SNR/RSSI
p->rx_snr = 0;
p->rx_rssi = 0;
router->enqueueReceivedMessage(p.release());
}
}
bool onSend(const meshtastic_MeshPacket *mp)
{
if (!mp || WiFi.status() != WL_CONNECTED) {
return false;
}
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
uint8_t buffer[meshtastic_MeshPacket_size];
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
udp.broadcastTo(buffer, encodedLength, UDP_MULTICAST_DEFAUL_PORT);
return true;
}
protected:
int32_t runOnce() override
{
canSleep = true;
// TODO: Implement nodeinfo broadcast
return UDP_MULTICAST_THREAD_INTERVAL_MS;
}
private:
IPAddress udpIpAddress;
AsyncUDP udp;
};
#endif // ARCH_ESP32

View File

@@ -112,6 +112,12 @@ static void onNetworkConnected()
APStartupComplete = true;
}
#if HAS_UDP_MULTICAST
if (udpThread) {
udpThread->start();
}
#endif
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
#ifndef MESHTASTIC_EXCLUDE_MQTT
if (mqtt)
@@ -437,4 +443,4 @@ uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
}
#endif
#endif

View File

@@ -352,7 +352,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
LOG_INFO("Client requesting to enter DFU mode");
#if defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARDUINO_USB_CDC_ON_BOOT)
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
enterDfuMode();
#endif
break;
@@ -1160,4 +1160,4 @@ void disableBluetooth()
nrf52Bluetooth->shutdown();
#endif
#endif
}
}

View File

@@ -81,7 +81,7 @@ int32_t DetectionSensorModule::runOnce()
}
LOG_INFO("Detection Sensor Module: init");
return DELAYED_INTERVAL;
return setStartDelay();
}
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i", digitalRead(moduleConfig.detection_sensor.monitor_pin));
@@ -161,4 +161,4 @@ bool DetectionSensorModule::hasDetectionEvent()
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
// LOG_DEBUG("Detection Sensor Module: Current state: %i", currentState);
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
}
}

View File

@@ -97,8 +97,9 @@ NodeInfoModule::NodeInfoModule()
: ProtobufModule("nodeinfo", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg), concurrency::OSThread("NodeInfo")
{
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
setIntervalFromNow(30 *
1000); // Send our initial owner announcement 30 seconds after we start (to give network time to setup)
setIntervalFromNow(setStartDelay()); // Send our initial owner announcement 30 seconds
// after we start (to give network time to setup)
}
int32_t NodeInfoModule::runOnce()
@@ -112,4 +113,4 @@ int32_t NodeInfoModule::runOnce()
sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
}
return Default::getConfiguredOrDefaultMs(config.device.node_info_broadcast_secs, default_node_info_broadcast_secs);
}
}

View File

@@ -28,8 +28,9 @@ PositionModule::PositionModule()
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
setIntervalFromNow(60 * 1000);
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
setIntervalFromNow(setStartDelay());
}
// Power saving trackers should clear their position on startup to avoid waking up and sending a stale position
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
@@ -160,7 +161,8 @@ bool PositionModule::hasGPS()
#endif
}
meshtastic_MeshPacket *PositionModule::allocReply()
// Allocate a packet with our position data if we have one
meshtastic_MeshPacket *PositionModule::allocPositionPacket()
{
if (precision == 0) {
LOG_DEBUG("Skip location send because precision is set to 0!");
@@ -262,7 +264,8 @@ meshtastic_MeshPacket *PositionModule::allocReply()
p.has_ground_speed = true;
}
LOG_INFO("Position reply: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
LOG_INFO("Position packet: time=%i lat=%i lon=%i", p.time, p.latitude_i, p.longitude_i);
lastSentToMesh = millis();
// TAK Tracker devices should send their position in a TAK packet over the ATAK port
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
@@ -271,6 +274,17 @@ meshtastic_MeshPacket *PositionModule::allocReply()
return allocDataProtobuf(p);
}
meshtastic_MeshPacket *PositionModule::allocReply()
{
if (config.device.role != meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND && lastSentToMesh &&
Throttle::isWithinTimespanMs(lastSentToMesh, 3 * 60 * 1000)) {
LOG_DEBUG("Skip Position reply since we sent it <3min ago");
ignoreRequest = true; // Mark it as ignored for MeshModule
return nullptr;
}
return allocPositionPacket();
}
meshtastic_MeshPacket *PositionModule::allocAtakPli()
{
LOG_INFO("Send TAK PLI packet");
@@ -333,9 +347,9 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
precision = 0;
}
meshtastic_MeshPacket *p = allocReply();
meshtastic_MeshPacket *p = allocPositionPacket();
if (p == nullptr) {
LOG_DEBUG("allocReply returned a nullptr");
LOG_DEBUG("allocPositionPacket returned a nullptr");
return;
}

View File

@@ -55,6 +55,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
virtual int32_t runOnce() override;
private:
meshtastic_MeshPacket *allocPositionPacket();
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
meshtastic_MeshPacket *allocAtakPli();
void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
@@ -62,6 +63,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
void sendLostAndFoundText();
bool hasQualityTimesource();
bool hasGPS();
uint32_t lastSentToMesh = 0; // Last time we sent our position to the mesh
const uint32_t minimumTimeThreshold =
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);

View File

@@ -50,12 +50,12 @@ int32_t AirQualityTelemetryModule::runOnce()
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
i2cScanner->fetchI2CBus(found.address);
return 1000;
return setStartDelay();
}
#endif
return disable();
}
return 1000;
return setStartDelay();
}
return disable();
} else {

View File

@@ -18,7 +18,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
uptimeWrapCount = 0;
uptimeLastMs = millis();
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent
setIntervalFromNow(setStartDelay()); // Wait until NodeInfo is sent
}
virtual bool wantUIFrame() { return false; }
@@ -62,4 +62,4 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
uint32_t uptimeWrapCount;
uint32_t uptimeLastMs;
};
};

View File

@@ -107,8 +107,6 @@ int32_t EnvironmentTelemetryModule::runOnce()
if (moduleConfig.telemetry.environment_measurement_enabled) {
LOG_INFO("Environment Telemetry: init");
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
#ifdef SENSECAP_INDICATOR
result = indicatorSensor.runOnce();
#endif
@@ -171,7 +169,9 @@ int32_t EnvironmentTelemetryModule::runOnce()
#endif
#endif
}
return result;
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
return result == UINT32_MAX ? disable() : setStartDelay();
} else {
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
if (!moduleConfig.telemetry.environment_measurement_enabled) {

View File

@@ -62,7 +62,7 @@ int32_t HealthTelemetryModule::runOnce()
if (max30102Sensor.hasSensor())
result = max30102Sensor.runOnce();
}
return result;
return result == UINT32_MAX ? disable() : setStartDelay();
} else {
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
if (!moduleConfig.telemetry.health_measurement_enabled) {

View File

@@ -65,7 +65,7 @@ int32_t PowerTelemetryModule::runOnce()
if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized())
result = max17048Sensor.runOnce();
}
return result;
return result == UINT32_MAX ? disable() : setStartDelay();
#else
return disable();
#endif

View File

@@ -40,14 +40,14 @@ bool INA226Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.has_current = true;
// mV conversion to V
measurement->variant.environment_metrics.voltage = ina226.getBusVoltage() / 1000;
measurement->variant.environment_metrics.voltage = ina226.getBusVoltage();
measurement->variant.environment_metrics.current = ina226.getCurrent_mA();
return true;
}
uint16_t INA226Sensor::getBusVoltageMv()
{
return lround(ina226.getBusVoltage());
return lround(ina226.getBusVoltage() * 1000);
}
int16_t INA226Sensor::getCurrentMa()

View File

@@ -109,7 +109,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa
void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination)
{
#ifdef DEBUG_PORT
std::string route = "Route traced:";
std::string route = "Route traced:\n";
route += vformat("0x%x --> ", origin);
for (uint8_t i = 0; i < r->route_count; i++) {
if (i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN)
@@ -129,6 +129,7 @@ void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin,
// If there's a route back (or we are the destination as then the route is complete), print it
if (r->route_back_count > 0 || origin == nodeDB->getNodeNum()) {
route += "\n";
if (r->snr_towards_count > 0 && origin == nodeDB->getNodeNum())
route += vformat("(%.2fdB) 0x%x <-- ", (float)r->snr_back[r->snr_back_count - 1] / 4, origin);
else

View File

@@ -135,20 +135,6 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
screen->drawCompassNorth(display, compassX, compassY, myHeading);
// Distance to Waypoint
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
else
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
}
// Compass bearing to waypoint
float bearingToOther =
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
@@ -157,6 +143,25 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
if (!config.display.compass_north_top)
bearingToOther -= myHeading;
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2*PI : bearingToOther;
bearingToOtherDegrees = bearingToOtherDegrees * 180 / PI;
// Distance to Waypoint
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (d < (2 * MILES_TO_FEET))
snprintf(distStr, sizeof(distStr), "%.0fft %.0f°", d * METERS_TO_FEET, bearingToOtherDegrees);
else
snprintf(distStr, sizeof(distStr), "%.1fmi %.0f°", d * METERS_TO_FEET / MILES_TO_FEET, bearingToOtherDegrees);
} else {
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0fm %.0f°", d, bearingToOtherDegrees);
else
snprintf(distStr, sizeof(distStr), "%.1fkm %.0f°", d / 1000, bearingToOtherDegrees);
}
}
// If our node doesn't have position
@@ -166,9 +171,9 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
// ? in the distance field
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
strncpy(distStr, "? mi", sizeof(distStr));
strncpy(distStr, "? mi", sizeof(distStr));
else
strncpy(distStr, "? km", sizeof(distStr));
strncpy(distStr, "? km", sizeof(distStr));
}
// Draw compass circle

View File

@@ -217,6 +217,7 @@ bool isPrivateIpAddress(const IPAddress &ip)
{.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16
{.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8
{.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32
{.network = 100u << 24 | 64 << 16, .mask = 0xffc00000}, // 100.64.0.0/10
};
const uint32_t addr = ntohl(ip);
for (const auto &cidrRange : privateCidrRanges) {

View File

@@ -248,12 +248,3 @@ void cpuDeepSleep(uint32_t msecToWake)
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
esp_deep_sleep_start(); // TBD mA sleep current (battery)
}
void enterDfuMode()
{
#if defined(ARDUINO_USB_CDC_ON_BOOT)
// This is a no-op on the ESP32 normally, but on the ESP32-S3/C3 it will reboot to USB/JTAG mode
REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
esp_restart();
#endif
}

View File

@@ -369,6 +369,12 @@ bool loadConfig(const char *configPath)
}
}
settingsMap[sx126x_max_power] = yamlConfig["Lora"]["SX126X_MAX_POWER"].as<int>(22);
settingsMap[sx128x_max_power] = yamlConfig["Lora"]["SX128X_MAX_POWER"].as<int>(13);
settingsMap[lr1110_max_power] = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
settingsMap[lr1120_max_power] = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
settingsMap[rf95_max_power] = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);
settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
@@ -518,7 +524,12 @@ bool loadConfig(const char *configPath)
if (yamlConfig["Webserver"]) {
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
settingsStrings[webserverrootpath] =
(yamlConfig["Webserver"]["RootPath"]).as<std::string>("/usr/share/meshtasticd/web");
settingsStrings[websslkeypath] =
(yamlConfig["Webserver"]["SSLKey"]).as<std::string>("/etc/meshtasticd/ssl/private_key.pem");
settingsStrings[websslcertpath] =
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
}
if (yamlConfig["General"]) {
@@ -569,4 +580,4 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
} else {
return false;
}
}
}

View File

@@ -27,6 +27,11 @@ enum configNames {
sx126x_ant_sw_pin,
sx126x_ant_sw_line,
sx126x_ant_sw_gpiochip,
sx126x_max_power,
sx128x_max_power,
lr1110_max_power,
lr1120_max_power,
rf95_max_power,
dio2_as_rf_switch,
dio3_tcxo_voltage,
use_rf95,
@@ -76,6 +81,8 @@ enum configNames {
webserver,
webserverport,
webserverrootpath,
websslkeypath,
websslcertpath,
maxtophone,
maxnodes,
ascii_logs,
@@ -94,4 +101,4 @@ int initGPIOPin(int pinNum, std::string gpioChipname, int line);
bool loadConfig(const char *configPath);
static bool ends_with(std::string_view str, std::string_view suffix);
void getMacAddr(uint8_t *dmac);
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
bool MAC_from_string(std::string mac_str, uint8_t *dmac);

View File

@@ -139,6 +139,12 @@ bool SimRadio::cancelSending(NodeNum from, PacketId id)
return result;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool SimRadio::findInTxQueue(NodeNum from, PacketId id)
{
return txQueue.find(from, id);
}
void SimRadio::onNotify(uint32_t notification)
{
switch (notification) {

View File

@@ -33,6 +33,9 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
virtual bool cancelSending(NodeNum from, PacketId id) override;
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
/**
* Start waiting to receive a message
*

View File

@@ -22,7 +22,6 @@
* THE SOFTWARE.
*/
#include <iostream>
#include <math.h>
#include <sstream>
#include <stdio.h>
@@ -851,18 +850,26 @@ std::string JSONValue::StringifyString(const std::string &str)
str_out += "\\r";
} else if (chr == '\t') {
str_out += "\\t";
} else if (chr < ' ' || chr > 126) {
str_out += "\\u";
for (int i = 0; i < 4; i++) {
int value = (chr >> 12) & 0xf;
if (value >= 0 && value <= 9)
str_out += (char)('0' + value);
else if (value >= 10 && value <= 15)
str_out += (char)('A' + (value - 10));
chr <<= 4;
}
} else if (chr < 0x20 || chr == 0x7F) {
char buf[7];
snprintf(buf, sizeof(buf), "\\u%04x", chr);
str_out += buf;
} else if (chr < 0x80) {
str_out += chr;
} else {
str_out += chr;
size_t remain = str.end() - iter - 1;
if ((chr & 0xE0) == 0xC0 && remain >= 1) {
++iter;
str_out += *iter;
} else if ((chr & 0xF0) == 0xE0 && remain >= 2) {
str_out += *(++iter);
str_out += *(++iter);
} else if ((chr & 0xF8) == 0xF0 && remain >= 3) {
str_out += *(++iter);
str_out += *(++iter);
str_out += *(++iter);
}
}
++iter;

View File

@@ -29,9 +29,13 @@
// "USERPREFS_FIXED_GPS_LON": "2.294508368",
// "USERPREFS_LORACONFIG_CHANNEL_NUM": "31",
// "USERPREFS_LORACONFIG_MODEM_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST",
// "USERPREFS_SPLASH_TITLE": "DEFCONtastic",
"USERPREFS_TZ_STRING": "tzplaceholder "
// "USERPREFS_USE_ADMIN_KEY_0": "{ 0xcd, 0xc0, 0xb4, 0x3c, 0x53, 0x24, 0xdf, 0x13, 0xca, 0x5a, 0xa6, 0x0c, 0x0d, 0xec, 0x85, 0x5a, 0x4c, 0xf6, 0x1a, 0x96, 0x04, 0x1a, 0x3e, 0xfc, 0xbb, 0x8e, 0x33, 0x71, 0xe5, 0xfc, 0xff, 0x3c }",
// "USERPREFS_USE_ADMIN_KEY_1": "{}",
// "USERPREFS_USE_ADMIN_KEY_2": "{}"
}
// "USERPREFS_USE_ADMIN_KEY_2": "{}",
// "USERPREFS_OEM_TEXT": "Caterham Car Club",
// "USERPREFS_OEM_FONT_SIZE": "0",
// "USERPREFS_OEM_IMAGE_WIDTH": "50",
// "USERPREFS_OEM_IMAGE_HEIGHT": "28",
// "USERPREFS_OEM_IMAGE_DATA": "{ 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x18, 0xFF, 0xFF, 0x67, 0x00, 0x00, 0x00, 0x18, 0x1F, 0xF0, 0x67, 0x00, 0x00, 0x00, 0x30, 0x1F, 0xF8, 0x33, 0x00, 0x00, 0x00, 0x30, 0x00, 0xFC, 0x31, 0x00, 0x00, 0x00, 0x60, 0x00, 0xFE, 0x18, 0x00, 0x00, 0x00, 0x60, 0x00, 0x7E, 0x18, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x3F, 0x0C, 0x00, 0x00, 0x00, 0xC0, 0x80, 0x1F, 0x0C, 0x00, 0x00, 0x00, 0x80, 0x81, 0x1F, 0x06, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x0F, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x8F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xC7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00}"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,107 @@
# Notes
## General
The pinout is contained in the variant.h file, and a [generic schematic](./Schematic_Pro-Micro_Pinouts%202024-12-14.pdf) is located in this directory.
### Note on DIO2, RXEN, TXEN, and RF switching
Several modules require external switching between transmit (Tx) and receive (Rx). This can be achieved using several methods:
1. Link the TXEN pin on the radio module to DIO2 on the same module, and then connect RXEN on the radio module to pin 0.17 on the Pro-Micro.
2. Use DIO2 to drive a logic inverter, so that when DIO2 is `high`, RXEN is `low`, and vice versa.
3. Use DIO2 to drive a pair of MOSFETs or transistors to supply the same function.
RXEN is not required to be connected if the selected module already has internal RF switching, or if external RF switching logic is already applied.
Also worth noting that the Seeed WIO SX1262 in particular only has RXEN exposed (marked RF_SW) and has the DIO2-TXEN link internally.
<details>
<summary> The table of known modules is at the bottom of the variant.h, and reproduced here for convenience. </summary>
| Mfr | Module | TCXO | RF Switch | Notes |
| ------------ | ---------------- | ---- | --------- | ------------------------------------- |
| Ebyte | E22-900M22S | Yes | Ext | |
| Ebyte | E22-900MM22S | No | Ext | |
| Ebyte | E22-900M30S | Yes | Ext | |
| Ebyte | E22-900M33S | Yes | Ext | MAX_POWER must be set to 8 for this |
| Ebyte | E220-900M22S | No | Ext | LLCC68, looks like DIO3 not connected |
| AI-Thinker | RA-01SH | No | Int | SX1262 |
| Heltec | HT-RA62 | Yes | Int | |
| NiceRF | Lora1262 | yes | Int | |
| Waveshare | Core1262-HF | yes | Ext | |
| Waveshare | LoRa Node Module | yes | Int | |
| Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed |
| AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** |
| RF Solutions | RFM95 | No | Int | Untested |
| Ebyte | E80-900M2213S | Yes | Int | LR1121 radio |
</details>
## LR1121 modules - E80 is the default
The E80 from CDEbyte is the most obtainable module at present, and has been selected as the default option.
Naturally, CDEbyte have chosen to ignore the generic Semtech impelementation of the RF switching logic and have supplied confusing and contradictory documentation, which is explained below.
tl;dr: The E80 is chosen as the default. **If you wish to use another module, the table in `rfswitch.h` must be adjusted accordingly.**
### E80 switching - the saga
The CDEbyte implementation of the LR1121 is contained in their E80 module. As stated above, CDEbyte have chosen to ignore the generic Semtech implementation of the RF switching logic and have their own table, which is located at the bottom of the page [here](https://www.cdebyte.com/products/E80-900M2213S/2#Pin), and reflected on page 6 of their user manual, and reproduced below:
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
| ---------- | ---------- | ----------------------------- |
| 0 | 0 | RX |
| 0 | 1 | TX (Sub-1GHz low power mode) |
| 1 | 0 | TX (Sub-1GHz high power mode) |
| 1 | 1 | TX2.4GHz |
However, looking at the sample code they provide on page 9, the values would be:
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
| ---------- | ---------- | ----------------------------- |
| 0 | 1 | RX |
| 1 | 1 | TX (Sub-1GHz low power mode) |
| 1 | 0 | TX (Sub-1GHz high power mode) |
| 0 | 0 | TX2.4GHz |
The Semtech default, the values are (taken from [here](https://github.com/Lora-net/SWSD006/blob/v2.6.1/lib/app_subGHz_config_lr11xx.c#L145-L154)):
<details>
```cpp
.rfswitch = {
.enable = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH | LR11XX_SYSTEM_RFSW2_HIGH,
.standby = 0,
.rx = LR11XX_SYSTEM_RFSW0_HIGH,
.tx = LR11XX_SYSTEM_RFSW0_HIGH | LR11XX_SYSTEM_RFSW1_HIGH,
.tx_hp = LR11XX_SYSTEM_RFSW1_HIGH,
.tx_hf = 0,
.gnss = LR11XX_SYSTEM_RFSW2_HIGH,
.wifi = 0,
},
```
</details>
| DIO5/RFSW0 | DIO6/RFSW1 | RF status |
| ---------- | ---------- | ----------------------------- |
| 1 | 0 | RX |
| 1 | 1 | TX (Sub-1GHz low power mode) |
| 0 | 1 | TX (Sub-1GHz high power mode) |
| 0 | 0 | TX2.4GHz |
It is evident from the tables above that there is no real consistency to those provided by Ebyte.
#### An experiment
Tests were conducted in each of the three configurations between a known-good SX1262 and an E80, passing packets in both directions and recording the reported RSSI. The E80 was set at 22db and 14db to activate the high and low power settings respectively. The results are shown in the chart below.
![Chart showing RSSI readings in each configuration and setting](./E80_RSSI_per_case.webp)
## Conclusion
The RF switching is based on the code example given. Logically, this shows the DIO5 and DIO6 are swapped compared to the reference design.
If future DIYers wish to use an alternative module, the table in `rfswitch.h` must be adjusted accordingly.

View File

@@ -1,17 +1,20 @@
#include "RadioLib.h"
// This is rewritten to match the requirements of the E80-900M2213S
// The E80 does not conform to the reference Semtech switches(!) and therefore needs a custom matrix.
// See footnote #3 in "https://www.cdebyte.com/products/E80-900M2213S/2#Pin"
// RF Switch Matrix SubG RFO_HP_LF / RFO_LP_LF / RFI_[NP]_LF0
// DIO5 -> RFSW0_V1
// DIO6 -> RFSW1_V2
// DIO7 -> ANT_CTRL_ON + ESP_IO9/LR_GPS_ANT_DC_EN -> RFI_GPS (Bias-T GPS) (LR11x0 only)
// DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible.
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC,
RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6 DIO7
{LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW}},
{LR11x0::MODE_TX, {LOW, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW}},
{LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW, LOW}},
{LR11x0::MODE_TX_HF, {LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH}},
{LR11x0::MODE_WIFI, {LOW, LOW, LOW}}, END_OF_MODE_TABLE,
};
};

View File

@@ -22,26 +22,26 @@ extern "C" {
/*
NRF52 PRO MICRO PIN ASSIGNMENT
| Pin   | Function   |   | Pin     | Function     | RF95 |
| Pin   | Function   |   | Pin     | Function     | RF95 |
| ----- | ----------- | --- | -------- | ------------ | ----- |
| Gnd   |             |   | vbat     |             | |
| P0.06 | Serial2 RX |   | vbat     |             | |
| P0.08 | Serial2 TX |   | Gnd     |             | |
| Gnd   |             |   | reset   |             | |
| Gnd   |             |   | ext_vcc | *see 0.13   | |
| P0.17 | RXEN       |   | P0.31   | BATTERY_PIN | |
| P0.20 | GPS_RX     |   | P0.29   | BUSY         | DIO0 |
| P0.22 | GPS_TX     |   | P0.02   | MISO | MISO |
| P0.24 | GPS_EN     |   | P1.15   | MOSI         | MOSI |
| P1.00 | BUTTON_PIN |   | P1.13   | CS           | CS   |
| P0.11 | SCL         |   | P1.11   | SCK         | SCK |
| P1.04 | SDA         |   | P0.10   | DIO1/IRQ     | DIO1 |
| P1.06 | Free pin   |   | P0.09   | RESET       | RST |
|       |             |   |         |             | |
|       | Mid board   |   |         | Internal     | |
| P1.01 | Free pin   |   | 0.15     | LED         | |
| P1.02 | Free pin   |   | 0.13     | 3V3_EN       | |
| P1.07 | Free pin   |   |         |             | |
| Gnd   |             |   | vbat     |             | |
| P0.06 | Serial2 RX |   | vbat     |             | |
| P0.08 | Serial2 TX |   | Gnd     |             | |
| Gnd   |             |   | reset   |             | |
| Gnd   |             |   | ext_vcc | *see 0.13   | |
| P0.17 | RXEN       |   | P0.31   | BATTERY_PIN | |
| P0.20 | GPS_RX     |   | P0.29   | BUSY         | DIO0 |
| P0.22 | GPS_TX     |   | P0.02   | MISO | MISO |
| P0.24 | GPS_EN     |   | P1.15   | MOSI         | MOSI |
| P1.00 | BUTTON_PIN |   | P1.13   | CS           | CS   |
| P0.11 | SCL         |   | P1.11   | SCK         | SCK |
| P1.04 | SDA         |   | P0.10   | DIO1/IRQ     | DIO1 |
| P1.06 | Free pin   |   | P0.09   | RESET       | RST |
|       |             |   |         |             | |
|       | Mid board   |   |         | Internal     | |
| P1.01 | Free pin   |   | 0.15     | LED         | |
| P1.02 | Free pin   |   | 0.13     | 3V3_EN       | |
| P1.07 | Free pin   |   |         |             | |
*/
// Number of pins defined in PinDescription array
@@ -175,7 +175,7 @@ settings.
| NiceRF | Lora1262 | yes | Int | |
| Waveshare | Core1262-HF | yes | Ext | |
| Waveshare | LoRa Node Module | yes | Int | |
| Seeed | Wio-SX1262 | yes | Int | Sooooo cute! |
| Seeed | Wio-SX1262 | yes | Ext | Cute! DIO2/TXEN are not exposed |
| AI-Thinker | RA-02 | No | Int | SX1278 **433mhz band only** |
| RF Solutions | RFM95 | No | Int | Untested |
| Ebyte | E80-900M2213S | Yes | Int | LR1121 radio |
@@ -183,8 +183,7 @@ settings.
*/
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
extern float tcxoVoltage; // make this available everywhere
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
#ifdef __cplusplus
}
@@ -194,4 +193,4 @@ extern float tcxoVoltage; // make this available everywhere
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
#endif

View File

@@ -10,12 +10,8 @@ build_flags =
-DEINK_HEIGHT=122
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
-DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
-DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
-DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
-DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
lib_deps =
${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d

View File

@@ -11,12 +11,8 @@ build_flags =
-D EINK_HEIGHT=128
-D USE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
-D EINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
-D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
; -D EINK_LIMIT_GHOSTING_PX=2000 ; How much image ghosting is tolerated
; -D EINK_BACKGROUND_USES_FAST ; (If enabled) don't redraw RESPONSIVE frames at next BACKGROUND update
lib_deps =
${esp32s3_base.lib_deps}

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