Compare commits

..

136 Commits

Author SHA1 Message Date
Thomas Göttgens
48af1cd0e1 ThinkNode G3, ETH support WIP 2025-11-27 09:52:27 +01:00
Jason P
c3a7ad2865 More GPS pin flips for devices (#8760) 2025-11-26 13:53:26 -06:00
Quency-D
06dac12a73 Swap the GPS serial port pins. (#8756)
* Swap the GPS serial port pins.

* Trunk fixes

---------

Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-26 11:10:21 -06:00
Ben Meadors
d60b263a00 Merge branch 'master' into develop 2025-11-26 06:24:41 -06:00
Jonathan Bennett
654abe5b2c Add support for muzi-base (#8753) 2025-11-25 18:28:06 -06:00
Thomas Göttgens
79e8fc94bc 3401 fix (#8755)
* Preliminary Thinknode M4 Support

* fix 3401 detection

* don't push unrelated work
2025-11-25 16:35:17 -06:00
Tom
486fa74549 Actions: Remove native from build_one (#8685)
* Remove native from the build, and remove the required permissions

* Delete .github/workflows/build_one_arch.yml

Its borken and not really needed. one_target is the goal.
2025-11-25 16:18:55 -05:00
Ben Meadors
66193e1776 Prevent double-registering of Rotary Encoder on TLora Pager (#8746)
* Reduce noise

* Prevent double registering of rotary encoder broker
2025-11-25 14:34:55 -06:00
Ben Meadors
bacff5c1f0 Reduce noise 2025-11-25 14:34:23 -06:00
Jason P
faa6af74af Swapping GPS pins for GPS TX/RX (#8751) 2025-11-25 13:55:28 -06:00
Jason P
81439f16d0 More quickly hide "Shutting Down" to prevent it showing on Eink sleep screen (#8749) 2025-11-25 08:59:11 -06:00
Jonathan Bennett
592a8f23db Further fix compass calibration (#8740)
* Update calibration logic for ICM20948 sensor

Initialize highest and lowest magnetic values based on sensor data readiness during calibration.

* Refactor BMX160 calibration to use magnetometer data

Update calibration logic to initialize highest and lowest values using magnetometer data.

* Add missed viable defines in ::calibrate()
2025-11-25 06:10:20 -06:00
Ben Meadors
2baa9ccbe0 Merge pull request #8741 from meshtastic/master
Master to develop
2025-11-25 04:52:54 -06:00
Jonathan Bennett
0336331411 Use LED_CHARGE and LED_PAIRING for M6 led control (#8742) 2025-11-25 09:29:35 +01:00
Jonathan Bennett
ed4a798c60 Thinknode M3 support against master (#8630)
* Add variant_shutdown() as a week function in main-nrf52.cpp

* Add Status LED module

* Add Thinknode M3 support

* Catch case of BLE disabled

* Update src/modules/StatusLEDModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/StatusLEDModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update variants/nrf52840/ELECROW-ThinkNode-M3/rfswitch.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove unused pin

* M3 pairing LED only active for 30 seconds after state change

* Thinknode M3 shutdown work

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 16:35:54 -06:00
Chloe Bethel
5d7da6868e Support overriding GPS serial pins on all architectures (#8486) 2025-11-24 09:40:27 +08:00
simon-muzi
1bfa9ed4c4 Tweak OCV_ARRAY 100% voltage to take into account charger hysteresis and voltage sag after charge (#8720)
Measured voltage of fully charged battery after a few minutes of rest
2025-11-22 16:35:10 -06:00
Jonathan Bennett
b18794e98d Log error if startReceive fails in LR11x0Interface (#8718) 2025-11-22 13:54:24 -06:00
simon-muzi
f4e260e0f1 R1 Neo - Added OCV_ARRAY from measured discharge curve testing + update ADC multiplier (#8716)
* Added OCV_ARRAY from measured discharge curve testing and update ADC multiplier

The ADC resistor divider ratio is 0.6 -> multiplier should be 1/0.6 ~=1.667

We data logged a full discharge curve at constant 100mA draw over 15hours to get a realistic voltage curve for battery SoC measurements.

* Remove power.h in favor of variant.h

---------

Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-11-22 13:54:10 -06:00
Avi0n
14463043bd Add WisMesh Tag OCV array (#8646)
* Add WisMesh Tag OCV array

* Update 10% to 3650

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2025-11-22 12:03:47 -06:00
renovate[bot]
376dc7ef3a Update actions/checkout action to v6 (#8695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 08:27:00 -06:00
renovate[bot]
c051c56544 Update Kongduino-Adafruit_nRFCrypto digest to 8cde718 (#8708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 08:26:39 -06:00
Ben Meadors
d3976e7461 Merge pull request #8713 from meshtastic/develop
Develop to master
2025-11-22 08:23:46 -06:00
github-actions[bot]
a4c92d9fd5 Update protobufs (#8707)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2025-11-21 20:31:12 +01:00
Ben Meadors
186cbe61bb Merge pull request #8705 from meshtastic/thinknode-m6
Add Thinknode M6
2025-11-21 05:48:16 -06:00
github-actions[bot]
0e3e8b7607 Update protobufs (#8707)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2025-11-21 11:02:13 +01:00
Thomas Göttgens
451e52b541 fix some minor compiler warnings. Note: The 'delete' is actually safe, so we suppress the warning. 2025-11-21 10:42:15 +01:00
Thomas Göttgens
d743ba8e75 Add Thinknode M6 2025-11-21 10:14:06 +01:00
Ben Meadors
626dce8323 Merge pull request #8701 from jasonbcox/fix-exclude-pki-menuhandler
Fix MenuHandler when MESHTASTIC_EXCLUDE_PKI is defined
2025-11-20 17:07:21 -06:00
Jason B. Cox
0100eeea67 Fix MenuHandler when MESHTASTIC_EXCLUDE_PKI is defined 2025-11-20 14:20:18 -08:00
Ben Meadors
5640179ce2 Merge pull request #8698 from jasonbcox/fix-exclude-pki
Fix build when MESHTASTIC_EXCLUDE_PKI is defined
2025-11-20 15:15:01 -06:00
Jason B. Cox
066da492d5 Fix build when MESHTASTIC_EXCLUDE_PKI is defined 2025-11-20 12:26:07 -08:00
Ben Meadors
2b8806486d Merge pull request #8670 from SebKuzminsky/nrf52-watchdog-take-2
nrf52 watchdog (attempt #2)
2025-11-20 10:14:30 -06:00
Ben Meadors
9ae545918c Merge pull request #8694 from meshtastic/develop
Missed one
2025-11-20 09:57:44 -06:00
Ben Meadors
5374291c3c Merge pull request #8663 from meshtastic/master
Master --> develop
2025-11-20 09:57:01 -06:00
Ben Meadors
38b0c1588a Merge pull request #8689 from meshtastic/develop-to-master
Develop to master
2025-11-20 08:50:03 -06:00
Quency-D
f329de04c4 Add a reset pulse signal to the OLED. (#8691)
* Add a reset pulse signal to the OLED.

* The modification time is the same as that of the Adafruit_SSD1306 library.
2025-11-20 08:47:41 -06:00
Jason P
b09fa31492 Update src/graphics/draw/MenuHandler.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-20 08:07:11 -06:00
Ben Meadors
a2a0150ee8 Trunk fmt 2025-11-20 06:14:29 -06:00
Ben Meadors
9ae92724a9 Merge branch 'master' into develop-to-master 2025-11-20 05:58:57 -06:00
Jonathan Bennett
9cf369c5d0 actually respect wake_on_motion setting (#8690) 2025-11-20 05:41:32 -06:00
Ben Meadors
441a7c5b20 Merge branch 'master' into develop-to-master 2025-11-19 16:07:58 -06:00
renovate[bot]
2ca03fbf4b chore(deps): update meshtastic-esp8266-oled-ssd1306 digest to 2887bf4 (#8688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 16:02:33 -06:00
Austin
ef298814f2 CI: Submit Bump Version PR against master (#8668) 2025-11-19 16:00:13 -06:00
Jason B. Cox
8d31fc5ec6 Unify uptime formatting (#8677)
* Unify uptime formatting

* Fix small label alignment item

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
2025-11-19 15:59:45 -06:00
github-actions[bot]
f9433a31d1 Automated version bumps (#8684)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-19 12:13:28 -06:00
Sebastian Kuzminsky
7232dddd69 nrf52 wdt: pause wdt in Sleep and Halt, set timeout to 90 s
The 90 seconds wdt timeout matches the esp32 wdt timeout.
2025-11-18 11:52:08 -07:00
Sebastian Kuzminsky
10de230dba nrf52: add watchdog (#8485)
* nrf52: add watchdog

Main thread only for now.

* bump framework-arduinoadafruitnrf52 to pick up new wdt support

* clang-format the new parts of main-nrf52.cpp

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
(cherry picked from commit 83954293d8)
2025-11-18 11:52:08 -07:00
viric
d18f3f7a65 Allow deepsleep in rak4630 and make it restart well when power comes back (#7882)
* Make RAK4631 nodes power back on deep sleep

The devices will hang if the VBAT goes under 1.7V (Brown-out reset) and
they will never come back unless power supply goes completely off.

This kills unattended nodes.

Using the SystemOff the LPCOMP we can get the nodes back again when
power comes back, even if VBAT goes under 1.7V, which moreover is more
unlikely because the device is off.

* Adding support for heltec t114

And moved particularities to variant.h

* Remove old cpp comment that belongs to variant.h

It was a leftover.

* Trunk fix

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-18 11:23:39 -06:00
github-actions[bot]
567b8ea1c2 Automated version bumps (#8626)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-18 08:25:17 -06:00
omgbebebe
d39d1917ad mqtt: do not try to send packets when it disconnected (#8658) 2025-11-18 06:33:15 -06:00
Jason P
b202559d37 Add code for preserving favorites, also move to Home screen before reseting (#8647) 2025-11-18 06:33:02 -06:00
Tom
85ea22ac38 Update to Pro-micro variants (#8600)
* Update to Pro-micro variants

Schematic updated
Xtal variant removed
Extra module added to list
Extra explanation added to readme.

* Fix markdown formatting in readme.md

* Fix formatting in readme.md for RF switch section

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
2025-11-18 06:31:41 -06:00
Quency-D
15257b017c Add the Heltec v4 expansion box. (#8539)
* Add the Heltec v4 expansion box.

* Change heltec-v4-oled to heltec-v4.

* Add touchscreen to I2C scanning.

* Add reset and busy pins to the ST7789.

* Ignore the touch interrupt pin and extend the sleep time to 1 hour.

* Remove the default sleep function.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-18 06:31:06 -06:00
Jonathan Bennett
59864dd09d Add API types, state, and log message in Debug screen. Added persistent "Connected" icon (#8576)
* Add API types, state, and log message in Debug screen

* un-goober the API state tracking

* Set the SerialConsole api_type

* Add api_type for Ethernet

* Remove API state debugging code

* Update wording for client connection states

* Improve string width for smaller screen devices

* Reserve space on navigation bar to fit link indicator

* Add persistent Connected icon to screen

* Connect System frame to ensure text doesn't overflow

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2025-11-18 06:26:34 -06:00
Jason B. Cox
edcdb2dcb2 Cleanup unnecessary global dereferencing in CryptoEngine (#8611)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-18 06:25:30 -06:00
Jonathan Bennett
6c09cf9d3d Gps reset detect (#8302)
* Properly format timestamp in log message

* Better formatting of GPS_DEBUG logging in gps probe

* Reset GPS after serial speed change, and look for magic string to identify chip

* Add UC6580 to boot message detection, for Heltec Tracker

* Add L76K detect from boot string, for Heltec-v4

* Slightly more useful GPS debugging

* Back out detection of L76K via startup messages.

* Ignore PIN_GPS_RESET = -1 and rename passive_detect array.

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-18 15:04:44 +08:00
Ben Meadors
ef4cb2abfb If we're not client proxying and we are not connected, don't publish 2025-11-17 20:05:42 -06:00
omgbebebe
c34f94abda mqtt: do not try to send packets when it disconnected (#8658) 2025-11-17 20:01:50 -06:00
Ben Meadors
a8d1a90e16 Fix ble rssi crash (#8661)
* Fix BLE crash occuring when trying to get RSSI from Android with a bad connection handle

* Cleanup
2025-11-17 19:58:12 -06:00
Austin
501c296e75 Linux: Fix silly EPEL9 mistake (#8660) 2025-11-17 17:39:52 -06:00
omgbebebe
79a91578b7 mqtt: do not try to send packets when it disconnected (#8658) 2025-11-17 16:54:02 -06:00
Austin
ec5e79585b Don't trust the AI! (#8659)
Read the docs instead
2025-11-17 16:40:19 -06:00
Austin
438e170b03 Packaging: Add libbsd where needed (#8533) 2025-11-17 06:41:37 -06:00
Jason P
17cd83085b Remove gating for Display Options (#8651) 2025-11-16 22:05:24 -06:00
renovate[bot]
43e0c35466 chore(deps): update dorny/test-reporter action to v2.2.0 (#8637)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 20:00:05 -06:00
Chloe Bethel
b7bdcbe43e Address review comments 2025-11-16 17:18:27 -06:00
Chloe Bethel
df063f40ff Try to look for a config file based on the HAT vendor/product for autoconfig 2025-11-16 17:18:27 -06:00
Jonathan Bennett
6e3be132f2 Reset the calibration data back to 0 when doing a compass calibration 2025-11-16 16:20:30 -06:00
Jason P
84bb1e33a6 Add code for preserving favorites, also move to Home screen before reseting (#8647) 2025-11-16 14:18:16 -06:00
Jason P
955347bf50 Remove fixed scaling in Digital Clock (#8620)
* Update digital clock draw to auto scale to correct size; no more fixed scaling

* Static scale calcuation to improve performance

* Update src/graphics/draw/ClockRenderer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Back off for width or height exceeds

* Fixes for some calcuations

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-16 08:42:51 -06:00
Dane Evans
4284fc2aec Feat/6704 neighbor info on demand (#8523)
* full thing. works

* works

* minimal changes

* roll back previous changes, move to using the alloc() overrride

* clean up comments

* format

* run clang-format manually.
Trunk may be the absolute worst formatter in existance

* format on WSL to fix trunks awfulness

* add a 3 minute cooldown to prevent messages going back and forth

* add ignoring the dummy neighbor.

* fix or.

* fix spelling, increase logging

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-15 19:49:46 -06:00
github-actions[bot]
034aaa376a Automated version bumps (#8626)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-14 06:09:25 -06:00
Jonathan Bennett
0aa11d810c Clean up GPS toggle logging
Removed redundant log warnings for GPS toggle events.
2025-11-13 12:04:39 -06:00
github-actions[bot]
4df6627ab1 Upgrade trunk (#8606)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-11-12 05:33:31 -06:00
Andrik45719
7212fb9caa Fix null pointer dereference in radio chip region check (#8613) 2025-11-11 10:00:08 -06:00
Jason B. Cox
4118e1c0f6 Cleanup unnecessary global dereferencing in CryptoEngine (#8611)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-10 20:19:15 -06:00
Thomas Göttgens
a62fed3289 Merge pull request #8610 from meshtastic/fix-ldro
Change RadioLib to commit zip til 7.4.1+ is released
2025-11-10 22:31:03 +01:00
Ben Meadors
e9590003f4 Only call stopNow if we're nagging (#8601) 2025-11-10 11:58:39 -06:00
Thomas Göttgens
7d2744fae0 Change RadioLib to commit zip til 7.4.1+ is released
fixes regression for SX127x chips per @GUVWAF
2025-11-10 16:16:24 +01:00
Chloe Bethel
beaebda4de stm32wl: Wrap and remove some functions that pull in large amounts of code/data to claw back even more flash space (#8609) 2025-11-10 09:08:04 -06:00
Tom
36c2178570 Update to Pro-micro variants (#8600)
* Update to Pro-micro variants

Schematic updated
Xtal variant removed
Extra module added to list
Extra explanation added to readme.

* Fix markdown formatting in readme.md

* Fix formatting in readme.md for RF switch section

---------

Co-authored-by: Tom <116762865+Nestpebble@users.noreply.github.com>
2025-11-09 09:24:03 -06:00
github-actions[bot]
1c0c6b2736 Automated version bumps (#8527)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-08 18:41:04 -06:00
Quency-D
602945f66b Add the Heltec v4 expansion box. (#8539)
* Add the Heltec v4 expansion box.

* Change heltec-v4-oled to heltec-v4.

* Add touchscreen to I2C scanning.

* Add reset and busy pins to the ST7789.

* Ignore the touch interrupt pin and extend the sleep time to 1 hour.

* Remove the default sleep function.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-08 06:50:20 -06:00
Quency-D
50f9be9a2b Add the Heltec v4 expansion box. (#8539)
* Add the Heltec v4 expansion box.

* Change heltec-v4-oled to heltec-v4.

* Add touchscreen to I2C scanning.

* Add reset and busy pins to the ST7789.

* Ignore the touch interrupt pin and extend the sleep time to 1 hour.

* Remove the default sleep function.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-08 06:47:24 -06:00
Benjamin Faershtein
b86827967e Drop PKI acks if there is no downlink on MQTTClientProxy (#8580)
* Discard everything if downlink isn't on

* Drop PKI packets when downlink not on
2025-11-08 06:00:38 -06:00
Benjamin Faershtein
8fe98db5dd Drop PKI acks if there is no downlink on MQTTClientProxy (#8580)
* Discard everything if downlink isn't on

* Drop PKI packets when downlink not on
2025-11-08 05:59:45 -06:00
Jonathan Bennett
531cad5e88 Add API types, state, and log message in Debug screen. Added persistent "Connected" icon (#8576)
* Add API types, state, and log message in Debug screen

* un-goober the API state tracking

* Set the SerialConsole api_type

* Add api_type for Ethernet

* Remove API state debugging code

* Update wording for client connection states

* Improve string width for smaller screen devices

* Reserve space on navigation bar to fit link indicator

* Add persistent Connected icon to screen

* Connect System frame to ensure text doesn't overflow

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2025-11-07 15:03:56 -06:00
github-actions[bot]
b707001873 Upgrade trunk (#8552)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-11-07 05:33:54 -06:00
renovate[bot]
85afd706fd chore(deps): update meshtastic/device-ui digest to 28167c6 (#8583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 05:33:36 -06:00
Ben Meadors
e76013fb60 Try-fix traceroute panic (#8568) 2025-11-07 05:16:00 -06:00
Benjamin Faershtein
b25797e1b3 Discard everything if downlink isn't on (#8578) 2025-11-06 21:02:46 -06:00
Ford Jones
bdb3fb1477 Persist favourites on NodeDB reset (#8292)
* Conditionally delete favourited nodes on reset

* trunk fmt

* Fix equality check, use existing macro for role validation

* Extend favourite persistence setting to devices of all roles

* Refactor: Decoupled role/config check and set role defaults appropriately

* Use American-English spelling

* Use existing reference

* Convert reset to bool, regen protos

* Add optional arg to nodedb_reset in favor of additional device setting

* Use correct proto commit ID

* Regen protos

* Log preservation status

* Pull latest from master
2025-11-06 21:02:36 -06:00
Jonathan Bennett
7eca061f01 Bugfix: Don't toggle BLE when choosing active state (#8579) 2025-11-06 21:01:30 -06:00
Benjamin Faershtein
77e0a24838 Discard everything if downlink isn't on (#8578) 2025-11-06 21:01:15 -06:00
Ford Jones
6cad393688 Persist favourites on NodeDB reset (#8292)
* Conditionally delete favourited nodes on reset

* trunk fmt

* Fix equality check, use existing macro for role validation

* Extend favourite persistence setting to devices of all roles

* Refactor: Decoupled role/config check and set role defaults appropriately

* Use American-English spelling

* Use existing reference

* Convert reset to bool, regen protos

* Add optional arg to nodedb_reset in favor of additional device setting

* Use correct proto commit ID

* Regen protos

* Log preservation status

* Pull latest from master
2025-11-06 16:06:37 -06:00
Ben Meadors
0725b46744 Merge branch 'master' into develop 2025-11-06 07:14:51 -06:00
Mike Robbins
4d86bbafe6 addFromContact: Don't auto-favorite when CLIENT_BASE; don't update last_heard unless CLIENT_BASE (#8495)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-06 07:14:33 -06:00
Wessel
112b294ef6 Store hop/mqtt/transport mechanism info in S&F (#8560)
Before this, all messages received when enabling S&F server would return
Hops away: -1
2025-11-06 07:14:14 -06:00
github-actions[bot]
5ba04ade2d Update protobufs (#8566)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-06 07:10:57 -06:00
Mike Robbins
6a6c409b9a addFromContact: Don't auto-favorite when CLIENT_BASE; don't update last_heard unless CLIENT_BASE (#8495)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-06 07:10:20 -06:00
Jonathan Bennett
69db3bd11c Reject legacy text message DMs (#8562)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-06 06:28:13 -06:00
Wessel
7b14b173d9 Store hop/mqtt/transport mechanism info in S&F (#8560)
Before this, all messages received when enabling S&F server would return
Hops away: -1
2025-11-06 06:27:25 -06:00
Manuel
45bf2468a9 fix missing key 0 (#8564) 2025-11-05 19:32:56 -06:00
Jason P
ce2e08e0d8 Don't Favorite Nodes if our Role is CLIENT_BASE (#8558)
* Don't Favorite Nodes if our Role is CLIENT_BASE

* Update CannedMessageModule.cpp
2025-11-05 13:19:55 -06:00
Jason P
3e40d7896b Revert "nrf52: add watchdog (#8485)" (#8554)
This reverts commit 83954293d8.
2025-11-05 10:07:28 -06:00
renovate[bot]
a579a9d011 chore(deps): update adafruit pct2075 to v1.0.6 (#8548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 15:35:14 -06:00
renovate[bot]
6b55ec6603 chore(deps): update python to v3.14.0 (#8542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 11:36:05 -06:00
Jonathan Bennett
f2400c9dc6 Update platform-native for WIFi lib fix (#8544)
Updates the WiFi library way down in Portduino, to detect TCP connection drops
2025-11-04 11:35:44 -06:00
github-actions[bot]
0a13bcaabf Upgrade trunk (#8437)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-11-04 06:07:12 -06:00
renovate[bot]
03f69b3b77 Update RadioLib to v7.4.0 (#8456)
* fix strlcpy compile error in Ubuntu 22.04 (#8520)

* fix strlcpy error in Ubuntu 20.04

* add to native after tests

* Add support for RAK_WISMESH_TAP_V2 and RAK3401 hardware models (#8537)

* Update RadioLib to v7.4.0

---------

Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: Daniel.Cao <144674500+DanielCao0@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 06:04:56 -06:00
Daniel.Cao
3ed831b8a3 Add support for RAK_WISMESH_TAP_V2 and RAK3401 hardware models (#8537) 2025-11-04 05:53:08 -06:00
Sebastian Kuzminsky
83954293d8 nrf52: add watchdog (#8485)
* nrf52: add watchdog

Main thread only for now.

* bump framework-arduinoadafruitnrf52 to pick up new wdt support

* clang-format the new parts of main-nrf52.cpp

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-04 05:35:44 -06:00
Austin
91621427f1 Packaging: Add libbsd where needed (#8533) 2025-11-03 17:56:31 -06:00
Manuel
cf716fe5ef fix strlcpy compile error in Ubuntu 22.04 (#8520)
* fix strlcpy error in Ubuntu 20.04

* add to native after tests
2025-11-03 17:11:16 -06:00
Ben Meadors
8d1b9c9dce Merge pull request #8531 from meshtastic/master
Master
2025-11-03 15:56:42 -06:00
Ben Meadors
538c05ad6c Revert "ADD - heltec v4 support to device install bat (#8528)" (#8532)
This reverts commit 468247fb94.
2025-11-03 15:56:23 -06:00
Quency-D
f6370bea8f Add the identification code for the DA217 triaxial accelerometer. (#8526) 2025-11-04 07:18:52 +11:00
Melon
468247fb94 ADD - heltec v4 support to device install bat (#8528) 2025-11-04 07:14:24 +11:00
github-actions[bot]
3ae7e54681 Automated version bumps (#8527)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-03 10:14:08 -06:00
Dmitry Ivanishkin
0a124b7f3d Fix SHT4x detection by reading unique serial nubmer (#8525) 2025-11-03 08:17:52 -06:00
Jonathan Bennett
7def82595d Rename screen options to display options and add units chooser (#8517)
* Rename screen options to display options and add units chooser

* Add InitialSelected to DisplayUnits_menu
2025-11-02 21:30:41 -06:00
Melon
3a67204f6d Update device-install.sh to support heltec-v4 (#8509)
* Update device-install.sh

* Update device-install.sh
2025-11-02 06:09:15 -06:00
Jonathan Bennett
d1b66782d1 Hide nodes that don't have position in the distance and bearings nodelists (#8518) 2025-11-02 05:57:15 -06:00
Xavier horwood
718fd118b0 Add IPv6 Support for esp32 (#6866)
* Update Default.h

* Update NodeDB.cpp

* Update WiFiAPClient.cpp

* Update userPrefs.jsonc

* set ipv6 to off by default

* Trunk fix

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-02 09:25:59 +11:00
Ben Meadors
75f7ded12c Merge pull request #8513 from meshtastic/master
Master backmerge
2025-11-01 17:17:14 -05:00
pa0lin082
7f78a624cd Add support for Bh1750 Light Sensor (#8376)
* regenerate protobuf with bh1750 TelemetrySensorType

* Added wollewald/BH1750_WE@^1.1.10 dependecy

* Added support for BH1750 during i2C detection

* Create new BH1750Sensor and added in EnvironmentTelemetry

* clean code

* Attempt to fix protobuf include

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-01 09:40:36 +11:00
shortwavesurfer2009
17324fa725 adjust battery curve (#8137)
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2025-11-01 09:39:30 +11:00
Marius Faber
001654e90a Add basic LR1121 support for T-Beam S3, full support needs #4775 fixed (#8349) 2025-11-01 09:38:37 +11:00
Andrew Yong
4f817d69eb fix(wio-e5): Fix LED state inversion (#8500)
Wio-E5 currently has LED appearing to be steadily on, due to incorrect LED_STATE_ON (it is actually briefly flashing off, but visually it is hard to perceive).

Wio-E5 has LED between GPIO PB5 and VCC, so LED_STATE_ON should be 0 for LED to blink correctly. With this commit, it is now flashing correctly.

Refer to schematics:

* [Wio-E5 Development Kit](https://files.seeedstudio.com/products/113990934/LoRa-E5%20Dev%20Board%20v1.0.pdf)
* [Wio-E5 mini](https://files.seeedstudio.com/products/113990939/LoRa-E5%20mini%20v1.0.pdf)

Signed-off-by: Andrew Yong <me@ndoo.sg>
2025-10-31 05:55:07 -05:00
Chloe Bethel
de83b448f9 Force stdout to be line buffered - this fixes logs ending early if meshtasticd crashes (#8499) 2025-10-31 05:54:35 -05:00
Ixitxachitl
c145be8e05 Refactor emote dimensions to 16x16 pixels (#8493)
Updated the dimensions of various emotes in emotes.h from 30x30 or 25x25 to 16x16 pixels for consistency and optimization. Added new emotes including heart_smile, Heart_eyes, and others, all with the same 16x16 size. This change improves memory usage and aligns with the design specifications for smaller emotes.
2025-10-31 21:20:29 +11:00
Clive Blackledge
28f53d132a refactor: change node count variables from uint8_t to uint16_t (#8478)
This is a non-breaking change that increases the internal representation
of node counts from uint8_t (max 255) to uint16_t (max 65535) to support
larger mesh networks, particularly on ESP32-S3 devices with PSRAM.

Changes:
- NodeStatus: numOnline, numTotal, lastNumTotal (uint8_t -> uint16_t)
- ProtobufModule: numOnlineNodes (uint8_t -> uint16_t)
- MapApplet: loop counters changed to size_t for consistency with getNumMeshNodes()
- NodeStatus: Fixed log format to use %u for unsigned integers

Note: Default class methods keep uint32_t for numOnlineNodes parameter
to match the public API and allow flexibility, even though internal node
counts use uint16_t (max 65535 nodes).

This change does NOT affect protobuf definitions, maintaining wire
compatibility with existing clients and devices.
2025-10-28 15:32:08 -05:00
Erayd
f045ca2303 Fix type to ensure correct alignment; saves 4B per entry (#8465) 2025-10-27 06:05:59 -05:00
Tom Fifield
b6830a68a0 Migrate test workflow to use Node 24 (#8466)
Node 24 is now the common version amoungst all of our actions.
2025-10-27 19:47:34 +11:00
Ben Meadors
13c4c2037d Merge pull request #8444 from meshtastic/master
Master --> develop
2025-10-24 21:06:29 -05:00
166 changed files with 36382 additions and 10911 deletions

View File

@@ -1,7 +1,7 @@
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
USER root

View File

@@ -8,7 +8,7 @@
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
"version": "3.13"
"version": "3.14"
}
},
"customizations": {

View File

@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd

View File

@@ -22,7 +22,7 @@ jobs:
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}

View File

@@ -1,176 +0,0 @@
name: Build One Arch
on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
arch:
type: choice
options:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- native
permissions: read-all
env:
INPUT_ARCH: ${{ github.event.inputs.arch }}
jobs:
setup:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra)
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF"
echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT
outputs:
selected_arch: ${{ steps.jsonStep.outputs.selected_arch }}
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
id: version
env:
BUILD_LOCATION: local
outputs:
long: ${{ steps.version.outputs.long }}
deb: ${{ steps.version.outputs.deb }}
build:
if: ${{ github.event_name != 'workflow_dispatch' }}
needs: [setup, version]
strategy:
fail-fast: false
matrix:
build: ${{ fromJson(needs.setup.outputs.selected_arch) }}
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ matrix.build.board }}
platform: ${{ matrix.build.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' }}
uses: ./.github/workflows/test_native.yml
gather-artifacts:
permissions:
contents: write
pull-requests: write
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
runs-on: ubuntu-latest
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v6
with:
path: ./
pattern: firmware-${{inputs.arch}}-*
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: Move files up
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v5
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
overwrite: true
path: |
./firmware-*.bin
./firmware-*.uf2
./firmware-*.hex
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 30
- uses: actions/download-artifact@v6
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
merge-multiple: true
path: ./output
# For diagnostics
- name: Show artifacts
run: ls -lR
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v5
with:
name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- uses: scruplelesswizard/comment-artifact@main
if: ${{ github.event_name == 'pull_request' }}
with:
name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}
description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -15,7 +15,6 @@ on:
- rp2040
- rp2350
- stm32
- native
target:
type: string
required: false
@@ -42,10 +41,9 @@ jobs:
- rp2040
- rp2350
- stm32
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -60,13 +58,13 @@ jobs:
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
version:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -87,25 +85,6 @@ jobs:
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
build-debian-src:
if: ${{ github.repository == 'meshtastic/firmware' && inputs.arch == 'native' }}
uses: ./.github/workflows/build_debian_src.yml
with:
series: UNRELEASED
build_location: local
secrets: inherit
package-pio-deps-native-tft:
if: ${{ inputs.arch == 'native' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
secrets: inherit
test-native:
if: ${{ !contains(github.ref_name, 'event/') && github.event_name != 'workflow_dispatch' || !contains(github.ref_name, 'event/') && inputs.arch == 'native' && inputs.target != '' }}
uses: ./.github/workflows/test_native.yml
gather-artifacts:
permissions:
contents: write
@@ -114,7 +93,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

View File

@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}

View File

@@ -83,7 +83,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{ github.ref }}

View File

@@ -35,7 +35,7 @@ jobs:
- check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -59,7 +59,7 @@ jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -81,7 +81,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build base
id: base
uses: ./.github/actions/setup-base
@@ -163,7 +163,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -242,7 +242,7 @@ jobs:
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -311,7 +311,7 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -366,7 +366,7 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6

View File

@@ -17,7 +17,7 @@ jobs:
- check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -40,7 +40,7 @@ jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -62,7 +62,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build base
id: base
uses: ./.github/actions/setup-base
@@ -142,7 +142,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -221,7 +221,7 @@ jobs:
- package-pio-deps-native-tft
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -290,7 +290,7 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -345,7 +345,7 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6

View File

@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1
@@ -31,7 +31,7 @@ jobs:
pull-requests: write # For trunk to create PRs
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1

View File

@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}

View File

@@ -32,7 +32,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd

View File

@@ -40,7 +40,7 @@ jobs:
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive

View File

@@ -60,7 +60,10 @@ jobs:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# Always use master branch for version bumps
ref: master
- name: Setup Python
uses: actions/setup-python@v6

View File

@@ -21,7 +21,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v5
uses: actions/checkout@v6
# step 2
- name: full scan

View File

@@ -13,7 +13,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0

View File

@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -70,7 +70,7 @@ jobs:
name: Native PlatformIO Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -127,7 +127,7 @@ jobs:
- platformio-tests
if: always()
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.1.1
uses: dorny/test-reporter@v2.2.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: test-runner
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
# - uses: actions/setup-python@v5
# with:

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1

View File

@@ -15,7 +15,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}

View File

@@ -11,7 +11,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
submodules: true

View File

@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.486
- renovate@41.157.0
- checkov@3.2.492
- renovate@42.5.4
- prettier@3.6.2
- trufflehog@3.90.11
- trufflehog@3.90.13
- yamllint@1.37.1
- bandit@1.8.6
- trivy@0.67.2
- taplo@0.10.0
- ruff@0.14.1
- ruff@0.14.4
- isort@7.0.0
- markdownlint@0.45.0
- oxipng@9.1.5
@@ -26,9 +26,9 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@25.9.0
- black@25.11.0
- git-diff-check
- gitleaks@8.28.0
- gitleaks@8.29.0
- clang-format@16.0.3
ignore:
- linters: [ALL]

View File

@@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀
## Stats
![Alt](https://repobeats.axiom.co/api/embed/8025e56c482ec63541593cc5bd322c19d5c0bdcf.svg "Repobeats analytics image")

View File

@@ -8,7 +8,8 @@ ARG PIO_ENV=native
ENV PIP_ROOT_USER_ACTION=ignore
RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev \
&& rm -rf /var/cache/apk/* \
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
USER root
RUN apk --no-cache add \
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
libx11 libinput libxkbcommon \
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
i2c-tools libuv libx11 libinput libxkbcommon \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \

View File

@@ -7,7 +7,7 @@ extends = arduino_base
platform_packages =
; our custom Git version until they merge our PR
# TODO renovate
platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39
; Don't renovate toolchain-gccarmnoneeabi
platformio/toolchain-gccarmnoneeabi@~1.90301.0

View File

@@ -8,7 +8,7 @@ lib_deps =
${environmental_base.lib_deps}
${environmental_extra.lib_deps}
# renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master
https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip
https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.

View File

@@ -2,7 +2,7 @@
[portduino_base]
platform =
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip
https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip
framework = arduino
build_src_filter =

View File

@@ -37,6 +37,9 @@ build_flags =
-DRADIOLIB_EXCLUDE_LR11X0=1
-DHAL_DAC_MODULE_ONLY
-DHAL_RNG_MODULE_ENABLED
-Wl,--wrap=__assert_func
-Wl,--wrap=strerror
-Wl,--wrap=_tzset_unlocked_r
build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp>

View File

@@ -87,6 +87,15 @@
</screenshots>
<releases>
<release version="2.7.16" date="2025-11-19">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16</url>
</release>
<release version="2.7.15" date="2025-11-13">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
</release>
<release version="2.7.14" date="2025-11-03">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
</release>
<release version="2.7.13" date="2025-10-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
</release>

53
boards/ThinkNode-M3.json Normal file
View File

@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "elecrow_eink",
"mcu": "nrf52840",
"variant": "ELECROW-ThinkNode-M3",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "elecrow nrf",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "",
"vendor": "ELECROW"
}

53
boards/ThinkNode-M6.json Normal file
View File

@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "elecrow_thinknode_m6",
"mcu": "nrf52840",
"variant": "ELECROW-ThinkNode-M6",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "ELECROW ThinkNode M6",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html",
"vendor": "ELECROW"
}

56
boards/muzi-base.json Normal file
View File

@@ -0,0 +1,56 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0xcafe"]],
"mcu": "nrf52840",
"variant": "muzi-base",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Muzi Base",
"url": "https://muzi.works/",
"vendor": "MuziWorks",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"blackmagic",
"cmsis-dap",
"mbed",
"stlink"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
}
}

19
debian/changelog vendored
View File

@@ -1,3 +1,22 @@
meshtasticd (2.7.16.0) unstable; urgency=medium
* Version 2.7.16
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 19 Nov 2025 16:12:32 +0000
meshtasticd (2.7.15.0) unstable; urgency=medium
* Version 2.7.15
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 13 Nov 2025 12:31:57 +0000
meshtasticd (2.7.14.0) unstable; urgency=medium
* Version 2.7.14
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 03 Nov 2025 16:11:31 +0000
meshtasticd (2.7.13.0) unstable; urgency=medium
* Version 2.7.13

1
debian/control vendored
View File

@@ -3,6 +3,7 @@ Section: misc
Priority: optional
Maintainer: Austin Lane <vidplace7@gmail.com>
Build-Depends: debhelper-compat (= 13),
libc6-dev (>= 2.38) | libbsd-dev,
lsb-release,
tar,
gzip,

View File

@@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
BuildRequires: python3dist(grpcio-tools)
BuildRequires: git-core
BuildRequires: gcc-c++
BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
BuildRequires: pkgconfig(yaml-cpp)
BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez)
@@ -49,6 +50,13 @@ BuildRequires: pkgconfig(x11)
BuildRequires: pkgconfig(libinput)
BuildRequires: pkgconfig(xkbcommon-x11)
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
BuildRequires: glibc-devel >= 2.38
%else
BuildRequires: pkgconfig(libbsd-overlay)
%endif
Requires: systemd-udev
%description

View File

@@ -62,7 +62,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -115,12 +115,13 @@ lib_deps =
[radiolib_base]
lib_deps =
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
jgromes/RadioLib@7.3.0
# jgromes/RadioLib@7.4.0
https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
https://github.com/meshtastic/device-ui/archive/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -176,11 +177,13 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
adafruit/Adafruit LTR390 Library@1.1.2
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
adafruit/Adafruit PCT2075@1.0.5
adafruit/Adafruit PCT2075@1.0.6
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
dfrobot/DFRobot_BMM150@1.0.0
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
adafruit/Adafruit TSL2561@1.1.2
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
wollewald/BH1750_WE@^1.1.10
; (not included in native / portduino)
[environmental_extra]

View File

@@ -13,6 +13,11 @@ extern MemGet memGet;
#define LED_STATE_ON 1
#endif
// WIFI LED
#ifndef WIFI_STATE_ON
#define WIFI_STATE_ON 1
#endif
// -----------------------------------------------------------------------------
// DEBUG
// -----------------------------------------------------------------------------
@@ -147,7 +152,9 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN
#define defaultBLEPin 123456
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && defined(USE_CH390D)
#include <ESP32_CH390.h>
#elif HAS_ETHERNET && !defined(USE_WS5500)
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET

View File

@@ -14,16 +14,16 @@ class NodeStatus : public Status
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
uint8_t numOnline = 0;
uint8_t numTotal = 0;
uint16_t numOnline = 0;
uint16_t numTotal = 0;
uint8_t lastNumTotal = 0;
uint16_t lastNumTotal = 0;
public:
bool forceUpdate = false;
NodeStatus() { statusType = STATUS_TYPE_NODE; }
NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
{
this->forceUpdate = forceUpdate;
this->numOnline = numOnline;
@@ -34,11 +34,11 @@ class NodeStatus : public Status
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
uint8_t getNumOnline() const { return numOnline; }
uint16_t getNumOnline() const { return numOnline; }
uint8_t getNumTotal() const { return numTotal; }
uint16_t getNumTotal() const { return numTotal; }
uint8_t getLastNumTotal() const { return lastNumTotal; }
uint16_t getLastNumTotal() const { return lastNumTotal; }
bool matches(const NodeStatus *newStatus) const
{
@@ -56,7 +56,7 @@ class NodeStatus : public Status
numTotal = newStatus->getNumTotal();
}
if (isDirty || newStatus->forceUpdate) {
LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
onNewStatus.notifyObservers(this);
}
return 0;

View File

@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
#ifdef BATTERY_PIN
static void adcEnable()
void battery_adcEnable()
{
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
@@ -214,7 +214,7 @@ static void adcEnable()
#endif
}
static void adcDisable()
static void battery_adcDisable()
{
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP
@@ -278,6 +278,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
break;
}
}
#if defined(BATTERY_CHARGING_INV)
// bit of trickery to show 99% up until the charge finishes
if (!digitalRead(BATTERY_CHARGING_INV) && battery_SOC > 99)
battery_SOC = 99;
#endif
return clamp((int)(battery_SOC), 0, 100);
}
@@ -320,7 +325,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0;
float scaled = 0;
adcEnable();
battery_adcEnable();
#ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
@@ -332,7 +337,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif
adcDisable();
battery_adcDisable();
if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
@@ -455,6 +460,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
// if it's not HIGH - check the battery
#endif
#elif defined(MUZI_BASE)
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
#endif
return getBattVoltage() > chargingVolt;
}
@@ -470,6 +477,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
#else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) {
@@ -702,7 +711,16 @@ bool Power::setup()
},
CHANGE);
#endif
#ifdef BATTERY_CHARGING_INV
attachInterrupt(
BATTERY_CHARGING_INV,
[]() {
power->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
},
CHANGE);
#endif
enabled = found;
low_voltage_counter = 0;
@@ -759,6 +777,8 @@ void Power::shutdown()
if (screen) {
#ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
#elif USE_EINK
screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
#else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif
@@ -906,13 +926,8 @@ void Power::readPowerStatus()
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board
LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
#else
LOG_INFO("Low voltage detected, trigger deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
}
} else {
low_voltage_counter = 0;
@@ -1552,4 +1567,4 @@ bool Power::meshSolarInit()
{
return false;
}
#endif
#endif

View File

@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
{
api_type = TYPE_SERIAL;
assert(!console);
console = this;
canWrite = false; // We don't send packets to our port until it has talked to us first

View File

@@ -228,6 +228,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define ICM20948_ADDR_ALT 0x68
#define BHI260AP_ADDR 0x28
#define BMM150_ADDR 0x13
#define DA217_ADDR 0x26
// -----------------------------------------------------------------------------
// LED
@@ -250,6 +251,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
#define FT6336U_ADDR 0x48
#define CST328_ADDR 0x1A
#define CHSC6X_ADDR 0x2E
// -----------------------------------------------------------------------------
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
@@ -394,6 +396,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_RGB_LED
#endif
#ifndef LED_STATE_OFF
#define LED_STATE_OFF 0
#endif
#ifndef LED_STATE_ON
#define LED_STATE_ON 1
#endif
// default mapping of pins
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
#define ALT_BUTTON_PIN PIN_BUTTON2

View File

@@ -82,7 +82,10 @@ class ScanI2C
BHI260AP,
BMM150,
TSL2561,
DRV2605
DRV2605,
BH1750,
DA217,
CHSC6X
} DeviceType;
// typedef uint8_t DeviceAddress;

View File

@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
if (i2cBus->available())
i2cBus->read();
}
LOG_DEBUG("Register value: 0x%x", value);
return value;
}
@@ -377,14 +378,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
registerValue == 0xc8d) {
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
if (registerValue == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
type = SHT31;
logFoundDevice("SHT31", (uint8_t)addr.address);
@@ -465,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
case TCA9555_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
if (registerValue == 0x13) {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
if (registerValue == 0x81) {
type = DA217;
logFoundDevice("DA217", (uint8_t)addr.address);
} else {
type = TCA9555;
logFoundDevice("TCA9555", (uint8_t)addr.address);
}
} else {
type = TCA9555;
logFoundDevice("TCA9555", (uint8_t)addr.address);
}
break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
if (registerValue == 0x50) {
@@ -485,7 +500,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
case LTR553ALS_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
if (registerValue == 0x92) { // LTR553ALS Part ID
type = LTR553ALS;
logFoundDevice("LTR553ALS", (uint8_t)addr.address);
} else {
// Test BH1750 - send power on command
i2cBus->beginTransmission(addr.address);
i2cBus->write(0x01); // Power On command
uint8_t bh1750_error = i2cBus->endTransmission();
if (bh1750_error == 0) {
type = BH1750;
logFoundDevice("BH1750", (uint8_t)addr.address);
} else {
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
}
}
break;
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
@@ -513,6 +547,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case ICM20948_ADDR: // same as BMX160_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);
break;
#endif
if (registerValue == 0xEA) {
type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address);

View File

@@ -38,14 +38,16 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
return N;
}
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
#if defined(GPS_SERIAL_PORT)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = &Serial1;
#ifndef GPS_SERIAL_PORT
#define GPS_SERIAL_PORT Serial1
#endif
#if defined(ARCH_NRF52)
Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_RP2040)
SerialUART *GPS::_serial_gps = &Serial1;
SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT;
#else
HardwareSerial *GPS::_serial_gps = nullptr;
#endif
@@ -240,6 +242,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
buffer[bytesRead] = b;
bytesRead++;
if ((bytesRead == 767) || (b == '\r')) {
#ifdef GPS_DEBUG
LOG_DEBUG(debugmsg.c_str());
#endif
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
#ifdef GPS_DEBUG
LOG_DEBUG("Found: %s", message); // Log the found message
@@ -247,9 +252,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
return GNSS_RESPONSE_OK;
} else {
bytesRead = 0;
#ifdef GPS_DEBUG
LOG_DEBUG(debugmsg.c_str());
#endif
}
}
}
@@ -1275,6 +1277,24 @@ GnssModel_t GPS::probe(int serialSpeed)
memset(&ublox_info, 0, sizeof(ublox_info));
delay(100);
#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
// attempt to detect the chip based on boot messages
std::vector<ChipInfo> passive_detect = {
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
if (detectedDriver != GNSS_MODEL_UNKNOWN) {
return detectedDriver;
}
#endif
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
delay(20);
@@ -1473,12 +1493,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
}
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver;
@@ -1486,6 +1506,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
}
}
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
@@ -1504,10 +1527,7 @@ GPS *GPS::createGps()
int8_t _rx_gpio = config.position.rx_gpio;
int8_t _tx_gpio = config.position.tx_gpio;
int8_t _en_gpio = config.position.gps_en_gpio;
#if HAS_GPS && !defined(ARCH_ESP32)
_rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
_tx_gpio = 1;
#endif
#if defined(GPS_RX_PIN)
if (!_rx_gpio)
_rx_gpio = GPS_RX_PIN;
@@ -1572,8 +1592,6 @@ GPS *GPS::createGps()
#ifdef PIN_GPS_RESET
pinMode(PIN_GPS_RESET, OUTPUT);
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
#endif
@@ -1583,16 +1601,28 @@ GPS *GPS::createGps()
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
#endif
// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
#elif defined(ARCH_RP2040)
_serial_gps->setPinout(new_gps->tx_gpio, new_gps->rx_gpio);
_serial_gps->setFIFOSize(256);
_serial_gps->begin(GPS_BAUDRATE);
#else
#elif defined(ARCH_NRF52)
_serial_gps->setPins(new_gps->rx_gpio, new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_STM32WL)
_serial_gps->setTx(new_gps->tx_gpio);
_serial_gps->setRx(new_gps->rx_gpio);
_serial_gps->begin(GPS_BAUDRATE);
#elif defined(ARCH_PORTDUINO)
// Portduino can't set the GPS pins directly.
_serial_gps->begin(GPS_BAUDRATE);
#else
#error Unsupported architecture!
#endif
}
return new_gps;

View File

@@ -194,6 +194,8 @@ class GPS : private concurrency::OSThread
/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#elif defined(ARCH_NRF52)
static Uart *_serial_gps;
#else
static HardwareSerial *_serial_gps;
#endif

View File

@@ -112,7 +112,11 @@ RTCSetResult readFromRTC()
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis();
#ifdef MUZI_BASE
ArtronShop_RX8130CE rtc(&Wire1);
#else
ArtronShop_RX8130CE rtc(&Wire);
#endif
tm t;
if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t);
@@ -245,7 +249,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
}
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
#ifdef MUZI_BASE
ArtronShop_RX8130CE rtc(&Wire1);
#else
ArtronShop_RX8130CE rtc(&Wire);
#endif
tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
@@ -310,7 +318,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
@@ -319,7 +327,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}

View File

@@ -435,6 +435,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
#if defined(MUZI_BASE)
dispdev->init();
dispdev->setBrightness(brightness);
dispdev->flipScreenVertically();
dispdev->resetDisplay();
digitalWrite(SCREEN_12V_ENABLE, HIGH);
delay(100);
#endif
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
@@ -484,6 +492,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif
dispdev->displayOff();
#ifdef SCREEN_12V_ENABLE
digitalWrite(SCREEN_12V_ENABLE, LOW);
#endif
#ifdef USE_ST7789
SPI1.end();
#if defined(ARCH_ESP32)
@@ -534,7 +546,7 @@ void Screen::setup()
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
#endif
#ifdef USE_SH1107_128_64
#if defined(USE_SH1107_128_64) || defined(USE_SH1107)
static_cast<SH1106Wire *>(dispdev)->setSubtype(7);
#endif
@@ -542,6 +554,9 @@ void Screen::setup()
// Apply custom RGB color (e.g. Heltec T114/T190)
static_cast<ST7789Spi *>(dispdev)->setRGB(TFT_MESH);
#endif
#if defined(MUZI_BASE)
dispdev->delayPoweron = true;
#endif
// === Initialize display and UI system ===
ui->init();

View File

@@ -249,6 +249,8 @@ class Screen : public concurrency::OSThread
bool isOverlayBannerShowing();
bool isScreenOn() { return screenOn; }
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
char ourId[5];

View File

@@ -1,6 +1,8 @@
#include "configuration.h"
#if HAS_SCREEN
#include "MeshService.h"
#include "RTC.h"
#include "draw/NodeListRenderer.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/UIRenderer.h"
@@ -398,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
return textPositions;
}
// *************************
// * Common Footer Drawing *
// *************************
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
{
bool drawConnectionState = false;
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
drawConnectionState = true;
}
if (drawConnectionState) {
if (isHighResolution) {
const int scale = 2;
const int bytesPerRow = (connection_icon_width + 7) / 8;
int iconX = 0;
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
for (int yy = 0; yy < connection_icon_height; ++yy) {
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
for (int xx = 0; xx < connection_icon_width; ++xx) {
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
if (byteVal & bitMask) {
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
}
}
}
} else {
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
connection_icon);
}
}
}
bool isAllowedPunctuation(char c)
{
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";

View File

@@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
bool show_date = false);
// Shared battery/time/mail header
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
const int *getTextPositions(OLEDDisplay *display);
bool isAllowedPunctuation(char c);

View File

@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
#elif defined(ST7789_CS)
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
#ifdef HELTEC_V4_TFT
#include "chsc6x.h"
#include "lgfx/v1/Touch.hpp"
namespace lgfx
{
inline namespace v1
{
class TOUCH_CHSC6X : public ITouch
{
public:
TOUCH_CHSC6X(void)
{
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
_cfg.x_min = 0;
_cfg.x_max = 240;
_cfg.y_min = 0;
_cfg.y_max = 320;
};
bool init(void) override
{
if (chsc6xTouch == nullptr) {
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
}
chsc6xTouch->chsc6x_init();
return true;
};
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
{
uint16_t raw_x, raw_y;
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
tp[0].x = 320 - 1 - raw_y;
tp[0].y = 240 - 1 - raw_x;
tp[0].size = 1;
tp[0].id = 1;
return 1;
}
tp[0].size = 0;
return 0;
};
void wakeup(void) override{};
void sleep(void) override{};
private:
chsc6x *chsc6xTouch = nullptr;
};
} // namespace v1
} // namespace lgfx
#endif
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7789 _panel_instance;
@@ -431,6 +481,8 @@ class LGFX : public lgfx::LGFX_Device
#if HAS_TOUCHSCREEN
#if defined(T_WATCH_S3) || defined(ELECROW)
lgfx::Touch_FT5x06 _touch_instance;
#elif defined(HELTEC_V4_TFT)
lgfx::TOUCH_CHSC6X _touch_instance;
#else
lgfx::Touch_GT911 _touch_instance;
#endif
@@ -464,9 +516,9 @@ class LGFX : public lgfx::LGFX_Device
{ // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
// The following setting values are general initial values for each panel, so please comment out any
// unknown items and try them.

View File

@@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
else
snprintf(timeStr, maxLength, "unknown age");
}
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs)
{
uint32_t days = uptimeMillis / 86400000;
uint32_t hours = (uptimeMillis % 86400000) / 3600000;
uint32_t mins = (uptimeMillis % 3600000) / 60000;
uint32_t secs = (uptimeMillis % 60000) / 1000;
if (days) {
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
} else if (hours) {
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
} else if (!includeSecs) {
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
} else if (mins) {
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
} else {
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
}
}

View File

@@ -24,3 +24,10 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int
* @param maxLength Maximum length of the resulting string buffer
*/
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
/**
* Get a compact human-readable string that only shows the largest non-zero time components.
* For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes
* will display as "1d 2h".
*/
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false);

View File

@@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
char timeString[16];
int hour = 0;
int minute = 0;
int second = 0;
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
@@ -215,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
}
bool isPM = hour >= 12;
// hour = hour > 12 ? hour - 12 : hour;
if (config.display.use_12h_clock) {
hour %= 12;
if (hour == 0)
if (hour == 0) {
hour = 12;
}
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second);
#ifdef T_WATCH_S3
float scale = 1.5;
#elif defined(CHATTER_2)
float scale = 1.1;
#else
float scale = 0.75;
if (isHighResolution) {
scale = 1.5;
}
#endif
static bool scaleInitialized = false;
static float scale = 0.75f;
static float segmentWidth = SEGMENT_WIDTH * 0.75f;
static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
if (!scaleInitialized) {
float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable)
float max_scale = 3.5f; // Safety limit to avoid runaway scaling
float step = 0.05f; // Step increment per iteration
float target_width = display->getWidth() * screenwidth_target_ratio;
float target_height =
display->getHeight() -
(isHighResolution
? 46
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
float calculated_width_size = 0.0f;
float calculated_height_size = 0.0f;
while (true) {
segmentWidth = SEGMENT_WIDTH * scale;
segmentHeight = SEGMENT_HEIGHT * scale;
calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4);
calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2);
if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) {
break;
}
scale += step;
}
// If we overshot width, back off one step and recompute segment sizes
if (calculated_width_size > target_width || calculated_height_size > target_height) {
scale -= step;
segmentWidth = SEGMENT_WIDTH * scale;
segmentHeight = SEGMENT_HEIGHT * scale;
}
scaleInitialized = true;
}
size_t len = strlen(timeString);
// calculate hours:minutes string width
uint16_t timeStringWidth = strlen(timeString) * 5;
uint16_t timeStringWidth = len * 5; // base spacing between characters
for (uint8_t i = 0; i < strlen(timeString); i++) {
for (size_t i = 0; i < len; i++) {
char character = timeString[i];
if (character == ':') {
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
}
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
uint16_t startingHourMinuteTextX = hourMinuteTextX;
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2;
// iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < strlen(timeString); i++) {
for (size_t i = 0; i < len; i++) {
char character = timeString[i];
if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
if (scale >= 2.0f) {
hourMinuteTextX += (uint16_t)(4.5f * scale);
}
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
@@ -279,34 +308,27 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hourMinuteTextX += 5;
}
// draw seconds string
// draw seconds string + AM/PM
display->setFont(FONT_SMALL);
int xOffset = (isHighResolution) ? 0 : -1;
if (hour >= 10) {
xOffset += (isHighResolution) ? 32 : 18;
}
int yOffset = (isHighResolution) ? 3 : 1;
#ifdef SENSECAP_INDICATOR
yOffset -= 3;
#endif
#ifdef T_DECK
yOffset -= 5;
#endif
if (config.display.use_12h_clock) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
isPM ? "pm" : "am");
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
}
#ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10;
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
if (scale >= 2.0f) {
xOffset -= (int)(4.5f * scale);
}
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1,
secondString);
#endif
}
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
graphics::drawCommonFooter(display, x, y);
}
// Draw an analog clock
@@ -319,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2;
@@ -516,6 +533,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->drawLine(centerX, centerY, secondX, secondY);
#endif
}
graphics::drawCommonFooter(display, x, y);
}
} // namespace ClockRenderer

View File

@@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
// UI elements for clock displays
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
} // namespace ClockRenderer

View File

@@ -3,6 +3,7 @@
#include "../Screen.h"
#include "DebugRenderer.h"
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Throttle.h"
#include "UIRenderer.h"
@@ -10,6 +11,7 @@
#include "gps/RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
#include "mesh/Channels.h"
@@ -223,6 +225,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
graphics::drawCommonFooter(display, x, y);
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
@@ -503,6 +507,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage);
#endif
graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -642,27 +647,48 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
#if !defined(M5STACK_UNITC6L)
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1;
display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
}
#endif
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
char api_state[32] = "";
const char *clientWord = nullptr;
// Determine if narrow or wide screen
if (isHighResolution) {
clientWord = "Client";
} else {
clientWord = "App";
}
snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord);
if (service->api_state == service->STATE_BLE) {
snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord);
} else if (service->api_state == service->STATE_WIFI) {
snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord);
} else if (service->api_state == service->STATE_SERIAL) {
snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord);
} else if (service->api_state == service->STATE_PACKET) {
snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord);
} else if (service->api_state == service->STATE_HTTP) {
snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord);
} else if (service->api_state == service->STATE_ETH) {
snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord);
}
if (api_state[0] != '\0') {
display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
api_state);
}
}
graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -694,4 +720,4 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
} // namespace DebugRenderer
} // namespace graphics
#endif
#endif

View File

@@ -119,6 +119,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
@@ -139,6 +140,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
@@ -574,21 +576,16 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options";
optionsArray[options] = "Display Options";
optionsEnumArray[options++] = ScreenOptions;
#endif
optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
@@ -626,9 +623,6 @@ void menuHandler::systemBaseMenu()
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow();
@@ -790,17 +784,24 @@ void menuHandler::nodeNameLengthMenu()
void menuHandler::resetNodeDBMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Confirm Reset NodeDB";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == 1 || selected == 2) {
disableBluetooth();
screen->setFrames(Screen::FOCUS_DEFAULT);
}
if (selected == 1) {
LOG_INFO("Initiate node-db reset");
nodeDB->resetNodes();
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == 2) {
LOG_INFO("Initiate node-db reset but keeping favorites");
nodeDB->resetNodes(1);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
screen->showOverlayBanner(bannerOptions);
@@ -936,7 +937,9 @@ void menuHandler::BluetoothToggleMenu()
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1 || selected == 2) {
if (selected == 0)
return;
else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
@@ -1330,7 +1333,7 @@ void menuHandler::screenOptionsMenu()
hasSupportBrightness = false;
#endif
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor, FrameToggles, DisplayUnits };
static const char *optionsArray[5] = {"Back"};
static int optionsEnumArray[5] = {Back};
int options = 1;
@@ -1352,8 +1355,14 @@ void menuHandler::screenOptionsMenu()
optionsEnumArray[options++] = ScreenColor;
#endif
optionsArray[options] = "Frame Visibility Toggle";
optionsEnumArray[options++] = FrameToggles;
optionsArray[options] = "Display Units";
optionsEnumArray[options++] = DisplayUnits;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Screen Options";
bannerOptions.message = "Display Options";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -1367,6 +1376,12 @@ void menuHandler::screenOptionsMenu()
} else if (selected == NodeNameLength) {
menuHandler::menuQueue = menuHandler::node_name_length_menu;
screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == DisplayUnits) {
menuHandler::menuQueue = menuHandler::DisplayUnits;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
@@ -1578,6 +1593,34 @@ void menuHandler::FrameToggles_menu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::DisplayUnits_menu()
{
enum optionsNumbers { Back, MetricUnits, ImperialUnits };
static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = " Select display units";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
bannerOptions.InitialSelected = 2;
else
bannerOptions.InitialSelected = 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == MetricUnits) {
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == ImperialUnits) {
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != menu_none)
@@ -1692,6 +1735,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case FrameToggles:
FrameToggles_menu();
break;
case DisplayUnits:
DisplayUnits_menu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;
@@ -1706,4 +1752,4 @@ void menuHandler::saveUIConfig()
} // namespace graphics
#endif
#endif

View File

@@ -44,7 +44,8 @@ class menuHandler
trace_route_menu,
throttle_message,
node_name_length_menu,
FrameToggles
FrameToggles,
DisplayUnits
};
static screenMenus menuQueue;
@@ -88,6 +89,7 @@ class menuHandler
static void powerMenu();
static void nodeNameLengthMenu();
static void FrameToggles_menu();
static void DisplayUnits_menu();
static void textMessageMenu();
private:

View File

@@ -213,6 +213,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#else
display->drawString(center_text, getTextPositions(display)[2], messageString);
#endif
graphics::drawCommonFooter(display, x, y);
return;
}
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr);
#endif
graphics::drawCommonFooter(display, x, y);
}
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)

View File

@@ -425,6 +425,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
bool locationScreen = false;
if (strcmp(title, "Bearings") == 0)
locationScreen = true;
else if (strcmp(title, "Distance") == 0)
locationScreen = true;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
@@ -440,7 +446,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalEntries = nodeDB->getNumMeshNodes();
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int numskipped = 0;
int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
@@ -460,6 +466,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int rowCount = 0;
for (int i = startIndex; i < endIndex; ++i) {
if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
numskipped++;
continue;
}
int xPos = x + (col * columnWidth);
int yPos = y + yOffset;
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
@@ -482,6 +492,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
}
// This should correct the scrollbar
totalEntries -= numskipped;
#if !defined(M5STACK_UNITC6L)
// Draw column separator
if (shownCount > 0) {
@@ -492,6 +505,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#endif
const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
graphics::drawCommonFooter(display, x, y);
}
// =============================

View File

@@ -11,6 +11,7 @@
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/TimeFormatters.h"
#include "graphics/images.h"
#include "main.h"
#include "target_specific.h"
@@ -383,17 +384,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
uint32_t uptime = node->device_metrics.uptime_seconds;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
}
if (uptimeStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
@@ -552,6 +543,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// else show nothing
}
#endif
graphics::drawCommonFooter(display, x, y);
}
// ****************************
@@ -591,18 +583,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
@@ -771,6 +753,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
}
#endif
graphics::drawCommonFooter(display, x, y);
}
// Start Functions to write date/time to the screen
@@ -1046,36 +1029,17 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
// === Second Row: Last GPS Fix ===
if (gpsStatus->getLastFixMillis() > 0) {
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
uint32_t days = delta / 86400;
uint32_t hours = (delta % 86400) / 3600;
uint32_t mins = (delta % 3600) / 60;
uint32_t secs = delta % 60;
char buf[32];
uint32_t delta = millis() - gpsStatus->getLastFixMillis();
char uptimeStr[32];
#if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins
if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else {
snprintf(buf, sizeof(buf), "Last: %um", mins);
}
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
#else
// Non E-Ink: include seconds where useful
if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else if (mins > 0) {
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
} else {
snprintf(buf, sizeof(buf), "Last: %us", secs);
}
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
#endif
display->drawString(0, getTextPositions(display)[line++], buf);
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
} else {
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
}
@@ -1183,6 +1147,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
#endif
#endif // HAS_GPS
graphics::drawCommonFooter(display, x, y);
}
#ifdef USERPREFS_OEM_TEXT
@@ -1267,7 +1232,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
if (totalIcons == 0)
return;
const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing);
const int navPadding = isHighResolution ? 24 : 12; // padding per side
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
if (usableWidth < iconSize)
usableWidth = iconSize;
const size_t iconsPerPage = usableWidth / (iconSize + spacing);
const size_t currentPage = currentFrame / iconsPerPage;
const size_t pageStart = currentPage * iconsPerPage;
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
@@ -1338,6 +1309,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE);
}
}
// Compact arrow drawer
auto drawArrow = [&](bool rightSide) {
display->setColor(WHITE);
const int offset = isHighResolution ? 3 : 1;
const int halfH = rectHeight / 2;
const int top = (y - 2) + (rectHeight - halfH) / 2;
const int bottom = top + halfH - 1;
const int midY = top + (halfH / 2);
const int maxW = 4;
// Determine left X coordinate
int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
(rectX - offset - 1); // left arrow
for (int yy = top; yy <= bottom; yy++) {
int dist = abs(yy - midY);
int lineW = maxW - (dist * maxW / (halfH / 2));
if (lineW < 1)
lineW = 1;
if (rightSide) {
display->drawHorizontalLine(baseX, yy, lineW);
} else {
display->drawHorizontalLine(baseX - lineW + 1, yy, lineW);
}
}
};
// Right arrow
if (pageEnd < totalIcons) {
drawArrow(true);
}
// Left arrow
if (pageStart > 0) {
drawArrow(false);
}
// Knock the corners off the square
display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1);
@@ -1372,4 +1384,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi
} // namespace graphics
#endif // HAS_SCREEN
#endif // HAS_SCREEN

View File

@@ -18,6 +18,8 @@ const Emote emotes[] = {
{"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face
{"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height}, // 😉 Winking Face
{"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // 😁 Grinning Smiling Eyes
{"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height}, // 😍 Heart Eyes
{"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
// --- Question/Alert ---
{"\u2753", question, question_width, question_height}, // ❓ Question Mark
@@ -30,11 +32,15 @@ const Emote emotes[] = {
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
{"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
{"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😭 Loudly Crying Face
// --- Gestures and People ---
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
{"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌️ Victory Hand
{"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
{"\U0001F64F", Praying, Praying_width, Praying_height}, // 🙏 Praying Hands
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
// --- Weather ---
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
@@ -45,8 +51,12 @@ const Emote emotes[] = {
// --- Misc Faces ---
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
{"\U0001F921", clown, clown_width, clown_height}, // 🤡 Clown Face
{"\U0001F916", robo, robo_width, robo_height}, // 🤖 Robot Face
// --- Hearts (Multiple Unicode Aliases) ---
{"\u2665", heart, heart_width, heart_height}, // ♥ Black Heart Suit
{"\u2665\uFE0F", heart, heart_width, heart_height}, // ♥️ Black Heart Suit (emoji presentation)
{"\u2764\uFE0F", heart, heart_width, heart_height}, // ❤️ Red Heart
{"\U0001F9E1", heart, heart_width, heart_height}, // 🧡 Orange Heart
{"\U00002763", heart, heart_width, heart_height}, // ❣ Heart Exclamation
@@ -57,223 +67,166 @@ const Emote emotes[] = {
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
// --- Objects ---
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height} // 🔔 Bell
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
{"\U0001F36A", cookie, cookie_width, cookie_height}, // 🍪 Cookie
{"\U0001F525", Fire, Fire_width, Fire_height}, // 🔥 Fire
{"\u2728", Sparkles, Sparkles_width, Sparkles_height}, // ✨ Sparkles
{"\U0001F573\uFE0F", hole, hole_width, hole_height}, // 🕳️ Hole
{"\U0001F3B3", bowling, bowling_width, bowling_height} // 🎳 Bowling
#endif
};
const int numEmotes = sizeof(emotes) / sizeof(emotes[0]);
#ifndef EXCLUDE_EMOJI
const unsigned char thumbup[] PROGMEM = {
0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
};
const unsigned char thumbup[] PROGMEM = {0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x40, 0x04, 0x20, 0x02, 0x18,
0x02, 0x06, 0x3F, 0x06, 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x70,
0x06, 0x40, 0x06, 0x30, 0x08, 0x20, 0xF0, 0x1F, 0x00, 0x00};
const unsigned char thumbdown[] PROGMEM = {
0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
};
const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0x06, 0x40, 0x06, 0x70, 0x06,
0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
const unsigned char Smiling_Eyes[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf,
0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff,
0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
const unsigned char Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Grinning[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char Grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Slightly_Smiling[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char Slightly_Smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Winking_Face[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf,
0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
const unsigned char Winking_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf,
0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf,
0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char question[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18, 0x7C, 0x20, 0x38, 0x24, 0x52,
0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
const unsigned char bang[] PROGMEM = {
0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
};
const unsigned char Heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char haha[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0,
0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf,
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda,
0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf,
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char question[] PROGMEM = {0xE0, 0x07, 0x10, 0x08, 0x08, 0x10, 0x88, 0x11, 0x48, 0x12, 0x48,
0x12, 0x48, 0x12, 0x30, 0x11, 0x80, 0x08, 0x40, 0x04, 0x40, 0x02,
0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x40, 0x02, 0x80, 0x01};
const unsigned char ROFL[] PROGMEM = {
0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0,
0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb,
0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe,
0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde,
0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3,
0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0};
const unsigned char bang[] PROGMEM = {0x30, 0x0C, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48,
0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x30, 0x0C,
0x00, 0x00, 0x30, 0x0C, 0x48, 0x12, 0x30, 0x0C, 0x00, 0x00};
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf,
0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0,
0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7,
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf,
0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf,
0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1,
0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
const unsigned char ROFL[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char wave_icon[] PROGMEM = {
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7,
0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1,
0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0,
0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0,
0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0,
0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char cowboy[] PROGMEM = {
0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0,
0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0,
0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7,
0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7,
0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0,
0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0};
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char deadmau5[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const unsigned char Loudly_Crying_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
const unsigned char sun[] PROGMEM = {
0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
};
const unsigned char wave_icon[] PROGMEM = {0x00, 0x00, 0xC0, 0x18, 0x30, 0x21, 0x48, 0x5A, 0x94, 0x64, 0x24,
0x25, 0x4A, 0x24, 0x12, 0x44, 0x22, 0x44, 0x04, 0x40, 0x08, 0x40,
0x12, 0x40, 0x22, 0x20, 0xC4, 0x10, 0x18, 0x0F, 0x00, 0x00};
const unsigned char rain[] PROGMEM = {
0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
};
const unsigned char cowboy[] PROGMEM = {0x70, 0x0E, 0x8F, 0xF1, 0x11, 0x88, 0x21, 0x84, 0xC2, 0x43, 0x1E,
0x78, 0xE2, 0x47, 0x42, 0x42, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char cloud[] PROGMEM = {
0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
};
const unsigned char deadmau5[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0xE4, 0x27, 0x12, 0x48, 0x0A,
0x50, 0x0E, 0x70, 0x11, 0x88, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98,
0x19, 0x98, 0x19, 0x98, 0x11, 0x88, 0x0E, 0x70, 0x00, 0x00};
const unsigned char fog[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const unsigned char sun[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xEC, 0x37, 0xFC, 0x3F, 0xF8, 0x1F, 0xFC,
0x3F, 0xFE, 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFC, 0x3F,
0xF8, 0x1F, 0xFC, 0x3F, 0xEC, 0x37, 0x80, 0x01, 0x00, 0x00};
const unsigned char devil[] PROGMEM = {
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc,
0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf,
0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3,
0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3,
0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0,
0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
const unsigned char rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
const unsigned char heart[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
};
const unsigned char cloud[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC,
0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const unsigned char poo[] PROGMEM = {
0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0,
0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0,
0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0,
0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7,
0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf,
0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0};
const unsigned char fog[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x54, 0x55, 0x22, 0x22, 0x00,
0x00, 0x44, 0x44, 0xAA, 0x2A, 0x11, 0x11, 0x00, 0x00, 0x88, 0x88,
0x54, 0x55, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
const unsigned char bell_icon[] PROGMEM = {
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000,
0b00000011, 0b00000000, 0b00000000, 0b11111100, 0b00001111, 0b00000000, 0b00000000, 0b00001111, 0b00111100, 0b00000000,
0b00000000, 0b00000011, 0b00110000, 0b00000000, 0b10000000, 0b00000001, 0b01100000, 0b00000000, 0b11000000, 0b00000000,
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000, 0b10000000, 0b00000000, 0b01100000, 0b00000000,
0b10000000, 0b00000001, 0b01110000, 0b00000000, 0b10000000, 0b00000011, 0b00110000, 0b00000000, 0b00000000, 0b00000011,
0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100,
0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000,
0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
const unsigned char devil[] PROGMEM = {0x06, 0x60, 0xCA, 0x53, 0x32, 0x4C, 0x22, 0x44, 0x44, 0x22, 0x3A,
0x5C, 0x32, 0x4C, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x22, 0x44,
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char heart[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, 0x7E, 0x7E, 0xFE, 0x7F, 0xFE,
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F, 0xF8, 0x1F,
0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01, 0x00, 0x00};
const unsigned char poo[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x20, 0x04, 0x10, 0x04, 0xF0,
0x08, 0x10, 0x10, 0x48, 0x12, 0x08, 0x18, 0xE8, 0x21, 0x1C, 0x40,
0x42, 0x42, 0x82, 0x41, 0x02, 0x30, 0xFC, 0x0F, 0x00, 0x00};
const unsigned char bell_icon[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0xF0,
0x0F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFC, 0x3F,
0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x01, 0x00, 0x00};
const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x22, 0x32,
0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25, 0x40, 0x15, 0x40, 0x12, 0x38,
0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
const unsigned char Praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
const unsigned char Sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
const unsigned char clown[] PROGMEM = {0x00, 0x00, 0xEE, 0x77, 0x1A, 0x58, 0x06, 0x60, 0x24, 0x24, 0x72,
0x4E, 0x22, 0x44, 0x82, 0x41, 0x82, 0x41, 0x1A, 0x58, 0xF2, 0x4F,
0x14, 0x28, 0xE4, 0x27, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
const unsigned char robo[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xFC, 0x3F, 0x04, 0x20, 0x74,
0x2E, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x02, 0x40, 0xA2, 0x4A,
0x52, 0x45, 0x04, 0x20, 0x04, 0x20, 0xFC, 0x3F, 0x00, 0x00};
const unsigned char hole[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x3C, 0x3C,
0x06, 0x60, 0x0C, 0x30, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00};
const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00,
0x38, 0x00, 0x28, 0x78, 0x44, 0x84, 0x82, 0x22, 0x83, 0x52, 0x83,
0x02, 0x83, 0x02, 0x45, 0x84, 0x44, 0x78, 0x38, 0x00, 0x00};
const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
#endif
} // namespace graphics

View File

@@ -17,98 +17,150 @@ extern const int numEmotes;
#ifndef EXCLUDE_EMOJI
// === Emote Bitmaps ===
#define thumbs_height 25
#define thumbs_width 25
#define thumbs_height 16
#define thumbs_width 16
extern const unsigned char thumbup[] PROGMEM;
extern const unsigned char thumbdown[] PROGMEM;
#define Smiling_Eyes_height 30
#define Smiling_Eyes_width 30
#define Smiling_Eyes_height 16
#define Smiling_Eyes_width 16
extern const unsigned char Smiling_Eyes[] PROGMEM;
#define Grinning_height 30
#define Grinning_width 30
#define Grinning_height 16
#define Grinning_width 16
extern const unsigned char Grinning[] PROGMEM;
#define Slightly_Smiling_height 30
#define Slightly_Smiling_width 30
#define Slightly_Smiling_height 16
#define Slightly_Smiling_width 16
extern const unsigned char Slightly_Smiling[] PROGMEM;
#define Winking_Face_height 30
#define Winking_Face_width 30
#define Winking_Face_height 16
#define Winking_Face_width 16
extern const unsigned char Winking_Face[] PROGMEM;
#define Grinning_Smiling_Eyes_height 30
#define Grinning_Smiling_Eyes_width 30
#define Grinning_Smiling_Eyes_height 16
#define Grinning_Smiling_Eyes_width 16
extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
#define question_height 25
#define question_width 25
#define heart_smile_height 16
#define heart_smile_width 16
extern const unsigned char heart_smile[] PROGMEM;
#define Heart_eyes_height 16
#define Heart_eyes_width 16
extern const unsigned char Heart_eyes[] PROGMEM;
#define question_height 16
#define question_width 16
extern const unsigned char question[] PROGMEM;
#define bang_height 30
#define bang_width 30
#define bang_height 16
#define bang_width 16
extern const unsigned char bang[] PROGMEM;
#define haha_height 30
#define haha_width 30
#define haha_height 16
#define haha_width 16
extern const unsigned char haha[] PROGMEM;
#define ROFL_height 30
#define ROFL_width 30
#define ROFL_height 16
#define ROFL_width 16
extern const unsigned char ROFL[] PROGMEM;
#define Smiling_Closed_Eyes_height 30
#define Smiling_Closed_Eyes_width 30
#define Smiling_Closed_Eyes_height 16
#define Smiling_Closed_Eyes_width 16
extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
#define Grinning_SmilingEyes2_height 30
#define Grinning_SmilingEyes2_width 30
#define Grinning_SmilingEyes2_height 16
#define Grinning_SmilingEyes2_width 16
extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
#define wave_icon_height 30
#define wave_icon_width 30
#define Loudly_Crying_Face_height 16
#define Loudly_Crying_Face_width 16
extern const unsigned char Loudly_Crying_Face[] PROGMEM;
#define wave_icon_height 16
#define wave_icon_width 16
extern const unsigned char wave_icon[] PROGMEM;
#define cowboy_height 30
#define cowboy_width 30
#define cowboy_height 16
#define cowboy_width 16
extern const unsigned char cowboy[] PROGMEM;
#define deadmau5_height 30
#define deadmau5_width 60
#define deadmau5_height 16
#define deadmau5_width 16
extern const unsigned char deadmau5[] PROGMEM;
#define sun_height 30
#define sun_width 30
#define sun_height 16
#define sun_width 16
extern const unsigned char sun[] PROGMEM;
#define rain_height 30
#define rain_width 30
#define rain_height 16
#define rain_width 16
extern const unsigned char rain[] PROGMEM;
#define cloud_height 30
#define cloud_width 30
#define cloud_height 16
#define cloud_width 16
extern const unsigned char cloud[] PROGMEM;
#define fog_height 25
#define fog_width 25
#define fog_height 16
#define fog_width 16
extern const unsigned char fog[] PROGMEM;
#define devil_height 30
#define devil_width 30
#define devil_height 16
#define devil_width 16
extern const unsigned char devil[] PROGMEM;
#define heart_height 30
#define heart_width 30
#define heart_height 16
#define heart_width 16
extern const unsigned char heart[] PROGMEM;
#define poo_height 30
#define poo_width 30
#define poo_height 16
#define poo_width 16
extern const unsigned char poo[] PROGMEM;
#define bell_icon_width 30
#define bell_icon_height 30
#define bell_icon_width 16
#define bell_icon_height 16
extern const unsigned char bell_icon[] PROGMEM;
#define cookie_width 16
#define cookie_height 16
extern const unsigned char cookie[] PROGMEM;
#define Fire_width 16
#define Fire_height 16
extern const unsigned char Fire[] PROGMEM;
#define peace_sign_width 16
#define peace_sign_height 16
extern const unsigned char peace_sign[] PROGMEM;
#define Praying_width 16
#define Praying_height 16
extern const unsigned char Praying[] PROGMEM;
#define Sparkles_width 16
#define Sparkles_height 16
extern const unsigned char Sparkles[] PROGMEM;
#define clown_width 16
#define clown_height 16
extern const unsigned char clown[] PROGMEM;
#define robo_width 16
#define robo_height 16
extern const unsigned char robo[] PROGMEM;
#define hole_width 16
#define hole_height 16
extern const unsigned char hole[] PROGMEM;
#define bowling_width 16
#define bowling_height 16
extern const unsigned char bowling[] PROGMEM;
#define vulcan_salute_width 16
#define vulcan_salute_height 16
extern const unsigned char vulcan_salute[] PROGMEM;
#endif // EXCLUDE_EMOJI
} // namespace graphics
} // namespace graphics

View File

@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
#define chirpy_small_image_height 8
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#define connection_icon_width 7
#define connection_icon_height 5
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else

View File

@@ -287,7 +287,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
float easternmost = lngCenter;
float westernmost = lngCenter;
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Skip if no position
@@ -474,8 +474,8 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
// Need at least two, to draw a sensible map
bool InkHUD::MapApplet::enoughMarkers()
{
uint8_t count = 0;
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
size_t count = 0;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Count nodes

View File

@@ -5,7 +5,6 @@ A pattern / collection of resources for creating custom UIs, to target small gro
For an example, see the `heltec-vision-master-e290-inkhud` platformio env.
- platformio.ini
- suppress default Meshtastic components (Screen, ButtonThread, etc)
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file

View File

@@ -52,7 +52,7 @@ int InputBroker::handleInputEvent(const InputEvent *event)
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
moduleConfig.external_notification.enabled) {
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
externalNotificationModule->stopNow();
}

View File

@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
{0x00, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x20, 0x00, 0x00},
{0x00, 0x00, 0x00},
{0x00, 0x00, '0'},
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
};

View File

@@ -330,7 +330,7 @@ void setup()
#ifdef WIFI_LED
pinMode(WIFI_LED, OUTPUT);
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
#ifdef BLE_LED
@@ -477,6 +477,10 @@ void setup()
#ifdef RESET_OLED
pinMode(RESET_OLED, OUTPUT);
digitalWrite(RESET_OLED, 1);
delay(2);
digitalWrite(RESET_OLED, 0);
delay(10);
digitalWrite(RESET_OLED, 1);
#endif
#ifdef SENSOR_POWER_CTRL_PIN
@@ -959,6 +963,7 @@ void setup()
i2cScanner.reset();
#endif
#if !defined(MESHTASTIC_EXCLUDE_PKI)
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
@@ -969,6 +974,7 @@ void setup()
service->sendClientNotification(cn);
nodeDB->hasWarned = true;
}
#endif
// buttons are now inputBroker, so have to come after setupModules
#if HAS_BUTTON
@@ -1401,7 +1407,7 @@ void setup()
#endif
// check if the radio chip matches the selected region
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
nodeDB->saveToDisk(SEGMENT_CONFIG);

View File

@@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
LOG_DEBUG("Node %d or their public_key not found", toNode);
return false;
}
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
if (!setDHPublicKey(remotePublic.bytes)) {
return false;
}
crypto->hash(shared_key, 32);
hash(shared_key, 32);
initNonce(fromNode, packetNum, extraNonceTmp);
// Calculate the shared secret with the destination node and encrypt
@@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ
}
// Calculate the shared secret with the sending node and decrypt
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
if (!setDHPublicKey(remotePublic.bytes)) {
return false;
}
crypto->hash(shared_key, 32);
hash(shared_key, 32);
initNonce(fromNode, packetNum, extraNonce);
printBytes("Attempt decrypt with nonce: ", nonce, 13);

View File

@@ -29,6 +29,7 @@
#else
#define default_ringtone_nag_secs 15
#endif
#define default_network_ipv6_enabled false
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"
@@ -46,12 +47,15 @@ class Default
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
// Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
// even though internal node counts use uint16_t (max 65535 nodes)
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
private:
static float congestionScalingCoefficient(int numOnlineNodes)
// Note: Kept as uint32_t to match the public API parameter type
static float congestionScalingCoefficient(uint32_t numOnlineNodes)
{
// Increase frequency of broadcasts for small networks regardless of preset
if (numOnlineNodes <= 10) {

View File

@@ -25,7 +25,7 @@ template class LR11x0Interface<LR1121>;
template class SX126xInterface<STM32WLx>;
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include "api/ethServerAPI.h"
template class ServerAPI<EthernetClient>;
template class APIServerPort<ethServerAPI, EthernetServer>;

View File

@@ -244,6 +244,8 @@ template <typename T> void LR11x0Interface<T>::startReceive()
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
int err =
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
if (err)
LOG_ERROR("StartReceive error: %d", err);
assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive();
@@ -304,4 +306,4 @@ template <typename T> bool LR11x0Interface<T>::sleep()
return true;
}
#endif
#endif

View File

@@ -79,6 +79,18 @@ class MeshService
uint32_t oldFromNum = 0;
public:
enum APIState {
STATE_DISCONNECTED, // Initial state, no API is connected
STATE_BLE,
STATE_WIFI,
STATE_SERIAL,
STATE_PACKET,
STATE_HTTP,
STATE_ETH
};
APIState api_state = STATE_DISCONNECTED;
static bool isTextPayload(const meshtastic_MeshPacket *p)
{
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {

View File

@@ -653,7 +653,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
defined(ELECROW_PANEL)) && \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
@@ -718,6 +718,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
strncpy(config.network.wifi_psk, USERPREFS_NETWORK_WIFI_PSK, sizeof(config.network.wifi_psk));
#endif
#if defined(USERPREFS_NETWORK_IPV6_ENABLED)
config.network.ipv6_enabled = USERPREFS_NETWORK_IPV6_ENABLED;
#else
config.network.ipv6_enabled = default_network_ipv6_enabled;
#endif
#ifdef DISPLAY_FLIP_SCREEN
config.display.flip_screen = true;
#endif
@@ -728,6 +734,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
#endif
#ifdef COMPASS_ORIENTATION
config.display.compass_orientation = COMPASS_ORIENTATION;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::isUpdated()) {
WiFiOTA::recoverConfig(&config.network);
@@ -978,12 +987,25 @@ void NodeDB::installDefaultChannels()
channelFile.version = DEVICESTATE_CUR_VER;
}
void NodeDB::resetNodes()
void NodeDB::resetNodes(bool keepFavorites)
{
if (!config.position.fixed_position)
clearLocalPosition();
numMeshNodes = 1;
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
if (keepFavorites) {
LOG_INFO("Clearing node database - preserving favorites");
for (size_t i = 0; i < meshNodes->size(); i++) {
meshtastic_NodeInfoLite &node = meshNodes->at(i);
if (i > 0 && !node.is_favorite) {
node = meshtastic_NodeInfoLite();
} else {
numMeshNodes += 1;
}
};
} else {
LOG_INFO("Clearing node database - removing favorites");
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
}
devicestate.has_rx_text_message = false;
devicestate.has_rx_waypoint = false;
saveNodeDatabaseToDisk();
@@ -1632,13 +1654,32 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
// If should_ignore is set,
// we need to clear the public key and other cruft, in addition to setting the node as ignored
info->is_ignored = true;
info->is_favorite = false;
info->has_device_metrics = false;
info->has_position = false;
info->user.public_key.size = 0;
info->user.public_key.bytes[0] = 0;
} else {
info->last_heard = getValidTime(RTCQualityNTP);
info->is_favorite = true;
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
*/
/* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
* nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
* new node as a favorite, and we leave last_heard alone (even if it's zero).
*/
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
// without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
// contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
// last_heard to now, so that the add_contact node doesn't immediately get evicted.
info->last_heard = getTime();
} else {
// Normal case: set is_favorite to prevent expiration.
// last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
info->is_favorite = true;
}
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
if (contact.manually_verified) {
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
@@ -1970,6 +2011,7 @@ UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
}
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
{
if (keyToTest.size == 32) {
@@ -1984,6 +2026,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
}
return false;
}
#endif
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
{

View File

@@ -229,7 +229,8 @@ class NodeDB
*/
size_t getNumOnlineMeshNodes(bool localOnly = false);
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
removeNodeByNum(NodeNum nodeNum);
bool factoryReset(bool eraseBleBonds = false);
@@ -282,7 +283,9 @@ class NodeDB
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
#endif
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
@@ -372,4 +375,4 @@ extern uint32_t error_address;
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
// Please do not remove this comment, it makes trunk and compiler happy at the same time.

View File

@@ -87,6 +87,18 @@ void PhoneAPI::handleStartConfig()
void PhoneAPI::close()
{
LOG_DEBUG("PhoneAPI::close()");
if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE)
service->api_state = service->STATE_DISCONNECTED;
else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI)
service->api_state = service->STATE_DISCONNECTED;
else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL)
service->api_state = service->STATE_DISCONNECTED;
else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET)
service->api_state = service->STATE_DISCONNECTED;
else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP)
service->api_state = service->STATE_DISCONNECTED;
else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH)
service->api_state = service->STATE_DISCONNECTED;
if (state != STATE_SEND_NOTHING) {
state = STATE_SEND_NOTHING;
@@ -578,6 +590,19 @@ void PhoneAPI::sendConfigComplete()
fromRadioScratch.config_complete_id = config_nonce;
config_nonce = 0;
state = STATE_SEND_PACKETS;
if (api_type == TYPE_BLE) {
service->api_state = service->STATE_BLE;
} else if (api_type == TYPE_WIFI) {
service->api_state = service->STATE_WIFI;
} else if (api_type == TYPE_SERIAL) {
service->api_state = service->STATE_SERIAL;
} else if (api_type == TYPE_PACKET) {
service->api_state = service->STATE_PACKET;
} else if (api_type == TYPE_HTTP) {
service->api_state = service->STATE_HTTP;
} else if (api_type == TYPE_ETH) {
service->api_state = service->STATE_ETH;
}
// Allow subclasses to know we've entered steady-state so they can lower power consumption
onConfigComplete();

View File

@@ -167,6 +167,18 @@ class PhoneAPI
/// begin a new connection
void handleStartConfig();
enum APIType {
TYPE_NONE, // Initial state, don't send anything until the client starts asking for config
TYPE_BLE,
TYPE_WIFI,
TYPE_SERIAL,
TYPE_PACKET,
TYPE_HTTP,
TYPE_ETH
};
APIType api_type = TYPE_NONE;
private:
void releasePhonePacket();

View File

@@ -13,7 +13,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
const pb_msgdesc_t *fields;
public:
uint8_t numOnlineNodes = 0;
uint16_t numOnlineNodes = 0;
/** Constructor
* name is for debugging output
*/

View File

@@ -479,6 +479,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
LOG_ERROR("Invalid portnum (bad psk?)!");
#if !(MESHTASTIC_EXCLUDE_PKI)
} else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
LOG_WARN("Rejecting legacy DM");
return DecodeState::DECODE_FAILURE;
#endif
} else {
p->decoded = decodedtmp;
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded

View File

@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
PacketAPI::PacketAPI(PacketServer *_server)
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
{
api_type = TYPE_PACKET;
}
int32_t PacketAPI::runOnce()

View File

@@ -25,6 +25,7 @@ void deInitApiServer()
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
{
api_type = TYPE_WIFI;
LOG_INFO("Incoming wifi connection");
}

View File

@@ -1,7 +1,7 @@
#include "configuration.h"
#include <Arduino.h>
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
#include "ethServerAPI.h"
@@ -20,6 +20,7 @@ void initApiServer(int port)
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
{
LOG_INFO("Incoming ethernet connection");
api_type = TYPE_ETH;
}
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ServerAPI.h"
#ifndef USE_WS5500
#if !defined(USE_WS5500) && !defined(USE_CH390D)
#include <RAK13800_W5100S.h>
/**

View File

@@ -272,8 +272,9 @@ typedef struct _meshtastic_AdminMessage {
int32_t shutdown_seconds;
/* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. */
int32_t factory_reset_config;
/* Tell the node to reset the nodedb. */
int32_t nodedb_reset;
/* Tell the node to reset the nodedb.
When true, favorites are preserved through reset. */
bool nodedb_reset;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -459,7 +460,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulato
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_seconds), 97) \
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL

View File

@@ -284,6 +284,16 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_WATCH_ULTRA = 114,
/* Elecrow ThinkNode M3 */
meshtastic_HardwareModel_THINKNODE_M3 = 115,
/* RAK WISMESH_TAP_V2 with ESP32-S3 CPU */
meshtastic_HardwareModel_WISMESH_TAP_V2 = 116,
/* RAK3401 */
meshtastic_HardwareModel_RAK3401 = 117,
/* RAK6421 Hat+ */
meshtastic_HardwareModel_RAK6421 = 118,
/* Elecrow ThinkNode M4 */
meshtastic_HardwareModel_THINKNODE_M4 = 119,
/* Elecrow ThinkNode M6 */
meshtastic_HardwareModel_THINKNODE_M6 = 120,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -821,7 +831,11 @@ typedef struct _meshtastic_MeshPacket {
Note: Our crypto implementation uses this field as well.
See [crypto](/docs/overview/encryption) for details. */
uint32_t from;
/* The (immediate) destination for this packet */
/* The (immediate) destination for this packet
If the value is 4,294,967,295 (maximum value of an unsigned 32bit integer), this indicates that the packet was
not destined for a specific node, but for a channel as indicated by the value of `channel` below.
If the value is another, this indicates that the packet was destined for a specific
node (i.e. a kind of "Direct Message" to this node) and not broadcast on a channel. */
uint32_t to;
/* (Usually) If set, this indicates the index in the secondary_channels table that this packet was sent/received on.
If unset, packet was on the primary channel.

View File

@@ -26,7 +26,7 @@ class HttpAPI : public PhoneAPI
{
public:
// Nothing here yet
HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet

View File

@@ -27,7 +27,7 @@ class HttpAPI : public PhoneAPI
{
public:
// Nothing here yet
HttpAPI() { api_type = TYPE_HTTP; }
private:
// Nothing here yet

View File

@@ -334,6 +334,23 @@ bool initWifi()
}
#ifdef ARCH_ESP32
#if ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
// Most of the next 12 lines of code are adapted from espressif/arduino-esp32
// Licensed under the GNU Lesser General Public License v2.1
// https://github.com/espressif/arduino-esp32/blob/1f038677eb2eaf5e9ca6b6074486803c15468bed/libraries/WiFi/src/WiFiSTA.cpp#L755
esp_netif_t *get_esp_interface_netif(esp_interface_t interface);
IPv6Address GlobalIPv6()
{
esp_ip6_addr_t addr;
if (WiFiGenericClass::getMode() == WIFI_MODE_NULL) {
return IPv6Address();
}
if (esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr)) {
return IPv6Address();
}
return IPv6Address(addr.addr);
}
#endif
// Called by the Espressif SDK to
static void WiFiEvent(WiFiEvent_t event)
{
@@ -355,14 +372,25 @@ static void WiFiEvent(WiFiEvent_t event)
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
LOG_INFO("Connected to access point");
if (config.network.ipv6_enabled) {
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
if (!WiFi.enableIPv6()) {
LOG_WARN("Failed to enable IPv6");
}
#else
if (!WiFi.enableIpV6()) {
LOG_WARN("Failed to enable IPv6");
}
#endif
}
#ifdef WIFI_LED
digitalWrite(WIFI_LED, HIGH);
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
LOG_INFO("Disconnected from WiFi access point");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
if (!isReconnecting) {
WiFi.disconnect(false, true);
@@ -383,7 +411,8 @@ static void WiFiEvent(WiFiEvent_t event)
LOG_INFO("Obtained Local IP6 address: %s", WiFi.linkLocalIPv6().toString().c_str());
LOG_INFO("Obtained GlobalIP6 address: %s", WiFi.globalIPv6().toString().c_str());
#else
LOG_INFO("Obtained IP6 address: %s", WiFi.localIPv6().toString().c_str());
LOG_INFO("Obtained Local IP6 address: %s", WiFi.localIPv6().toString().c_str());
LOG_INFO("Obtained GlobalIP6 address: %s", GlobalIPv6().toString().c_str());
#endif
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
@@ -413,13 +442,13 @@ static void WiFiEvent(WiFiEvent_t event)
case ARDUINO_EVENT_WIFI_AP_START:
LOG_INFO("WiFi access point started");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, HIGH);
digitalWrite(WIFI_LED, LOW ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
LOG_INFO("WiFi access point stopped");
#ifdef WIFI_LED
digitalWrite(WIFI_LED, LOW);
digitalWrite(WIFI_LED, HIGH ^ WIFI_STATE_ON);
#endif
break;
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
@@ -514,4 +543,4 @@ uint8_t getWifiDisconnectReason()
{
return wifiDisconnectReason;
}
#endif // HAS_WIFI
#endif // HAS_WIFI

View File

@@ -104,9 +104,18 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
(config.security.admin_key[2].size == 32 &&
memcmp(mp.public_key.bytes, config.security.admin_key[2].bytes, 32) == 0)) {
LOG_INFO("PKC admin payload with authorized sender key");
// Automatically favorite the node that is using the admin key
auto remoteNode = nodeDB->getMeshNode(mp.from);
if (remoteNode && !remoteNode->is_favorite) {
remoteNode->is_favorite = true;
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
// without the user doing so deliberately.
LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
} else {
LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
remoteNode->is_favorite = true;
}
}
} else {
myReply = allocErrorResponse(meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED, &mp);
@@ -280,7 +289,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
case meshtastic_AdminMessage_nodedb_reset_tag: {
disableBluetooth();
LOG_INFO("Initiate node-db reset");
nodeDB->resetNodes();
// CLIENT_BASE, ROUTER and ROUTER_LATE are able to preserve the remaining hop count when relaying a packet via a
// favorited node, so ensure that their favorites are kept on reset
bool rolePreference =
isOneOf(config.device.role, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE,
meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
nodeDB->resetNodes(rolePreference ? rolePreference : r->nodedb_reset);
reboot(DEFAULT_REBOOT_SECONDS);
break;
}
@@ -759,6 +773,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
@@ -777,6 +792,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
}
#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
@@ -1211,7 +1227,7 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
}
#endif
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET && !defined(USE_WS5500) && !defined(USE_CH390D)
conn.has_ethernet = true;
conn.ethernet.has_status = true;
if (Ethernet.linkStatus() == LinkON) {

View File

@@ -973,8 +973,14 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
LOG_INFO("Send message id=%u, dest=%x, msg=%.*s", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
if (p->to != 0xffffffff) {
LOG_INFO("Proactively adding %x as favorite node", p->to);
nodeDB->set_favorite(true, p->to);
// Only add as favorite if our role is NOT CLIENT_BASE
if (config.device.role != 12) {
LOG_INFO("Proactively adding %x as favorite node", p->to);
nodeDB->set_favorite(true, p->to);
} else {
LOG_DEBUG("Not favoriting node %x as we are CLIENT_BASE role", p->to);
}
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
p->pki_encrypted = true;
p->channel = 0;

View File

@@ -314,11 +314,10 @@ void ExternalNotificationModule::stopNow()
audioThread->stop();
#endif
// Turn off all outputs
LOG_INFO("Turning off setExternalStates: ");
LOG_INFO("Turning off setExternalStates");
for (int i = 0; i < 3; i++) {
setExternalState(i, false);
externalTurnedOn[i] = 0;
LOG_INFO("%d ", i);
}
setIntervalFromNow(0);
#ifdef T_WATCH_S3

View File

@@ -13,6 +13,8 @@
#include "input/TrackballInterruptImpl1.h"
#endif
#include "modules/StatusLEDModule.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "input/cardKbI2cImpl.h"
#endif
@@ -119,6 +121,10 @@ void setupModules()
buzzerFeedbackThread = new BuzzerFeedbackThread();
}
#endif
#if defined(LED_CHARGE) || defined(LED_PAIRING)
statusLEDModule = new StatusLEDModule();
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
@@ -175,12 +181,13 @@ void setupModules()
// new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#ifndef T_LORA_PAGER
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr;
}
#ifdef T_LORA_PAGER
#elif defined(T_LORA_PAGER)
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {

View File

@@ -34,7 +34,8 @@ void NeighborInfoModule::printNodeDBNeighbors()
}
}
/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */
/* Send our initial owner announcement 35 seconds after we start (to give
* network time to setup) */
NeighborInfoModule::NeighborInfoModule()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
concurrency::OSThread("NeighborInfo")
@@ -53,8 +54,8 @@ NeighborInfoModule::NeighborInfoModule()
}
/*
Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
Assumes that the neighborInfo packet has been allocated
Collect neighbor info from the nodeDB's history, capping at a maximum number of
entries and max time Assumes that the neighborInfo packet has been allocated
@returns the number of entries collected
*/
uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo)
@@ -71,8 +72,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb
if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr;
// Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over
// the mesh
// Note: we don't set the last_rx_time and node_broadcast_intervals_secs
// here, because we don't want to send this over the mesh
neighborInfo->neighbors_count++;
}
}
@@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors()
uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum();
for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
// We will remove a neighbor if we haven't heard from them in twice the broadcast interval
// cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970
// We will remove a neighbor if we haven't heard from them in twice the
// broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is
// seconds since 1970
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
it = std::vector<meshtastic_Neighbor>::reverse_iterator(
@@ -132,25 +134,55 @@ int32_t NeighborInfoModule::runOnce()
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs);
}
meshtastic_MeshPacket *NeighborInfoModule::allocReply()
{
LOG_INFO("NeighborInfoRequested.");
if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) {
LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago");
ignoreRequest = true; // Mark it as ignored for MeshModule
return nullptr;
}
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo);
if (reply) {
lastSentReply = millis(); // Track when we sent this reply
}
return reply;
}
/*
Collect a received neighbor info packet from another node
Pass it to an upper client; do not persist this data on the mesh
*/
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{
LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (np) {
printNeighborInfo("RECEIVED", np);
updateNeighbors(mp, np);
// Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0
if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) {
LOG_DEBUG(" Updating neighbours");
updateNeighbors(mp, np);
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
getOrCreateNeighbor(mp.from, mp.from, 0,
mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
}
// Allow others to handle this packet
return false;
}
/*
Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum
Copy the content of a current NeighborInfo packet into a new one and update the
last_sent_by_id to our NodeNum
*/
void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{
@@ -168,8 +200,10 @@ void NeighborInfoModule::resetNeighbors()
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
{
// The last sent ID will be 0 if the packet is from the phone, which we don't count as
// an edge. So we assume that if it's zero, then this packet is from our node.
LOG_DEBUG("updateNeighbors");
// The last sent ID will be 0 if the packet is from the phone, which we don't
// count as an edge. So we assume that if it's zero, then this packet is from
// our node.
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr);
}
@@ -188,7 +222,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
// if found, update it
neighbors[i].snr = snr;
neighbors[i].last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it
// Only if this is the original sender, the broadcast interval corresponds
// to it
if (originalSender == n && node_broadcast_interval_secs != 0)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i];
@@ -200,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
new_nbr.node_id = n;
new_nbr.snr = snr;
new_nbr.last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it
// Only if this is the original sender, the broadcast interval corresponds to
// it
if (originalSender == n && node_broadcast_interval_secs != 0)
new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
else // Assume the same broadcast interval as us for the neighbor if we don't know it
else // Assume the same broadcast interval as us for the neighbor if we don't
// know it
new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
if (neighbors.size() < MAX_NUM_NEIGHBORS) {

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