Compare commits

..

109 Commits

Author SHA1 Message Date
Ben Meadors
9fce57e74d Remove .tmp 2025-03-04 08:37:54 -06:00
Ben Meadors
0533ca0eda Move order 2025-03-04 06:57:54 -06:00
Ben Meadors
661fcd0e77 Locks 2025-03-04 06:55:00 -06:00
Ben Meadors
ac3980e98d Remove old junk 2025-03-03 07:48:39 -06:00
Ben Meadors
ff4b7fdce3 Remove this one 2025-03-02 08:54:10 -06:00
Ben Meadors
b933b48898 Update calls 2025-03-02 08:41:20 -06:00
Ben Meadors
df52bbcae3 Merge branch 'master' into backup-restore 2025-03-02 08:23:55 -06:00
Ben Meadors
4a9f6ceb55 Remove automatic backup 2025-02-27 06:49:15 -06:00
Ben Meadors
db84fbed5d Change to warning 2025-02-27 06:45:07 -06:00
Ben Meadors
96663218ee Move reboot 2025-02-27 06:41:09 -06:00
Ben Meadors
51dc2da83c Merge remote-tracking branch 'origin/2.6' into backup-restore 2025-02-27 06:16:26 -06:00
Ben Meadors
19951e3b6a Merge branch 'master' into 2.6 2025-02-26 17:43:54 -06:00
mverch67
0b106d4642 MUI: BT programming mode button 2025-02-26 23:41:52 +01:00
mverch67
70d8da6561 MUI hot-fix username dialog 2025-02-26 22:37:34 +01:00
mverch67
5f5fac25fd MUI hot-fix pub/priv keys 2025-02-26 21:58:36 +01:00
mverch67
9cdd6b8633 defined(SENSECAP_INDICATOR) 2025-02-26 20:10:34 +01:00
mverch67
f5db94e606 device-ui lib: last minute update 2025-02-26 14:01:28 +01:00
mverch67
3f512976b8 device-lib update: fix settings panel -> no scrolling 2025-02-26 10:42:04 +01:00
mverch67
ad41f9e013 fix channel OK button 2025-02-26 10:11:43 +01:00
mverch67
b85d9f988e Merge branch '2.6' of https://github.com/meshtastic/firmware into 2.6 2025-02-26 03:28:28 +01:00
mverch67
7150a68714 device-ui lib update 2025-02-26 03:28:22 +01:00
Ben Meadors
6cf8cbcda7 Updeet 2025-02-25 20:28:00 -06:00
Ben Meadors
4e74c549ed Remove backup 2025-02-25 08:56:57 -06:00
Ben Meadors
065370c6b6 Merge remote-tracking branch 'origin/2.6' into backup-restore 2025-02-25 07:57:57 -06:00
github-actions[bot]
f913ce0310 [create-pull-request] automated change (#6152)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-25 07:49:06 -06:00
Ben Meadors
261c326da6 Protos 2025-02-25 07:34:40 -06:00
Ben Meadors
eb3ffc1922 Updates and fix lame legacy code paths 2025-02-25 07:32:17 -06:00
Ben Meadors
c986c4a742 Merge branch '2.6' into backup-restore 2025-02-25 05:11:27 -06:00
github-actions[bot]
cb0982c2f1 [create-pull-request] automated change (#6144)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-24 19:25:46 -06:00
Ben Meadors
ccbec44bfb Space 2025-02-24 19:20:37 -06:00
Ben Meadors
109d66e976 Merge remote-tracking branch 'origin/master' into 2.6 2025-02-24 19:20:27 -06:00
Ben Meadors
320a38f9ac Update protobufs 2025-02-24 19:18:26 -06:00
Ben Meadors
488e76d27b Merge remote-tracking branch 'origin/master' into 2.6 2025-02-24 07:11:06 -06:00
Ben Meadors
ada8b96842 Backup / restore preferences method 2025-02-24 07:10:08 -06:00
github-actions[bot]
c2cc3207ba [create-pull-request] automated change (#6133)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-23 13:30:45 -06:00
Ben Meadors
f309fba89c Proto update for backup 2025-02-23 13:06:45 -06:00
github-actions[bot]
7dc0330897 [create-pull-request] automated change (#6131) 2025-02-23 10:10:05 -06:00
Ben Meadors
6c227157b5 Proto 2025-02-23 08:56:06 -06:00
github-actions[bot]
e4c6aa588e [create-pull-request] automated change (#6129)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-23 08:09:39 -06:00
Ben Meadors
164d46facb Update protos and device-ui 2025-02-23 08:05:12 -06:00
Ben Meadors
6c6b804b53 Update device-ui 2025-02-19 18:17:50 -06:00
Ben Meadors
1a92b7881f Merge remote-tracking branch 'origin/master' into 2.6 2025-02-19 18:17:30 -06:00
Ben Meadors
1961bcaf9d Backup / migrate pub private keys when upgrading to new files in 2.6 (#6096)
* Save a backup of pub/private keys before factory reset

* Fix licensed mode warning

* Unlock spi on else file doesn't exist
2025-02-20 07:54:56 +08:00
Ben Meadors
a7c4361d7c Merge remote-tracking branch 'origin/master' into 2.6 2025-02-17 15:04:11 -06:00
Manuel
c0145001ef unPhone TFT: include into build, enable SD card, increase PSRAM (#6082)
* unPhone-tft: include into build, enable SD card, increase assigned PSRAM

* lib update
2025-02-17 13:45:12 -06:00
Ben Meadors
01618e99e5 Update device-ui 2025-02-17 13:44:42 -06:00
Manuel
a02d538b58 MUI: move UI initialization out of main.cpp and adding lightsleep observer + mutex (#6078)
* added device-ui to lightSleep observers for handling graceful sleep; refactoring main.cpp

* bump lib version
2025-02-17 10:35:11 -06:00
Thomas Göttgens
ac9cb235a8 Merge branch 'master' into 2.6 2025-02-17 15:53:14 +01:00
Ben Meadors
9ce19c5c1c Prevent licensed users from rebroadcasting unlicensed traffic (#6068)
* Prevent licensed users from rebroadcasting unlicensed traffic

* Added method and enum to make user license status more clear
2025-02-16 20:18:16 -06:00
Ben Meadors
022dc29ea7 Device UI submodule bump 2025-02-15 09:35:01 -06:00
Ben Meadors
18410ba80d Merge remote-tracking branch 'origin/master' into 2.6 2025-02-15 09:34:05 -06:00
Ben Meadors
3f3f89c06e Licensed usage compliance (#6047)
* Prevent psk and legacy admin channel on licensed mode

* Move it

* Consolidate warning strings

* More holes
2025-02-15 09:24:37 -06:00
GUVWAF
919085379e Increase MAX_THREADS for InkHUD variants with WiFi (#6064) 2025-02-15 09:05:26 -06:00
Ben Meadors
bdf60d8e4d Fixed persistence stragglers from NodeDB / Device State divorce (#6059) 2025-02-15 07:56:04 -06:00
todd-herbert
431b067f30 Wireless Paper: remove stray board_level = extra (#6060)
Makes sure the InkHUD version gets build into the release zip
2025-02-15 19:04:39 +08:00
mverch67
4a63b36a7d remove debuglog leftover 2025-02-14 10:25:54 +01:00
Manuel
1fe16a0471 MUI: fix nag timeout, disable BT programming mode for native (#6052)
* allow BT connection with disabled MUI

* Update device-ui

* MUI: fix nag timeout default and remove programming mode for native

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-14 09:04:54 +01:00
Manuel
2b1f45fd8b MUI: BT programming mode (#6046)
* allow BT connection with disabled MUI

* Update device-ui

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-02-13 12:01:24 -06:00
Ben Meadors
79c0e8168d Sigh... 2025-02-12 15:07:19 -06:00
Ben Meadors
459dbfff23 Yet another case issue! 2025-02-12 13:39:16 -06:00
Ben Meadors
27ddd549f9 RTC 2025-02-12 11:55:03 -06:00
Ben Meadors
e5ed913a8a Moar 2025-02-12 11:31:39 -06:00
Ben Meadors
93d041b4b0 More case-sensitivity instances 2025-02-12 09:39:25 -06:00
Ben Meadors
005e8501ce Case sensitive perhaps? 2025-02-12 08:02:03 -06:00
Ben Meadors
6cb3acd79f Guard eink drivers w/ MESHTASTIC_INCLUDE_NICHE_GRAPHICS 2025-02-12 06:23:49 -06:00
todd-herbert
910efd86f0 fix: InkHUD shouldn't nag about timezone (#6040) 2025-02-12 19:09:42 +08:00
Ben Meadors
ded45cf17d Collect inkhud 2025-02-11 20:01:57 -06:00
Ben Meadors
6f9e4d5b1a Merge remote-tracking branch 'origin/master' into 2.6 2025-02-11 19:57:57 -06:00
mverch67
b012e561eb update device-ui: fix touch/crash issue while light sleep 2025-02-11 23:54:42 +01:00
todd-herbert
9fd100dd92 InkHUD UI for E-Ink (#6034)
* Decouple ButtonThread from sleep.cpp
Reorganize sleep observables. Don't call ButtonThread methods inside doLightSleep. Instead, handle in class with new lightsleep Observables.

* InkHUD: initial commit (WIP)
Publicly discloses the current work in progress. Not ready for use.

* feat: battery icon

* chore: implement meshtastic/firmware #5454
Clean up some inline functions

* feat: menu & settings for "jump to applet"

* Remove the beforeRender pattern
It hugely complicates things. If we can achieve acceptable performance without it, so much the better.

* Remove previous Map Applet
Needs re-implementation to work without the beforeRender pattern

* refactor: reimplement map applet
Doesn't require own position
Doesn't require the beforeRender pattern to precalculate; now all-at-once in render
Lays groundwork for fixed-size map with custom background image

* feat: autoshow
Allow user to select which applets (if any) should be automatically brought to foreground when they have new data to display

* refactor: tidy-up applet constructors
misc. jobs including:
- consistent naming
- move initializer-list-only constructors to header
- give derived applets unique identifiers for MeshModule and OSThread logging

* hotfix: autoshow always uses FAST update
In future, it *will* often use FAST, but this will be controlled by a WindowManager component which has not yet been written.
Hotfixed, in case anybody is attempting to use this development version on their deployed devices.

* refactor: bringToForeground no longer requests FAST update
In situations where an applet has moved to foreground because of user input, requestUpdate can be manually called, to upgrade to FAST refresh.
More permanent solution for #23e1dfc

* refactor: extract string storage from ThreadedMessageApplet
Separates the code responsible for storing the limited message history, which was previously part of the ThreadedMessageApplet.
We're now also using this code to store the "most recent message". Previously, this was stored in the `InkHUD::settings` struct, which was much less space-efficient.
We're also now storing the latest DM, laying the foundation for an applet to display only DMs, which will complement the threaded message applet.

* fix: text wrapping
Attempts to fix a disparity between `Applet::printWrapped` and `Applet::getWrappedTextHeight`, which would occasionally cause a ThreadedMessageApplet message to render "too short", overlapping other text.

* fix: purge old constructor
This one slipped through the last commit..

* feat: DM Applet
Useful in combination with the ThreadedMessageApplets, which don't show DMs

* fix: applets shouldn't handle events while deactivated
Only one or two applets were actually doing this, but I'm making a habit of having all applets return early from their event handling methods (as good practice), even if those methods are disabled elsewhere (e.g. not observing observable, return false from wantPacket)

* refactor: allow requesting update without requesting autoshow
Some applets may want to redraw, if they are displayed, but not feel the information is worth being brought to foreground for. Example: ActiveNodesApplet, when purging old nodes from list.

* feat: custom "Recently Active" duration
Allows users to tailor how long nodes will appear in the "Recents" applets, to suit the activity level of their mesh.

* refactor: rename some applets

* fix: autoshow

* fix: getWrappedTextHeight
Remove the "simulate" option from printWrapped; too hard to keep inline with genuine printing (because of AdafruitGFX Fonts' xAdvance, mabye?). Instead of simulating, we printWrapped as normal, and discard pixel output by setting crop. Both methods are similarly inefficient, apparently.

* fix: text wrapping in ThreadedMessageApplet
Wrong arguments were passed to Applet::printWrapped

* feat: notifications for text messages
Only shown if current applet does not already display the same info. Autoshow takes priority over notifications, if both would be used to display the same info.

* feat: optimize FAST vs FULL updates
New UpdateMediator class counts the number of each update type, and suggets which one to use, if the code doesn't already have an explicit prefence. Also performs "maintenance refreshes" unprovoked if display is not given an opportunity to before a FULL refresh through organic use.

* chore: update todo list

* fix: rare lock-up of buttons

* refactor: backlight
Replaces the initial proof-of-concept frontlight code for T-Echo
Presses less than 5 seconds momentarily illuminate the display
Presses longer than 5 seconds latch the light, requiring another tap to disable
If user has previously removed the T-Echo's capacitive touch button (some DIY projects), the light is controlled by the on-screen menu. This fallback is used by all T-Echo devices, until a press of the capacitive touch button is detected.

* feat: change tile with aux button
Applied to VM-E290.
Working as is, but a refactor of WindowManager::render is expected shortly, which will also tidy code from this push.

* fix: specify out-of-the-box tile assignments
Prevents placeholder applet showing on initial boot, for devices which use a mult-tile layout by default (VM-E290)

* fix: verify settings version when loading

* fix: wrong settings version

* refactor: remove unimplemented argument from requestUpdate
Specified whether or not to update "async", however the implementation was slightly broken, Applet::requestUpdate is only handled next time WindowManager::runOnce is called. This didn't allow code to actually await an update, which was misleading.

* refactor: renaming
Applet::render becomes Applet::onRender.
Tile::displayedApplet becomes Tile::assignedApplet.
New onRender method name allows us to move some of the pre and post render code from WindowManager into new Applet::render method, which will call onRender for us.

* refactor: rendering
Bit of a tidy-up. No intended change in behavior.

* fix: optimize refresh times
Shorter wait between retrying update if display was previously busy.
Set anticipated update durations closer to observed values. No signifacant performance increase, but does decrease the amount of polling required.

* feat: blocking update for E-Ink
Option to wait for display update to complete before proceeding. Important when shutting down the device.

* refactor: allow system applets to lock rendering
Temporarily prevents other applets from rendering.

* feat: boot and shutdown screens

* feat: BluetoothStatus
Adds a meshtastic::Status object which exposes the state of the Bluetooth connection. Intends to allow decoupling of UI code.

* feat: Bluetooth pairing screen

* fix: InkHUD defaults not honored

* fix: random Bluetooth pin for NicheGraphics UIs

* chore: button interrupts tested

* fix: emoji reactions show as blank messages

* fix: autoshow and notification triggered by outgoing message

* feat: save InkHUD data before reboot
Implemented with a new Observable. Previously, config and a few recent messages were saved on shutdown. These were lost if the device rebooted, for example when firmware settings were changed by a client. Now, the InkHUD config and recent messages saved on reboot, the same as during an intentional shutdown.

* feat: imperial distances
Controlled by the config.display.units setting

* fix: hide features which are not yet implemented

* refactor: faster rendering
Previously, only tiles which requested update were re-rendered. Affected tiles had their region blanked before render, pixel by pixel. Benchmarking revealed that it is significantly faster to memset the framebuffer and redraw all tiles.

* refactor: tile ownership
Tiles and Applets now maintain a reciprocal link, which is enforced by asserts. Less confusing than the old situation, where an applet and a tile may disagree on their relationship. Empty tiles are now identified by a nullptr *Applet, instead of by having the placeholderApplet assigned.

* fix: notifications and battery when menu open
Do render notifications in front of menu; don't render battery icon in front of menu.

* fix: simpler defaults
Don't expose new users to multiplexed applets straight away: make them enable the feature for themselves.

* fix: Inputs::TwoButton interrupts, when only one button in use

* fix: ensure display update is complete when ESP32 enters light sleep
Many panels power down automatically, but some require active intervention from us. If light sleep (ESP32) occurs during a display update, these panels could potentially remain powered on, applying voltage the pixels for an extended period of time, and potentially damaging the display.

* fix: honor per-variant user tile limit
Set as the default value for InkHUD::settings.userTiles.maxCount in nicheGraphics.h

* feat: initial InkHUD support for Wireless Paper v1.1 and VM-E213

* refactor: Heard and Recents Applets
Tidier code, significant speed boost. Possibly no noticable change in responsiveness, but rendering now spends much less time blocking execution, which is important for correction functioning of the other firmware components.

* refactor: use a common pio base config
Easier to make any future PlatformIO config changes

* feat: tips
Show information that we think the user might find helpful. Some info shown first boot only. Other info shown when / if relevant.

* fix: text wrapping for '\n'
Previously, the newline was honored, but the adojining word was not printed.

* Decouple ButtonThread from sleep.cpp
Reorganize sleep observables. Don't call ButtonThread methods inside doLightSleep. Instead, handle in class with new lightsleep Observables.

* feat: BluetoothStatus
Adds a meshtastic::Status object which exposes the state of the Bluetooth connection. Intends to allow decoupling of UI code.

* feat: observable for reboot

* refactor: Heltec VM-E290 installDefaultConfig

* fix: random Bluetooth pin for NicheGraphics UIs
2025-02-11 16:01:17 -06:00
github-actions[bot]
c756bea711 [create-pull-request] automated change (#6037)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-11 11:30:20 -06:00
Ben Meadors
068d529298 2.6 update 2025-02-11 11:27:30 -06:00
Ben Meadors
add75f073a Littlefs per device 2025-02-10 15:14:08 -06:00
Austin
a00e272afe meshtasticd: Add X11 480x480 preset (#6020) 2025-02-10 14:29:45 -06:00
Manuel
48b00188fb Fix T-Deck SD card detection (#6023)
* increase T-Deck PSRAM to avoid too early out-of-memory when messages fill up the storage

* fix SDCard for T-Deck; allow SPI frequency config
2025-02-10 07:41:37 -06:00
mverch67
62058b650a update device-ui lib 2025-02-10 14:10:43 +01:00
mverch67
147416045a increase T-Deck PSRAM to avoid too early out-of-memory when messages fill up the storage 2025-02-10 13:40:35 +01:00
mverch67
72e991104e Merge branch '2.6' of https://github.com/meshtastic/firmware into 2.6 2025-02-09 22:01:59 +01:00
Manuel
26a0612e37 fixed Indicator touch issue (causing IO expander issues), added more RAM (#6013) 2025-02-08 18:06:03 +01:00
mverch67
6cfa5d8f29 update lib 2025-02-08 17:49:32 +01:00
mverch67
2e21e49144 fixed Indicator touch issue (causing IO expander issues), added more RAM 2025-02-08 17:49:16 +01:00
Tom Fifield
aad9e352b7 Merge branch 'master' into 2.6 2025-02-08 20:04:12 +08:00
Ben Meadors
368d811ea6 Exclude unphone tft for now. Something is wonky 2025-02-07 20:07:54 -06:00
Ben Meadors
2996a9616f Add -tft environments to the ci matrix 2025-02-07 19:07:38 -06:00
rcarteraz
99e47cf73c Merge pull request #6007 from meshtastic/update-indicator-speed-again
Update Indicator upload_speed... again
2025-02-07 15:43:16 -07:00
rcarteraz
102c328436 Merge branch '2.6' into update-indicator-speed-again 2025-02-07 15:41:21 -07:00
Ben Meadors
0cd4224033 Un-extra 2025-02-07 16:35:38 -06:00
rcarteraz
20743ae2c4 Merge branch '2.6' into update-indicator-speed-again 2025-02-07 14:09:05 -07:00
rcarteraz
51c5d8ce93 tested higher speed and it works 2025-02-07 14:02:11 -07:00
rcarteraz
b229d351cf Update platformio.ini (#6006) 2025-02-07 14:32:09 -06:00
Ben Meadors
350b82bc08 Version this 2025-02-07 13:32:48 -06:00
Manuel
d21b272680 fancy T-Deck / SenseCAP Indicator / unPhone / PICOmputer-S3 TFT screen (#3259)
* lib update: light theme

* fix merge issue

* lib update: home buttons + button try-fix

* lib update: icon color fix

* lib update: fix instability/crash on notification

* update lib: timezone

* timezone label

* lib update: fix set owner

* fix spiLock in RadioLibInterface

* add picomputer tft build

* picomputer build

* fix compiler error std::find()

* fix merge

* lib update: theme runtime config

* lib update: packet logger + T-Deck Plus

* lib update: mesh detector

* lib update: fix brightness & trackball crash

* try-fix less paranoia

* sensecap indicator updates

* lib update: indicator fix

* lib update: statistic & some fixes

* lib-update: other T-Deck touch driver

* use custom touch driver for Indicator

* lower tft task prio

* prepare LVGL ST7789 driver

* lib update: try-fix audio

* Drop received packets from self

* Additional decoded packet ignores

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

* Honor TFT_MESH color if defined for Heltec T114 or T190

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

* Update lib_deps; tidy preprocessor logic

* Download debian files after firmware zip

* set title for protobufs bump PR (#4792)

* set title for version bump PR (#4791)

* Enable Dependabot

* chore: trunk fmt

* fix dependabot syntax (#4795)

* fix dependabot syntax

* Update dependabot.yml

* Update dependabot.yml

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

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

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

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

Patch updates our CI workflow for these images.

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

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

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

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

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

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

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

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

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

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

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

* Raspberry Pico2 - needs protos

* Re-order doDeepSleep (#4802)

Make sure PMU sleep takes place before I2C ends

* [create-pull-request] automated change

* heltec-wireless-bridge
requires Proto PR first

* feat: trigger class update when protobufs are changed

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

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

* Hopefully extract and commit to meshtastic.github.io

* CI fixes

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

* New variant "t-energy-s3_e22"

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

* add board_level = extra

* Update variant.h

---------

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

* Consolidate variant build steps (#4806)

* poc: consolidate variant build steps

* use build-variant action

* only checkout once and clean up after run

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

This reverts commit 9f8d86cb25.

* Make Ublox code more readable (#4727)

* Simplify Ublox code

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

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

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

* Make trunk happy

* Make trunk happy

---------

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

* Consider the LoRa header when checking packet length

* Minor fix (#4666)

* Minor fixes

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

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

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

* Update PortduinoGlue.cpp

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

* Revert "Update classes on protobufs update"

* remove quotes to fix trunk.

---------

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

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

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

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

* Tidier macros

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

* Refactor and consolidate time window logic (#4826)

* Refactor and consolidate windowing logic

* Trunk

* Fixes

* More

* Fix braces and remove unused now variables.

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

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

Move throttle include in WifiAPClient.cpp to top.

Add Default.h to sleep.cpp

rest of files just remove unused now variables.

* Remove a couple more meows

---------

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

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

* Rename message length headers and set payload max to 255

* Add MESHTASTIC_PKC_OVERHEAD

* compare to MESHTASTIC_HEADER_LENGTH

---------

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

* Check for null before printing debug (#4835)

* fix merge

* try-fix crash

* lib update: fix neighbors

* fix GPIO0 mode after I2S audio

* lib update: audio fix

* lib update: fixes and improvements

* extra

* added ILI9342 (from master)

* device-ui persistency

* review update

* fix request, add handled

* fix merge issue

* fix merge issue

* remove newline

* remove newlines from debug log

* playing with locks; but needs more testing

* diy mesh-tab initial files

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

* use DISPLAY_SET_RESOLUTION to avoid hw dependency in code

* no telemetry for Indicator

* 16MB partition for Indicator

* 8MB partition for Indicator

* stability: add SPI lock before saving via littleFS

* dummy for config transfer (#5154)

* update indicator (due to compile and linker errors)

* remove faulty partition line

* fix missing include

* update indicator board

* update mesh-tab ILI9143 TFT

* fix naming

* mesh-tab targets

* try: disable duplicate locks

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

* upgrade toolchain for nrf52 to gcc 9.3.1

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

* update mesh-tab tft configs

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

* swap mclk to gpio 21

* update meshtab voltage divider

* update mesh-tab ini

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

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

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

* rotation fix; added ST7789 3.2" display

* dreamcatcher: assign GPIO44 to audio mclk

* mesh-tab touch updates

* add mesh-tab powersave as default

* fix DIO1 wakeup

* mesh-tab: enable alert message menu

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

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

* add point-of-checkin

* use board/unphone.json

---------

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

* fix native targets

* add RadioLib debugging options for (T-Deck)

* fix T-Deck build

* fix native tft targets for rpi

* remove wrong debug defines

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

* disable default lightsleep for indicator

* Windows Support - Trunk and Platformio (#5397)

* Add support for GPG

* Add usb device support

* Add trunk.io to devcontainer

* Trunk things

* trunk fmt

* formatting

* fix trivy/DS002, checkov/CKV_DOCKER_3

* hide docker extension popup

* fix trivy/DS026, checkov/CKV_DOCKER_2

* fix radioLib warnings for T-Deck target

* wake screen with button only

* use custom touch driver

* define wake button for unphone

* use board definition for mesh-tab

* mesh-tab rotation upside-down

* update platform native

* use MESH_TAB hardware model definition

* radioLib update (fix crash/assert)

* reference seeed indicator fix commit arduino-esp32

* Remove unneeded file change :)

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

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

* mesh-tab: lower I2C touch frequency

* log error when packet queue is full

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

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

---------

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

* T-Deck: revert back to lovyanGFX touch driver

* T-Deck: increase allocated PSRAM by 50%

* mesh-tab: streamline target definitions

* update RadioLib 7.1.2

* mesh-tab: fix touch rotation 4.0 inch display

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

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

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

* restructure device-ui library into sub-directories

* preparations for generic DisplayDriverFactory

* T-Deck: increase LVGL memory size

* update lib

* trunk fmt

---------

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

* Set original hop limit in header flags

* Short-circuit to FloodingRouter for broadcasts

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

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

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

* temp

* Add 1 retransmission for intermediate hops when using NextHopRouter

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

* Update protos, store multiple relayers

* Remove next-hop update logic from NeighborInfoModule

* Fix retransmissions

* Improve ACKs for repeated packets and responses

* Stop retransmission even if there's not relay node

* Revert perhapsRebroadcast()

* Remove relayer if we cancel a transmission

* Better checking for fallback to flooding

* Fix newlines in traceroute print logs

* Stop retransmission for original packet

* Use relayID

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

* Fix cppcheck error

* Fix 'router' not in scope error

* Fix another cppcheck error

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

* Formatting and correct NUM_RETRANSMISSIONS

* Update protos

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

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

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

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

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

---------

Co-authored-by: GUVWAF <thijs@havinga.eu>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
2025-02-07 06:29:36 -06:00
Ben Meadors
d65d9305d3 Merge branch 'master' into 2.6 2025-02-07 06:03:13 -06:00
github-actions[bot]
34e5cf0d96 [create-pull-request] automated change (#6002)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-06 20:26:27 -06:00
Ben Meadors
b28db07409 Back to 80 2025-02-06 20:01:18 -06:00
Ben Meadors
f753caf15d Update ref 2025-02-06 20:00:56 -06:00
github-actions[bot]
0c1838dde7 [create-pull-request] automated change (#6000)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-02-06 14:55:39 -06:00
Ben Meadors
b183febd3c Merge remote-tracking branch 'origin/master' into 2.6 2025-02-06 14:20:46 -06:00
Ben Meadors
87601f4760 File system persistence fixes 2025-01-12 19:03:21 -06:00
GUVWAF
0e3c419652 Explicitly set CAD symbols, improve slot time calculation and adjust CW size accordingly (#5772) 2025-01-12 17:52:35 +01:00
Ben Meadors
891bf643e2 Remove logging statement no longer needed 2025-01-12 09:13:14 -06:00
Ben Meadors
143cdf4572 Make NodeDatabase (and file) independent of DeviceState (#5813)
* Make NodeDatabase (and file) independent of DeviceState

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

* Logs

* Comment

* Update UdpMulticastThread.h

* Changes

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

View File

@@ -29,11 +29,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
gpg \ gpg \
gnupg2 \ gnupg2 \
libusb-1.0-0-dev \ libusb-1.0-0-dev \
libuv1-dev \
libi2c-dev \ libi2c-dev \
libxcb-xkb-dev \
libxkbcommon-dev \
libinput-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pipx install platformio RUN pipx install platformio

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env sh #!/usr/bin/env sh
git submodule update --init git submodule update --init
pip install --no-cache-dir setuptools
pipx install esptool

5
.gitattributes vendored
View File

@@ -1,5 +1,4 @@
* text=auto eol=lf * text=auto eol=lf
*.cmd text eol=crlf *.{cmd,[cC][mM][dD]} text eol=crlf
*.bat text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf
*.ps1 text eol=crlf
*.{sh,[sS][hH]} text eol=lf *.{sh,[sS][hH]} text eol=lf

View File

@@ -20,7 +20,7 @@ runs:
shell: bash shell: bash
run: | run: |
sudo apt-get -y update --fix-missing sudo apt-get -y update --fix-missing
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libuv1-dev lsb-release sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5

View File

@@ -11,4 +11,4 @@ runs:
- name: Install libs needed for native build - name: Install libs needed for native build
shell: bash shell: bash
run: | run: |
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev

View File

@@ -19,8 +19,6 @@ updates:
interval: daily interval: daily
time: "05:00" time: "05:00"
timezone: US/Pacific timezone: US/Pacific
ignore:
- dependency-name: protobufs
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: /.github/workflows directory: /.github/workflows
schedule: schedule:

View File

@@ -4,7 +4,7 @@ on:
workflow_call: workflow_call:
secrets: secrets:
PPA_GPG_PRIVATE_KEY: PPA_GPG_PRIVATE_KEY:
required: false required: true
inputs: inputs:
series: series:
description: Ubuntu/Debian series to target description: Ubuntu/Debian series to target

View File

@@ -136,7 +136,6 @@ jobs:
secrets: inherit secrets: inherit
package-pio-deps-native-tft: package-pio-deps-native-tft:
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml uses: ./.github/workflows/package_pio_deps.yml
with: with:
pio_env: native-tft pio_env: native-tft
@@ -330,13 +329,13 @@ jobs:
with: with:
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }} pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
merge-multiple: true merge-multiple: true
path: ./output/pio-deps-native-tft path: ./output/pio-deps-native
- name: Zip linux sources - name: Zip linux sources
working-directory: output working-directory: output
run: | run: |
zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src zip -j -9 -r ./meshtasticd-${{ steps.version.outputs.deb }}-src.zip ./debian-src
zip -9 -r ./platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip ./pio-deps-native-tft zip -9 -r ./platformio-deps-native-${{ steps.version.outputs.long }}.zip ./pio-deps-native
# For diagnostics # For diagnostics
- name: Display structure of downloaded files - name: Display structure of downloaded files
@@ -345,10 +344,32 @@ jobs:
- name: Add linux sources to release - name: Add linux sources to release
run: | run: |
gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip gh release upload v${{ steps.version.outputs.long }} ./output/meshtasticd-${{ steps.version.outputs.deb }}-src.zip
gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-tft-${{ steps.version.outputs.long }}.zip gh release upload v${{ steps.version.outputs.long }} ./output/platformio-deps-native-${{ steps.version.outputs.long }}.zip
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Bump version.properties
run: >-
bin/bump_version.py
- name: Ensure debian deps are installed
shell: bash
run: |
sudo apt-get update -y --fix-missing
sudo apt-get install -y devscripts
- name: Update debian changelog
run: >-
debian/ci_changelog.sh
- name: Create version.properties pull request
uses: peter-evans/create-pull-request@v7
with:
title: Bump version.properties
add-paths: |
version.properties
debian/changelog
release-firmware: release-firmware:
strategy: strategy:
fail-fast: false fail-fast: false

View File

@@ -43,49 +43,3 @@ jobs:
copr_project: |- copr_project: |-
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }} ${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
secrets: inherit secrets: inherit
# Create a PR to bump version when a release is Published
bump-version:
if: ${{ github.event.release.published }}
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.x
- 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
- name: Bump version.properties
run: >-
bin/bump_version.py
- name: Ensure debian deps are installed
shell: bash
run: |
sudo apt-get update -y --fix-missing
sudo apt-get install -y devscripts
- name: Update debian changelog
run: >-
debian/ci_changelog.sh
- name: Create version.properties pull request
uses: peter-evans/create-pull-request@v7
with:
title: Bump version.properties
add-paths: |
version.properties
debian/changelog

View File

@@ -6,14 +6,11 @@ on:
schedule: schedule:
- cron: 0 1 * * 6 - cron: 0 1 * * 6
permissions: permissions: read-all
actions: read
contents: read
security-events: write
jobs: jobs:
semgrep-full: semgrep-full:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
container: container:
image: semgrep/semgrep image: semgrep/semgrep

View File

@@ -18,6 +18,5 @@ jobs:
- name: Stale PR+Issues - name: Stale PR+Issues
uses: actions/stale@v9.1.0 uses: actions/stale@v9.1.0
with: with:
days-before-stale: 45
exempt-issue-labels: pinned,3.0 exempt-issue-labels: pinned,3.0
exempt-pr-labels: pinned,3.0 exempt-pr-labels: pinned,3.0

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Test Report - name: Test Report
uses: dorny/test-reporter@v2.0.0 uses: dorny/test-reporter@v1.9.1
with: with:
name: PlatformIO Tests name: PlatformIO Tests
path: testreport.xml path: testreport.xml

3
.gitmodules vendored
View File

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

View File

@@ -1,6 +1,6 @@
version: 0.1 version: 0.1
cli: cli:
version: 1.22.11 version: 1.22.10
plugins: plugins:
sources: sources:
- id: trunk - id: trunk
@@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins uri: https://github.com/trunk-io/plugins
lint: lint:
enabled: enabled:
- prettier@3.5.3 - prettier@3.5.2
- trufflehog@3.88.17 - trufflehog@3.88.14
- yamllint@1.36.0 - yamllint@1.35.1
- bandit@1.8.3 - bandit@1.8.3
- checkov@3.2.386 - checkov@3.2.378
- terrascan@1.19.9 - terrascan@1.19.9
- trivy@0.60.0 - trivy@0.59.1
- taplo@0.9.3 - taplo@0.9.3
- ruff@0.10.0 - ruff@0.9.7
- isort@6.0.1 - isort@6.0.1
- markdownlint@0.44.0 - markdownlint@0.44.0
- oxipng@9.1.4 - oxipng@9.1.4

View File

@@ -7,8 +7,5 @@
"cmake.configureOnOpen": false, "cmake.configureOnOpen": false,
"[cpp]": { "[cpp]": {
"editor.defaultFormatter": "trunk.io" "editor.defaultFormatter": "trunk.io"
},
"[powershell]": {
"editor.defaultFormatter": "ms-vscode.powershell"
} }
} }

View File

@@ -13,7 +13,7 @@ ENV TZ=Etc/UTC
ENV PIP_ROOT_USER_ACTION=ignore ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apt-get update && apt-get install --no-install-recommends -y \
wget g++ zip git ca-certificates \ wget g++ zip git ca-certificates \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \ libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \ && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \ && pip install --no-cache-dir -U platformio \
@@ -38,7 +38,7 @@ ENV TZ=Etc/UTC
USER root USER root
RUN apt-get update && apt-get --no-install-recommends -y install \ RUN apt-get update && apt-get --no-install-recommends -y install \
libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libuv1 libusb-1.0-0-dev liborcania2.3 libulfius2.7 libssl3 \ libc-bin libc6 libgpiod2 libyaml-cpp0.7 libi2c0 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \ && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/lib/meshtasticd \ && mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \ && mkdir -p /etc/meshtasticd/config.d \

View File

@@ -9,7 +9,7 @@ FROM python:3.13-alpine3.21 AS builder
ENV PIP_ROOT_USER_ACTION=ignore ENV PIP_ROOT_USER_ACTION=ignore
RUN apk --no-cache add \ 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 libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \ libusb-dev i2c-tools-dev openssl-dev pkgconf argp-standalone \
&& rm -rf /var/cache/apk/* \ && rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \ && pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware && mkdir /tmp/firmware
@@ -32,7 +32,7 @@ FROM alpine:3.21
USER root USER root
RUN apk --no-cache add \ RUN apk --no-cache add \
libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \ libstdc++ libgpiod yaml-cpp libusb i2c-tools \
&& rm -rf /var/cache/apk/* \ && rm -rf /var/cache/apk/* \
&& mkdir -p /var/lib/meshtasticd \ && mkdir -p /var/lib/meshtasticd \
&& mkdir -p /etc/meshtasticd/config.d \ && mkdir -p /etc/meshtasticd/config.d \

View File

@@ -1,6 +1,6 @@
; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated). ; The Portduino based 'native' environment. Currently supported on Linux targets with real LoRa hardware (or simulated).
[portduino_base] [portduino_base]
platform = https://github.com/Jorropo/platform-native.git#17fa89daec4402af491512f75278a7fec8a5818c platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9
framework = arduino framework = arduino
build_src_filter = build_src_filter =
@@ -34,12 +34,10 @@ build_flags =
-Isrc/platform/portduino -Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED -DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE -DPORTDUINO_LINUX_HARDWARE
-DHAS_UDP_MULTICAST
-lpthread -lpthread
-lstdc++fs -lstdc++fs
-lbluetooth -lbluetooth
-lgpiod -lgpiod
-lyaml-cpp -lyaml-cpp
-li2c -li2c
-luv
-std=c++17 -std=c++17

View File

@@ -1,8 +1,8 @@
; Common settings for rp2040 Processor based targets ; Common settings for rp2040 Processor based targets
[rp2040_base] [rp2040_base]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#76ecf3c7e9dd4503af0331154c4ca1cddc4b03e5 ; For arduino-pico >= 4.4.3 platform = https://github.com/maxgerhardt/platform-raspberrypi.git#19e30129fb1428b823be585c787dcb4ac0d9014c ; For arduino-pico >=4.2.1
extends = arduino_base extends = arduino_base
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#4.4.3 platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#6024e9a7e82a72e38dd90f42029ba3748835eb2e ; 4.3.0 with fix MDNS
board_build.core = earlephilhower board_build.core = earlephilhower
board_build.filesystem_size = 0.5m board_build.filesystem_size = 0.5m

View File

@@ -1,296 +1,72 @@
@ECHO OFF @ECHO OFF
SETLOCAL EnableDelayedExpansion
TITLE Meshtastic device-install
SET "SCRIPT_NAME=%~nx0" set PYTHON=python
SET "DEBUG=0" set WEB_APP=0
SET "PYTHON="
SET "WEB_APP=0"
SET "TFT_BUILD=0"
SET "TFT8=0"
SET "TFT16=0"
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
GOTO getopts :: Determine the correct esptool command to use
:help where esptool >nul 2>&1
ECHO Flash image file to device, but first erasing and writing system information. if %ERRORLEVEL% EQU 0 (
ECHO. set "ESPTOOL_CMD=esptool"
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web) ) else (
ECHO. set "ESPTOOL_CMD=%PYTHON% -m esptool"
ECHO Options: )
ECHO -f filename The firmware .bin file to flash. Custom to your device type and region. (required)
ECHO The file must be located in this current directory.
ECHO -p PORT Set the environment variable for ESPTOOL_PORT.
ECHO If not set, ESPTOOL iterates all ports (Dangerous).
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
ECHO If supplied the script will use python.
ECHO If not supplied the script will try to find esptool in Path.
ECHO --web Enable WebUI. (default: false)
ECHO.
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4.bin -p COM11
ECHO Example: %SCRIPT_NAME% -f firmware-unphone-2.6.0.0b106d4.bin -p COM11 --web
GOTO eof
:version goto GETOPTS
ECHO %SCRIPT_NAME% [Version 2.6.0] :HELP
ECHO Meshtastic echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME] [--web]
GOTO eof echo Flash image file to device, but first erasing and writing system information
echo.
echo -h Display this help and exit
echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
echo -f FILENAME The .bin file to flash. Custom to your device type and region.
echo --web Flash WEB APP.
goto EOF
:getopts :GETOPTS
IF "%~1"=="" GOTO endopts if /I "%1"=="-h" goto HELP
IF /I "%~1"=="-?" GOTO help if /I "%1"=="--help" goto HELP
IF /I "%~1"=="-h" GOTO help if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
IF /I "%~1"=="--help" GOTO help if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
IF /I "%~1"=="-v" GOTO version if /I "%1"=="-P" set PYTHON=%2 & SHIFT
IF /I "%~1"=="--version" GOTO version if /I "%1"=="--web" set WEB_APP=1 & SHIFT
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
IF /I "%~1"=="--web" SET "WEB_APP=1"
SHIFT SHIFT
GOTO getopts IF NOT "__%1__"=="____" goto GETOPTS
:endopts
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..." IF "__%FILENAME%__" == "____" (
IF "__!FILENAME!__"=="____" ( echo "Missing FILENAME"
CALL :LOG_MESSAGE DEBUG "Missing -f filename input." goto HELP
GOTO help )
) ELSE ( IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!" echo Trying to flash update %FILENAME%, but first erasing and writing system information"
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" ( %ESPTOOL_CMD% --baud 115200 erase_flash
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported." %ESPTOOL_CMD% --baud 115200 write_flash 0x00 %FILENAME%
GOTO help
@REM Account for S3 and C3 board's different OTA partition
IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% (
IF x%FILENAME:esp32c3=%==x%FILENAME% (
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota.bin
) else (
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota-c3.bin
)
) else (
%ESPTOOL_CMD% --baud 115200 write_flash 0x260000 bleota-s3.bin
) )
IF "__!FILENAME:firmware-=!__"=="__!FILENAME!__" ( IF %WEB_APP%==1 (
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file." for %%f in (littlefswebui-*.bin) do (
GOTO help %ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
)
) else (
for %%f in (littlefs-*.bin) do (
%ESPTOOL_CMD% --baud 115200 write_flash 0x300000 %%f
)
) )
@REM Remove ".\" or "./" file prefix if present. ) else (
SET "FILENAME=!FILENAME:.\=!" echo "Invalid file: %FILENAME%"
SET "FILENAME=!FILENAME:./=!" goto HELP
) else (
echo "Invalid file: %FILENAME%"
goto HELP
) )
CALL :LOG_MESSAGE DEBUG "Checking if !FILENAME! exists..." :EOF
IF NOT EXIST !FILENAME! (
CALL :LOG_MESSAGE ERROR "File does not exist: !FILENAME!. Terminating."
GOTO eof
)
IF NOT "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
CALL :LOG_MESSAGE INFO "Use script device-update.bat to flash update !FILENAME!."
GOTO eof
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
)
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
) ELSE (
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
WHERE esptool >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
@REM WHERE exits with code 0 if esptool is found.
SET "ESPTOOL_CMD=esptool"
) ELSE (
SET "ESPTOOL_CMD=python -m esptool"
CALL :RESET_ERROR
)
)
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1
IF %ERRORLEVEL% GTR 2 (
@REM esptool exits with code 1 if help is displayed.
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1
GOTO eof
)
IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
SET "ESPTOOL_CMD=REM !ESPTOOL_CMD!"
)
CALL :LOG_MESSAGE DEBUG "Using esptool command: !ESPTOOL_CMD!"
IF "__!ESPTOOL_PORT!__" == "____" (
CALL :LOG_MESSAGE WARN "Using esptool port: UNSET."
) ELSE (
CALL :LOG_MESSAGE INFO "Using esptool port: !ESPTOOL_PORT!."
)
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
@REM Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
@REM https://github.com/meshtastic/web-flasher/blob/main/types/resources.ts#L3
IF NOT "!FILENAME:-tft-=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are working with a *-tft-* file. !FILENAME!"
IF %WEB_APP% EQU 1 (
CALL :LOG_MESSAGE ERROR "Cannot enable WebUI (--web) and MUI." & GOTO eof
)
SET "TFT_BUILD=1"
GOTO tft
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *-tft-* file. !FILENAME!"
GOTO no_tft
)
:tft
SET "TFT8MB=picomputer-s3 unphone seeed-sensecap-indicator"
FOR %%a IN (%TFT8MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %TFT8MB%.
SET "TFT8=1"
GOTO end_loop_tft8mb
)
)
:end_loop_tft8mb
SET "TFT16MB=t-deck"
FOR %%a IN (%TFT16MB%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %TFT16MB%.
SET "TFT16=1"
GOTO end_loop_tft16mb
)
)
:end_loop_tft16mb
IF %TFT8% EQU 1 CALL :LOG_MESSAGE INFO "tft and MUI 8mb selected."
IF %TFT16% EQU 1 CALL :LOG_MESSAGE INFO "tft and MUI 16mb selected."
:no_tft
@REM Extract BASENAME from %FILENAME% for later use.
SET "BASENAME=!FILENAME:firmware-=!"
CALL :LOG_MESSAGE DEBUG "Computed firmware basename: !BASENAME!"
@REM Account for S3 and C3 board's different OTA partition.
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone"
FOR %%a IN (%S3%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %S3%.
SET "OTA_FILENAME=bleota-s3.bin"
GOTO :end_loop_s3
)
)
SET "C3=esp32c3"
FOR %%a IN (%C3%) DO (
IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
@REM We are working with any of %C3%.
SET "OTA_FILENAME=bleota-c3.bin"
GOTO :end_loop_c3
)
)
@REM Everything else
SET "OTA_FILENAME=bleota.bin"
:end_loop_s3
:end_loop_c3
CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!"
@REM Check if (--web) is enabled and prefix BASENAME with "littlefswebui-" else "littlefs-".
IF %WEB_APP% EQU 1 (
CALL :LOG_MESSAGE INFO "WebUI selected."
SET "SPIFFS_FILENAME=littlefswebui-%BASENAME%"
) ELSE (
SET "SPIFFS_FILENAME=littlefs-%BASENAME%"
)
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_FILENAME to: !SPIFFS_FILENAME!"
@REM Default offsets.
@REM https://github.com/meshtastic/web-flasher/blob/main/stores/firmwareStore.ts#L202
SET "OTA_OFFSET=0x260000"
SET "SPIFFS_OFFSET=0x300000"
@REM Offsets for MUI 8mb.
IF %TFT8% EQU 1 IF %TFT_BUILD% EQU 1 (
SET "OTA_OFFSET=0x340000"
SET "SPIFFS_OFFSET=0x670000"
)
@REM Offsets for MUI 16mb.
IF %TFT16% EQU 1 IF %TFT_BUILD% EQU 1 (
SET "OTA_OFFSET=0x650000"
SET "SPIFFS_OFFSET=0xc90000"
)
CALL :LOG_MESSAGE DEBUG "Set OTA_OFFSET to: !OTA_OFFSET!"
CALL :LOG_MESSAGE DEBUG "Set SPIFFS_OFFSET to: !SPIFFS_OFFSET!"
@REM Ensure target files exist before flashing operations.
IF NOT EXIST !FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
IF NOT EXIST !OTA_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!OTA_FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
IF NOT EXIST !SPIFFS_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!SPIFFS_FILENAME!". Terminating." & EXIT /B 2 & GOTO eof
@REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash "!FILENAME!", but first erasing and writing system information..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase_flash || GOTO eof
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x00 "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Trying to flash BLEOTA "!OTA_FILENAME!" at OTA_OFFSET !OTA_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Trying to flash SPIFFS "!SPIFFS_FILENAME!" at SPIFFS_OFFSET !SPIFFS_OFFSET!..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Script complete!."
:eof
ENDLOCAL
EXIT /B %ERRORLEVEL%
:RUN_ESPTOOL
@REM Subroutine used to run ESPTOOL_CMD with arguments.
@REM Also handles %ERRORLEVEL%.
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
@REM.
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
IF %ERRORLEVEL% NEQ 0 (
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
EXIT /B %ERRORLEVEL%
)
GOTO :eof
:LOG_MESSAGE
@REM Subroutine used to print log messages in four different levels.
@REM DEBUG messages only get printed if [-d] flag is passed to script.
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
@REM.
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
SET /A LOGCOUNTER=LOGCOUNTER+1
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
GOTO :eof
:GET_TIMESTAMP
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
@REM CALL :GET_TIMESTAMP
@REM.
@REM Updates: !TIMESTAMP!
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
SET "HH=%%a"
SET "MM=%%b"
SET "ss=%%c"
)
SET "TIMESTAMP=!HH!:!MM!:!ss!"
GOTO :eof
:RESET_ERROR
@REM Subroutine to reset %ERRORLEVEL% to 0.
@REM CALL :RESET_ERROR
@REM.
@REM Updates: %ERRORLEVEL%
EXIT /B 0
GOTO :eof

View File

@@ -1,21 +1,18 @@
#!/bin/bash #!/bin/sh
PYTHON=${PYTHON:-$(which python3 python | head -n 1)} PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
WEB_APP=false WEB_APP=false
TFT8=false
TFT16=false
TFT_BUILD=false
# Determine the correct esptool command to use # Determine the correct esptool command to use
if "$PYTHON" -m esptool version >/dev/null 2>&1; then if "$PYTHON" -m esptool version >/dev/null 2>&1; then
ESPTOOL_CMD="$PYTHON -m esptool" ESPTOOL_CMD="$PYTHON -m esptool"
elif command -v esptool >/dev/null 2>&1; then elif command -v esptool >/dev/null 2>&1; then
ESPTOOL_CMD="esptool" ESPTOOL_CMD="esptool"
elif command -v esptool.py >/dev/null 2>&1; then elif command -v esptool.py >/dev/null 2>&1; then
ESPTOOL_CMD="esptool.py" ESPTOOL_CMD="esptool.py"
else else
echo "Error: esptool not found" echo "Error: esptool not found"
exit 1 exit 1
fi fi
set -e set -e
@@ -23,138 +20,75 @@ set -e
# Usage info # Usage info
show_help() { show_help() {
cat <<EOF cat <<EOF
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web] Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME|FILENAME] [--web]
Flash image file to device, but first erasing and writing system information. Flash image file to device, but first erasing and writing system information"
-h Display this help and exit. -h Display this help and exit
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous). -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON") -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
-f FILENAME The firmware .bin file to flash. Custom to your device type and region. -f FILENAME The .bin file to flash. Custom to your device type and region.
--web Enable WebUI. (Default: false) --web Flash WEB APP.
EOF EOF
} }
# Parse arguments using a single while loop # Preprocess long options like --web
while [ $# -gt 0 ]; do for arg in "$@"; do
case "$1" in case "$arg" in
-h | --help) --web)
WEB_APP=true
shift # Remove this argument from the list
;;
esac
done
while getopts ":hp:P:f:" opt; do
case "${opt}" in
h)
show_help show_help
exit 0 exit 0
;; ;;
-p) p)
ESPTOOL_PORT="$2" export ESPTOOL_PORT=${OPTARG}
shift # Shift past the option argument
;; ;;
-P) P)
PYTHON="$2" PYTHON=${OPTARG}
shift
;; ;;
-f) f)
FILENAME="$2" FILENAME=${OPTARG}
shift
;;
--web)
WEB_APP=true
;;
--) # Stop parsing options
shift
break
;; ;;
*) *)
echo "Unknown argument: $1" >&2 echo "Invalid flag."
show_help >&2
exit 1 exit 1
;; ;;
esac esac
shift # Move to the next argument
done done
shift "$((OPTIND - 1))"
[ -z "$FILENAME" -a -n "$1" ] && { [ -z "$FILENAME" -a -n "$1" ] && {
FILENAME=$1 FILENAME=$1
shift shift
} }
if [[ $FILENAME != firmware-* ]]; then
echo "Filename must be a firmware-* file."
exit 1
fi
# Check if FILENAME contains "-tft-" and set target partitionScheme accordingly.
if [[ ${FILENAME//-tft-/} != "$FILENAME" ]]; then
TFT_BUILD=true
if [[ $WEB_APP == true ]] && [[ $TFT_BUILD == true ]]; then
echo "Cannot enable WebUI (--web) and MUI."
exit 1
fi
if [[ $FILENAME == *"picomputer-s3"* || $FILENAME == *"unphone"* || $FILENAME == *"seeed-sensecap-indicator"* ]]; then
TFT8=true
fi
if [[ $FILENAME == *"t-deck"* ]]; then
TFT16=true
fi
fi
# Extract BASENAME from %FILENAME% for later use.
BASENAME="${FILENAME/firmware-/}"
if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
# Default littlefs* offset (--web).
OFFSET=0x300000
# Default OTA Offset
OTA_OFFSET=0x260000
# littlefs* offset for MUI 8mb and OTA OFFSET.
if [ "$TFT8" = true ] && [ "$TFT_BUILD" = true ]; then
OFFSET=0x670000
OTA_OFFSET=0x340000
fi
# littlefs* offset for MUI 16mb and OTA OFFSET.
if [ "$TFT16" = true ] && [ "$TFT_BUILD" = true ]; then
OFFSET=0xc90000
OTA_OFFSET=0x650000
fi
# Account for S3 board's different OTA partition
if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
if [ -n "${FILENAME##*"esp32c3"*}" ]; then
OTAFILE=bleota.bin
else
OTAFILE=bleota-c3.bin
fi
else
OTAFILE=bleota-s3.bin
fi
# Check if WEB_APP (--web) is enabled and add "littlefswebui-" to BASENAME else "littlefs-".
if [ "$WEB_APP" = true ]; then
SPIFFSFILE=littlefswebui-${BASENAME}
else
SPIFFSFILE=littlefs-${BASENAME}
fi
if [[ ! -f $FILENAME ]]; then
echo "Error: file ${FILENAME} wasn't found. Terminating."
exit 1
fi
if [[ ! -f $OTAFILE ]]; then
echo "Error: file ${OTAFILE} wasn't found. Terminating."
exit 1
fi
if [[ ! -f $SPIFFSFILE ]]; then
echo "Error: file ${SPIFFSFILE} wasn't found. Terminating."
exit 1
fi
echo "Trying to flash ${FILENAME}, but first erasing and writing system information" echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD erase_flash $ESPTOOL_CMD erase_flash
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}" $ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" # Account for S3 board's different OTA partition
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" if [ -n "${FILENAME##*"esp32c3"*}" ]; then
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" $ESPTOOL_CMD write_flash 0x260000 bleota.bin
else
$ESPTOOL_CMD write_flash 0x260000 bleota-c3.bin
fi
else
$ESPTOOL_CMD write_flash 0x260000 bleota-s3.bin
fi
if [ "$WEB_APP" = true ]; then
$ESPTOOL_CMD write_flash 0x300000 littlefswebui-*.bin
else
$ESPTOOL_CMD write_flash 0x300000 littlefs-*.bin
fi
else else
show_help show_help

View File

@@ -1,112 +0,0 @@
<#
.SYNOPSIS
Unit-test for .\device-install.bat.
.DESCRIPTION
This script performs a positive unit-test on .\device-install.bat by creating the expected .bin
files for a device followed by running the .bat script without flashing the firmware (--debug).
If any errors are hit they are presented in the standard output. Investigate accordingly.
This script needs to be placed in the same directory as .\device-install.bat.
.EXAMPLE
.\device-install_test.ps1
.EXAMPLE
.\device-install_test.ps1 -Verbose
.LINK
.\device-install.bat --help
#>
[CmdletBinding()]
param()
function New-EmptyFile() {
[CmdletBinding()]
param (
[Parameter(Position = 0, Mandatory = $true)]
# Specifies the file name.
[string]$FileName,
[Parameter(Position = 1)]
# Specifies the target path. (Get-Location).Path is the default.
[string]$Directory = (Get-Location).Path
)
$filePath = Join-Path -Path $Directory -ChildPath $FileName
Write-Verbose -Message "Create empty test file if it doesn't exist: $($FileName)"
New-Item -Path "$filePath" -ItemType File -ErrorAction SilentlyContinue | Out-Null
}
function Remove-EmptyFile() {
[CmdletBinding()]
param (
[Parameter(Position = 0, Mandatory = $true)]
# Specifies the file name.
[string]$FileName,
[Parameter(Position = 1)]
# Specifies the target path. (Get-Location).Path is the default.
[string]$Directory = (Get-Location).Path
)
$filePath = Join-Path -Path $Directory -ChildPath $FileName
Write-Verbose -Message "Deleted empty test file: $($FileName)"
Remove-Item -Path "$filePath" | Out-Null
}
$TestCases = New-Object -TypeName PSObject -Property @{
# Use this PSObject to define testcases according to this syntax:
# "testname" = @("firmware-testname","bleota","littlefs-testname","args")
"t-deck" = @("firmware-t-deck-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-t-deck-2.6.0.0b106d4.bin", "")
"t-deck_web" = @("firmware-t-deck-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefswebui-t-deck-2.6.0.0b106d4.bin", "--web")
"t-deck-tft" = @("firmware-t-deck-tft-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-t-deck-tft-2.6.0.0b106d4.bin", "")
"heltec-ht62-esp32c3" = @("firmware-heltec-ht62-esp32c3-sx1262-2.6.0.0b106d4.bin", "bleota-c3.bin", "littlefs-heltec-ht62-esp32c3-sx1262-2.6.0.0b106d4.bin", "")
"tlora-c6" = @("firmware-tlora-c6-2.6.0.0b106d4.bin", "bleota.bin", "littlefs-tlora-c6-2.6.0.0b106d4.bin", "")
"heltec-v3_web" = @("firmware-heltec-v3-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefswebui-heltec-v3-2.6.0.0b106d4.bin", "--web")
"seeed-sensecap-indicator-tft" = @("firmware-seeed-sensecap-indicator-tft-2.6.0.0b106d4.bin", "bleota.bin", "littlefs-seeed-sensecap-indicator-tft-2.6.0.0b106d4.bin", "")
"picomputer-s3-tft" = @("firmware-picomputer-s3-tft-2.6.0.0b106d4.bin", "bleota-s3.bin", "littlefs-picomputer-s3-tft-2.6.0.0b106d4.bin", "")
}
foreach ($TestCase in $TestCases.PSObject.Properties) {
$Name = $TestCase.Name
$Files = $TestCase.Value
$Errors = $null
$Counter = 0
Write-Host -Object "Testcase: $Name`:" -ForegroundColor Green
foreach ($File in $Files) {
if ($File.EndsWith(".bin")) {
New-EmptyFile -FileName $File
}
}
Write-Host -Object "Performing test on $Name..." -ForegroundColor Blue
$Test = Invoke-Expression -Command "cmd /c .\device-install.bat --debug -f $($TestCases."$Name"[0]) $($TestCases."$Name"[3])"
foreach ($Line in $Test) {
if ($Line -match "Set OTA_OFFSET to" -or `
$Line -match "Set SPIFFS_OFFSET to") {
Write-Host -Object "$($Line -replace "^.*?Set","Set")" -ForegroundColor Blue
}
elseif ($VerbosePreference -eq "Continue") {
Write-Host -Object $Line
}
if ($Line -match "ERROR") {
$Errors += $Line
$Counter++
}
}
if ($null -ne $Errors) {
Write-Host -Object "$Counter ERROR(s) detected!" -ForegroundColor Red
if (-not ($VerbosePreference -eq "Continue")) { Write-Host -Object $Errors }
}
foreach ($File in $Files) {
if ($File.EndsWith(".bin")) {
Remove-EmptyFile -FileName $File
}
}
}

View File

@@ -1,175 +1,48 @@
@ECHO OFF @ECHO OFF
SETLOCAL EnableDelayedExpansion
TITLE Meshtastic device-update
SET "SCRIPT_NAME=%~nx0" set PYTHON=python
SET "DEBUG=0"
SET "PYTHON="
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
GOTO getopts :: Determine the correct esptool command to use
:help where esptool >nul 2>&1
ECHO Flash image file to device, but leave existing system intact. if %ERRORLEVEL% EQU 0 (
ECHO. set "ESPTOOL_CMD=esptool"
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] ) else (
ECHO. set "ESPTOOL_CMD=%PYTHON% -m esptool"
ECHO Options: )
ECHO -f filename The .bin file to flash. Custom to your device type and region. (required)
ECHO The file must be located in this current directory.
ECHO -p PORT Set the environment variable for ESPTOOL_PORT.
ECHO If not set, ESPTOOL iterates all ports (Dangerous).
ECHO -P python Specify alternate python interpreter to use to invoke esptool. (default: python)
ECHO If supplied the script will use python.
ECHO If not supplied the script will try to find esptool in Path.
ECHO.
ECHO Example: %SCRIPT_NAME% -f firmware-t-deck-tft-2.6.0.0b106d4-update.bin -p COM11
GOTO eof
:version goto GETOPTS
ECHO %SCRIPT_NAME% [Version 2.6.0] :HELP
ECHO Meshtastic echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
GOTO eof echo Flash image file to device, leave existing system intact.
echo.
echo -h Display this help and exit
echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
echo -f FILENAME The *update.bin file to flash. Custom to your device type.
goto EOF
:getopts :GETOPTS
IF "%~1"=="" GOTO endopts if /I "%1"=="-h" goto HELP
IF /I "%~1"=="-?" GOTO help if /I "%1"=="--help" goto HELP
IF /I "%~1"=="-h" GOTO help if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
IF /I "%~1"=="--help" GOTO help if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
IF /I "%~1"=="-v" GOTO version if /I "%1"=="-P" set PYTHON=%2 & SHIFT
IF /I "%~1"=="--version" GOTO version
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
IF /I "%~1"=="-f" SET "FILENAME=%~2" & SHIFT
IF "%~1"=="-p" SET "ESPTOOL_PORT=%~2" & SHIFT
IF /I "%~1"=="--port" SET "ESPTOOL_PORT=%~2" & SHIFT
IF "%~1"=="-P" SET "PYTHON=%~2" & SHIFT
SHIFT SHIFT
GOTO getopts IF NOT "__%1__"=="____" goto GETOPTS
:endopts
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..." IF "__%FILENAME%__" == "____" (
IF "__!FILENAME!__"=="____" ( echo "Missing FILENAME"
CALL :LOG_MESSAGE DEBUG "Missing -f filename input." goto HELP
GOTO help )
) ELSE ( IF EXIST %FILENAME% IF NOT x%FILENAME:update=%==x%FILENAME% (
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" ( echo Trying to flash update %FILENAME%
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported." %ESPTOOL_CMD% --baud 115200 write_flash 0x10000 %FILENAME%
GOTO help ) else (
) echo "Invalid file: %FILENAME%"
@REM Remove ".\" or "./" file prefix if present. goto HELP
SET "FILENAME=!FILENAME:.\=!" ) else (
SET "FILENAME=!FILENAME:./=!" echo "Invalid file: %FILENAME%"
goto HELP
) )
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!" :EOF
CALL :LOG_MESSAGE DEBUG "Checking if !FILENAME! exists..."
IF NOT EXIST !FILENAME! (
CALL :LOG_MESSAGE ERROR "File does not exist: !FILENAME!. Terminating."
GOTO eof
)
IF "!FILENAME:update=!"=="!FILENAME!" (
CALL :LOG_MESSAGE DEBUG "We are NOT working with a *update* file. !FILENAME!"
CALL :LOG_MESSAGE INFO "Use script device-install.bat to flash update !FILENAME!."
GOTO eof
) ELSE (
CALL :LOG_MESSAGE DEBUG "We are working with a *update* file. !FILENAME!"
)
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" (
SET "ESPTOOL_CMD=!PYTHON! -m esptool"
CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
) ELSE (
CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
WHERE esptool >nul 2>&1
IF %ERRORLEVEL% EQU 0 (
@REM WHERE exits with code 0 if esptool is found.
SET "ESPTOOL_CMD=esptool"
) ELSE (
SET "ESPTOOL_CMD=python -m esptool"
CALL :RESET_ERROR
)
)
CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
!ESPTOOL_CMD! >nul 2>&1
IF %ERRORLEVEL% GTR 2 (
@REM esptool exits with code 1 if help is displayed.
CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
EXIT /B 1
GOTO eof
)
IF %DEBUG% EQU 1 (
CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
SET "ESPTOOL_CMD=REM !ESPTOOL_CMD!"
)
CALL :LOG_MESSAGE DEBUG "Using esptool command: !ESPTOOL_CMD!"
IF "__!ESPTOOL_PORT!__" == "____" (
CALL :LOG_MESSAGE WARN "Using esptool port: UNSET."
) ELSE (
CALL :LOG_MESSAGE INFO "Using esptool port: !ESPTOOL_PORT!."
)
CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
@REM Flashing operations.
CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof
CALL :LOG_MESSAGE INFO "Script complete!."
:eof
ENDLOCAL
EXIT /B %ERRORLEVEL%
:RUN_ESPTOOL
@REM Subroutine used to run ESPTOOL_CMD with arguments.
@REM Also handles %ERRORLEVEL%.
@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
@REM.
@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
CALL :RESET_ERROR
!ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
IF %ERRORLEVEL% NEQ 0 (
CALL :LOG_MESSAGE ERROR "Error running command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
EXIT /B %ERRORLEVEL%
)
GOTO :eof
:LOG_MESSAGE
@REM Subroutine used to print log messages in four different levels.
@REM DEBUG messages only get printed if [-d] flag is passed to script.
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
@REM.
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
SET /A LOGCOUNTER=LOGCOUNTER+1
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
GOTO :eof
:GET_TIMESTAMP
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
@REM CALL :GET_TIMESTAMP
@REM.
@REM Updates: !TIMESTAMP!
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
SET "HH=%%a"
SET "MM=%%b"
SET "ss=%%c"
)
SET "TIMESTAMP=!HH!:!MM!:!ss!"
GOTO :eof
:RESET_ERROR
@REM Subroutine to reset %ERRORLEVEL% to 0.
@REM CALL :RESET_ERROR
@REM.
@REM Updates: %ERRORLEVEL%
EXIT /B 0
GOTO :eof

View File

@@ -125,9 +125,4 @@ for flag in flags:
projenv.Append( projenv.Append(
CCFLAGS=flags, CCFLAGS=flags,
) )
for lb in env.GetLibBuilders():
if lb.name == "meshtastic-device-ui":
lb.env.Append(CPPDEFINES=[("APP_VERSION", verObj["long"])])
break

View File

@@ -1,10 +1 @@
@ECHO OFF cd protobufs && ..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
SETLOCAL
cd protobufs
..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
GOTO eof
:eof
ENDLOCAL
EXIT /B %ERRORLEVEL%

View File

@@ -1,124 +1,2 @@
@ECHO OFF @echo off
SETLOCAL EnableDelayedExpansion if [%1]==[] (echo "Please specify a platformio NRF target (i.e. rak4631) as the first argument.") else (python3 .\bin\uf2conv.py .\.pio\build\%1\firmware.hex -c -o .\.pio\build\%1\firmware.uf2 -f 0xADA52840)
TITLE Meshtastic uf2-convert
SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0"
SET "NRF=0"
SET "UF2CONV_CMD=python3 .\bin\uf2conv.py"
GOTO getopts
:help
ECHO.
ECHO Usage: %SCRIPT_NAME% -t [t-echo^|rak4631^|nano-g2-ultra^|wio-tracker-wm1110^|canaryone^|
ECHO heltec-mesh-node-t114^|tracker-t1000-e^|rak_wismeshtap^|rak2560^|
ECHO nrf52_promicro_diy_tcxo]
ECHO.
ECHO Options:
ECHO -t target Specify a platformio NRF target to build for. (required)
ECHO.
ECHO Example: %SCRIPT_NAME% -t rak4631
GOTO eof
:version
ECHO %SCRIPT_NAME% [Version 2.6.0]
ECHO Meshtastic
GOTO eof
:getopts
IF "%~1"=="" GOTO endopts
IF /I "%~1"=="-?" GOTO help
IF /I "%~1"=="-h" GOTO help
IF /I "%~1"=="--help" GOTO help
IF /I "%~1"=="-v" GOTO version
IF /I "%~1"=="--version" GOTO version
IF /I "%~1"=="--debug" SET "DEBUG=1" & CALL :LOG_MESSAGE DEBUG "DEBUG mode: enabled."
IF /I "%~1"=="-t" SET "TARGETNAME=%~2" & SHIFT
IF /I "%~1"=="--target" SET "TARGETNAME=%~2" & SHIFT
SHIFT
GOTO getopts
:endopts
CALL :LOG_MESSAGE DEBUG "Checking TARGETNAME parameter..."
IF "__!TARGETNAME!__"=="____" (
CALL :LOG_MESSAGE DEBUG "Missing -t target input."
GOTO help
)
IF %DEBUG% EQU 1 SET "UF2CONV_CMD=REM python3 .\bin\uf2conv.py"
SET "NRFTARGETS=t-echo rak4631 nano-g2-ultra wio-tracker-wm1110 canaryone heltec-mesh-node-t114 tracker-t1000-e rak_wismeshtap rak2560 nrf52_promicro_diy_tcxo"
FOR %%a IN (%NRFTARGETS%) DO (
IF /I "%%a"=="!TARGETNAME!" (
@REM We are working with any of %NRFTARGETS%.
SET "NRF=1"
GOTO end_loop_nrf
)
)
:end_loop_nrf
@REM Building operations.
IF !NRF! EQU 1 (
CALL :LOG_MESSAGE INFO "Trying to build for !TARGETNAME!..."
CALL :RUN_UF2CONV !TARGETNAME! || GOTO eof
) ELSE (
CALL :LOG_MESSAGE WARN "!TARGETNAME! is not supported..."
GOTO eof
)
CALL :LOG_MESSAGE INFO "Script complete!."
:eof
ENDLOCAL
EXIT /B %ERRORLEVEL%
:RUN_UF2CONV
@REM Subroutine used to run .\bin\uf2conv.py with arguments.
@REM Also handles %ERRORLEVEL%.
@REM CALL :RUN_UF2CONV [target]
@REM.
@REM Example:: CALL :RUN_UF2CONV rak4631
IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840"
CALL :RESET_ERROR
!UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840
IF %ERRORLEVEL% NEQ 0 (
CALL :LOG_MESSAGE ERROR "Error running command: !UF2CONV_CMD! .\.pio\build\%~1\firmware.hex -c -o .\.pio\build\%~1\firmware.uf2 -f 0xADA52840"
EXIT /B %ERRORLEVEL%
)
GOTO :eof
:LOG_MESSAGE
@REM Subroutine used to print log messages in four different levels.
@REM DEBUG messages only get printed if [-d] flag is passed to script.
@REM CALL :LOG_MESSAGE [ERROR|INFO|WARN|DEBUG] "Message"
@REM.
@REM Example:: CALL :LOG_MESSAGE INFO "Message."
SET /A LOGCOUNTER=LOGCOUNTER+1
IF "%1" == "ERROR" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "INFO" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "WARN" CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
IF "%1" == "DEBUG" IF %DEBUG% EQU 1 CALL :GET_TIMESTAMP & ECHO %1 ^| !TIMESTAMP! !LOGCOUNTER! %~2
GOTO :eof
:GET_TIMESTAMP
@REM Subroutine used to set !TIMESTAMP! to HH:MM:ss.
@REM CALL :GET_TIMESTAMP
@REM.
@REM Updates: !TIMESTAMP!
FOR /F "tokens=1,2,3 delims=:,." %%a IN ("%TIME%") DO (
SET "HH=%%a"
SET "MM=%%b"
SET "ss=%%c"
)
SET "TIMESTAMP=!HH!:!MM!:!ss!"
GOTO :eof
:RESET_ERROR
@REM Subroutine to reset %ERRORLEVEL% to 0.
@REM CALL :RESET_ERROR
@REM.
@REM Updates: %ERRORLEVEL%
EXIT /B 0
GOTO :eof

View File

@@ -7,15 +7,13 @@
"core": "esp32", "core": "esp32",
"extra_flags": [ "extra_flags": [
"-DARDUINO_ESP32S3_DEV", "-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1", "-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1"
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DBOARD_HAS_PSRAM"
], ],
"f_cpu": "240000000L", "f_cpu": "240000000L",
"f_flash": "80000000L", "f_flash": "80000000L",
"flash_mode": "qio", "flash_mode": "qio",
"psram_type": "qio",
"hwids": [["0x303A", "0x1001"]], "hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3", "mcu": "esp32s3",
"variant": "esp32s3" "variant": "esp32s3"

View File

@@ -1,56 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_MDBT50Q_RX -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x2886", "0x0166"]
],
"usb_product": "XIAO-BOOT",
"mcu": "nrf52840",
"variant": "seeed_xiao_nrf52840_kit",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "seeed_xiao_nrf52840_kit",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/XIAO-nRF52840-Wio-SX1262-Kit-for-Meshtastic-p-6400.html",
"vendor": "seeed"
}

View File

@@ -0,0 +1,40 @@
{
"build": {
"arduino": {
"earlephilhower": {
"boot2_source": "boot2_w25q080_2_padded_checksum.S",
"usb_vid": "0x2E8A",
"usb_pid": "0x000A"
}
},
"core": "earlephilhower",
"cpu": "cortex-m0plus",
"extra_flags": "-DARDUINO_GENERIC_RP2040 -DRASPBERRY_PI_PICO -DARDUINO_ARCH_RP2040 -DUSBD_MAX_POWER_MA=250",
"f_cpu": "133000000L",
"hwids": [
["0x2E8A", "0x00C0"],
["0x2E8A", "0x000A"]
],
"mcu": "rp2040",
"variant": "WisBlock_RAK11300_Board"
},
"debug": {
"jlink_device": "RP2040_M0_0",
"openocd_target": "rp2040.cfg",
"svd_path": "rp2040.svd"
},
"frameworks": ["arduino"],
"name": "WisBlock RAK11300",
"upload": {
"maximum_ram_size": 270336,
"maximum_size": 2097152,
"require_upload_port": true,
"native_usb": true,
"use_1200bps_touch": true,
"wait_for_upload_port": false,
"protocol": "picotool",
"protocols": ["cmsis-dap", "raspberrypi-swd", "picotool", "picoprobe"]
},
"url": "https://docs.rakwireless.com/",
"vendor": "RAKwireless"
}

1
debian/control vendored
View File

@@ -17,7 +17,6 @@ Build-Depends: debhelper-compat (= 13),
libbluetooth-dev, libbluetooth-dev,
libusb-1.0-0-dev, libusb-1.0-0-dev,
libi2c-dev, libi2c-dev,
libuv1-dev,
openssl, openssl,
libssl-dev, libssl-dev,
libulfius-dev, libulfius-dev,

1
lib/device-ui Submodule

Submodule lib/device-ui added at cbe5c14e8a

View File

@@ -36,7 +36,6 @@ BuildRequires: pkgconfig(libgpiod)
BuildRequires: pkgconfig(bluez) BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0) BuildRequires: pkgconfig(libusb-1.0)
BuildRequires: libi2c-devel BuildRequires: libi2c-devel
BuildRequires: pkgconfig(libuv)
# Web components: # Web components:
BuildRequires: pkgconfig(openssl) BuildRequires: pkgconfig(openssl)
BuildRequires: pkgconfig(liborcania) BuildRequires: pkgconfig(liborcania)

View File

@@ -7,8 +7,6 @@ default_envs = tbeam
extra_configs = extra_configs =
arch/*/*.ini arch/*/*.ini
variants/*/platformio.ini variants/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic description = Meshtastic
[env] [env]
@@ -60,7 +58,7 @@ lib_deps =
mathertel/OneButton@2.6.1 mathertel/OneButton@2.6.1
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159 https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4 https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
https://github.com/meshtastic/ArduinoThread.git#7c3ee9e1951551b949763b1f5280f8db1fa4068d https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
nanopb/Nanopb@0.4.91 nanopb/Nanopb@0.4.91
erriez/ErriezCRC32@1.0.1 erriez/ErriezCRC32@1.0.1
@@ -79,7 +77,7 @@ lib_deps =
${env.lib_deps} ${env.lib_deps}
end2endzone/NonBlockingRTTTL@1.3.0 end2endzone/NonBlockingRTTTL@1.3.0
build_flags = ${env.build_flags} -Os build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> build_src_filter = ${env.build_src_filter} -<platform/portduino/>
; Common libs for communicating over TCP/IP networks such as MQTT ; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base] [networking_base]
@@ -92,10 +90,6 @@ lib_deps =
lib_deps = lib_deps =
jgromes/RadioLib@7.1.2 jgromes/RadioLib@7.1.2
[device-ui_base]
lib_deps =
https://github.com/meshtastic/device-ui.git#74e739ed4532ca10393df9fc89ae5a22f0bab2b1
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
; (not included in native / portduino) ; (not included in native / portduino)
[environmental_base] [environmental_base]
@@ -106,7 +100,6 @@ lib_deps =
adafruit/Adafruit BMP085 Library@1.2.4 adafruit/Adafruit BMP085 Library@1.2.4
adafruit/Adafruit BME280 Library@2.2.4 adafruit/Adafruit BME280 Library@2.2.4
adafruit/Adafruit BMP3XX Library@2.1.5 adafruit/Adafruit BMP3XX Library@2.1.5
adafruit/Adafruit DPS310@1.1.5
adafruit/Adafruit MCP9808 Library@2.0.2 adafruit/Adafruit MCP9808 Library@2.0.2
adafruit/Adafruit INA260 Library@1.5.2 adafruit/Adafruit INA260 Library@1.5.2
adafruit/Adafruit INA219@1.2.3 adafruit/Adafruit INA219@1.2.3

View File

@@ -30,7 +30,7 @@ class BluetoothStatus : public Status
BluetoothStatus() { statusType = STATUS_TYPE_BLUETOOTH; } BluetoothStatus() { statusType = STATUS_TYPE_BLUETOOTH; }
// New BluetoothStatus: connected or disconnected // New BluetoothStatus: connected or disconnected
explicit BluetoothStatus(ConnectionState state) BluetoothStatus(ConnectionState state)
{ {
assert(state != ConnectionState::PAIRING); // If pairing, use constructor which specifies passkey assert(state != ConnectionState::PAIRING); // If pairing, use constructor which specifies passkey
statusType = STATUS_TYPE_BLUETOOTH; statusType = STATUS_TYPE_BLUETOOTH;
@@ -38,7 +38,7 @@ class BluetoothStatus : public Status
} }
// New BluetoothStatus: pairing, with passkey // New BluetoothStatus: pairing, with passkey
explicit BluetoothStatus(const std::string &passkey) : Status() BluetoothStatus(std::string passkey) : Status()
{ {
statusType = STATUS_TYPE_BLUETOOTH; statusType = STATUS_TYPE_BLUETOOTH;
this->state = ConnectionState::PAIRING; this->state = ConnectionState::PAIRING;

View File

@@ -121,15 +121,10 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN // Default Bluetooth PIN
#define defaultBLEPin 123456 #define defaultBLEPin 123456
#if HAS_ETHERNET && !defined(USE_WS5500) #if HAS_ETHERNET
#include <RAK13800_W5100S.h> #include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET #endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#if HAS_WIFI #if HAS_WIFI
#include <WiFi.h> #include <WiFi.h>
#endif // HAS_WIFI #endif // HAS_WIFI
@@ -169,4 +164,4 @@ class Syslog
bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0))); bool vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) __attribute__((format(printf, 3, 0)));
}; };
#endif // HAS_NETWORKING #endif // HAS_ETHERNET || HAS_WIFI

View File

@@ -32,11 +32,6 @@
#include <WiFi.h> #include <WiFi.h>
#endif #endif
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#endif #endif
#ifndef DELAY_FOREVER #ifndef DELAY_FOREVER

View File

@@ -7,6 +7,7 @@ static File openFile(const char *filename, bool fullAtomic)
{ {
concurrency::LockGuard g(spiLock); concurrency::LockGuard g(spiLock);
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic); LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
#ifdef ARCH_NRF52 #ifdef ARCH_NRF52
FSCom.remove(filename); FSCom.remove(filename);
return FSCom.open(filename, FILE_O_WRITE); return FSCom.open(filename, FILE_O_WRITE);
@@ -18,10 +19,10 @@ static File openFile(const char *filename, bool fullAtomic)
String filenameTmp = filename; String filenameTmp = filename;
filenameTmp += ".tmp"; filenameTmp += ".tmp";
// FIXME: If we are doing a full atomic write, we may need to remove the old tmp file now // If we are doing a full atomic write, remove the old tmp file now
// if (fullAtomic) { if (fullAtomic) {
// FSCom.remove(filename); FSCom.remove(filename);
// } }
// clear any previous LFS errors // clear any previous LFS errors
return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE); return FSCom.open(filenameTmp.c_str(), FILE_O_WRITE);

View File

@@ -135,7 +135,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D #define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44 #define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA0031_ADDR 0x12 #define PMSA0031_ADDR 0x12
#define QMA6100P_ADDR 0x12 #define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38 #define AHT10_ADDR 0x38
@@ -151,7 +150,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MAX30102_ADDR 0x57 #define MAX30102_ADDR 0x57
#define MLX90614_ADDR_DEF 0x5A #define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66 #define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ACCELEROMETER // ACCELEROMETER

View File

@@ -67,8 +67,6 @@ class ScanI2C
INA226, INA226,
NXP_SE050, NXP_SE050,
DFROBOT_RAIN, DFROBOT_RAIN,
DPS310,
LTR390UV,
} DeviceType; } DeviceType;
// typedef uint8_t DeviceAddress; // typedef uint8_t DeviceAddress;

View File

@@ -237,16 +237,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMP085/BMP180", (uint8_t)addr.address); logFoundDevice("BMP085/BMP180", (uint8_t)addr.address);
type = BMP_085; type = BMP_085;
break; break;
case 0x00:
// do we have a DPS310 instead?
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0D), 1);
switch (registerValue) {
case 0x10:
logFoundDevice("DPS310", (uint8_t)addr.address);
type = DPS310;
break;
}
break;
default: default:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
switch (registerValue) { switch (registerValue) {
@@ -349,8 +339,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
} }
break; break;
} }
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT case SHT31_4x_ADDR:
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) { if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
type = SHT4X; type = SHT4X;
@@ -423,11 +412,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (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); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
#ifdef HAS_TPS65233 #ifdef HAS_TPS65233
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
#endif #endif

View File

@@ -1,7 +1,3 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h" #include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
#include "Default.h" #include "Default.h"
@@ -1104,16 +1100,12 @@ int32_t GPS::runOnce()
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000; return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
} }
// clear the GPS rx/tx buffer as quickly as possible // clear the GPS rx buffer as quickly as possible
void GPS::clearBuffer() void GPS::clearBuffer()
{ {
#ifdef ARCH_ESP32
_serial_gps->flush(false);
#else
int x = _serial_gps->available(); int x = _serial_gps->available();
while (x--) while (x--)
_serial_gps->read(); _serial_gps->read();
#endif
} }
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
@@ -1125,7 +1117,7 @@ int GPS::prepareDeepSleep(void *unused)
} }
static const char *PROBE_MESSAGE = "Trying %s (%s)..."; static const char *PROBE_MESSAGE = "Trying %s (%s)...";
static const char *DETECTED_MESSAGE = "%s detected"; static const char *DETECTED_MESSAGE = "%s detected, using %s Module";
#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \ #define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
do { \ do { \
@@ -1133,22 +1125,11 @@ static const char *DETECTED_MESSAGE = "%s detected";
clearBuffer(); \ clearBuffer(); \
_serial_gps->write(TOWRITE "\r\n"); \ _serial_gps->write(TOWRITE "\r\n"); \
if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \ if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
LOG_INFO(DETECTED_MESSAGE, CHIP); \ LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \
return DRIVER; \ return DRIVER; \
} \ } \
} while (0) } while (0)
#define PROBE_FAMILY(FAMILY_NAME, COMMAND, RESPONSE_MAP, TIMEOUT) \
do { \
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \
_serial_gps->write(COMMAND "\r\n"); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
return detectedDriver; \
} \
} while (0)
GnssModel_t GPS::probe(int serialSpeed) GnssModel_t GPS::probe(int serialSpeed)
{ {
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
@@ -1179,34 +1160,31 @@ GnssModel_t GPS::probe(int serialSpeed)
delay(20); delay(20);
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}}; PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 500);
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); PROBE_SIMPLE("UM600", "$PDTINFO", "UM600", GNSS_MODEL_UC6580, 500);
PROBE_SIMPLE("ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H, 500);
std::vector<ChipInfo> atgm = { /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H}, based on AT6558 */
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ PROBE_SIMPLE("ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H, 500);
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration _serial_gps->write("$PAIR513*3D\r\n"); // save configuration
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500);
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", GNSS_MODEL_AG3352, 500);
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
// Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS) // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
delay(20); delay(20);
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
{"LS20031", "MC-1513", GNSS_MODEL_LS20031}}; PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
PROBE_SIMPLE("LS20031", "$PMTK605*31", "MC-1513", GNSS_MODEL_LS20031, 500);
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate)); UBXChecksum(cfg_rate, sizeof(cfg_rate));
@@ -1303,38 +1281,6 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UNKNOWN; return GNSS_MODEL_UNKNOWN;
} }
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
{
String response = "";
unsigned long start = millis();
while (millis() - start < timeout) {
if (_serial_gps->available()) {
response += (char)_serial_gps->read();
if (response.endsWith(",") || response.endsWith("\r\n")) {
#ifdef GPS_DEBUG
LOG_DEBUG(response.c_str());
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str());
return chipInfo.driver;
}
}
}
if (response.endsWith("\r\n")) {
response.trim();
response = ""; // Reset the response string for the next potential message
}
}
}
#ifdef GPS_DEBUG
LOG_DEBUG(response.c_str());
#endif
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
}
GPS *GPS::createGps() GPS *GPS::createGps()
{ {
int8_t _rx_gpio = config.position.rx_gpio; int8_t _rx_gpio = config.position.rx_gpio;

View File

@@ -48,11 +48,6 @@ enum GPSPowerState : uint8_t {
GPS_OFF // Powered off indefinitely GPS_OFF // Powered off indefinitely
}; };
struct ChipInfo {
String chipName; // The name of the chip (for logging)
String detectionString; // The string to match in the response
GnssModel_t driver; // The driver to use
};
/** /**
* A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading * A gps class that only reads from the GPS periodically and keeps the gps powered down except when reading
* *
@@ -235,8 +230,6 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
// Get GNSS model // Get GNSS model
GnssModel_t probe(int serialSpeed); GnssModel_t probe(int serialSpeed);

View File

@@ -166,7 +166,7 @@ bool EInkDisplay::connect()
} }
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ #elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
{ {
// Start HSPI // Start HSPI
hspi = new SPIClass(HSPI); hspi = new SPIClass(HSPI);
@@ -182,9 +182,6 @@ bool EInkDisplay::connect()
// Init GxEPD2 // Init GxEPD2
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
adafruitDisplay->setRotation(0);
#endif
} }
#elif defined(PCA10059) || defined(ME25LS01) #elif defined(PCA10059) || defined(ME25LS01)
{ {

View File

@@ -68,7 +68,7 @@ class EInkDisplay : public OLEDDisplay
// If display uses HSPI // If display uses HSPI
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
SPIClass *hspi = NULL; SPIClass *hspi = NULL;
#endif #endif
@@ -77,4 +77,4 @@ class EInkDisplay : public OLEDDisplay
uint32_t lastDrawMsec = 0; uint32_t lastDrawMsec = 0;
}; };
#endif #endif

View File

@@ -324,14 +324,6 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
if (refresh != UNSPECIFIED) if (refresh != UNSPECIFIED)
return; return;
// Bypass limit if UNLIMITED_FAST mode is active
if (frameFlags & UNLIMITED_FAST) {
refresh = FAST;
reason = NO_OBJECTIONS;
LOG_DEBUG("refresh=FAST, reason=UNLIMITED_FAST_MODE_ACTIVE, frameFlags=0x%x", frameFlags);
return;
}
// If too many FAST refreshes consecutively - force a FULL refresh // If too many FAST refreshes consecutively - force a FULL refresh
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) { if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
refresh = FULL; refresh = FULL;

View File

@@ -23,10 +23,6 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus); EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
~EInkDynamicDisplay(); ~EInkDynamicDisplay();
// Methods to enable or disable unlimited fast refresh mode
void enableUnlimitedFastMode() { addFrameFlag(UNLIMITED_FAST); }
void disableUnlimitedFastMode() { frameFlags = (frameFlagTypes)(frameFlags & ~UNLIMITED_FAST); }
// What kind of frame is this // What kind of frame is this
enum frameFlagTypes : uint8_t { enum frameFlagTypes : uint8_t {
BACKGROUND = (1 << 0), // For frames via display() BACKGROUND = (1 << 0), // For frames via display()
@@ -34,7 +30,6 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
COSMETIC = (1 << 2), // For splashes COSMETIC = (1 << 2), // For splashes
DEMAND_FAST = (1 << 3), // Special case only DEMAND_FAST = (1 << 3), // Special case only
BLOCKING = (1 << 4), // Modifier - block while refresh runs BLOCKING = (1 << 4), // Modifier - block while refresh runs
UNLIMITED_FAST = (1 << 5)
}; };
void addFrameFlag(frameFlagTypes flag); void addFrameFlag(frameFlagTypes flag);

View File

@@ -73,16 +73,6 @@
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28 #define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#endif #endif
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
#include "graphics/fonts/EinkDisplayFonts.h"
#undef FONT_SMALL
#undef FONT_MEDIUM
#undef FONT_LARGE
#define FONT_SMALL FONT_LARGE_LOCAL // Height: 30
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 30
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 30
#endif
#define _fontHeight(font) ((font)[1] + 1) // height is position 1 #define _fontHeight(font) ((font)[1] + 1) // height is position 1
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL) #define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
#ifndef EINKDISPLAYFONTS_h
#define EINKDISPLAYFONTS_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define PROGMEM
#endif
/**
* Monospaced Plain 30
*/
extern const uint8_t Monospaced_plain_30[] PROGMEM;
#endif

View File

@@ -40,11 +40,13 @@ void LatchingBacklight::setPin(uint8_t pin, bool activeWhen)
// Ensures the backlight is off // Ensures the backlight is off
int LatchingBacklight::beforeDeepSleep(void *unused) int LatchingBacklight::beforeDeepSleep(void *unused)
{ {
// Contingency only // We shouldn't need to guard the block like this
// - pin wasn't set // Contingency for:
// - settings corruption: settings.optionalMenuItems.backlight guards backlight code in MenuApplet
// - improper use in the future
if (pin != (uint8_t)-1) { if (pin != (uint8_t)-1) {
off(); off();
pinMode(pin, INPUT); // High impedance - unnecessary? pinMode(pin, INPUT); // High impedence - unnecessary?
} else } else
LOG_WARN("LatchingBacklight instantiated, but pin not set"); LOG_WARN("LatchingBacklight instantiated, but pin not set");
return 0; // Continue with deep sleep return 0; // Continue with deep sleep

View File

@@ -12,7 +12,7 @@ EInk::EInk(uint16_t width, uint16_t height, UpdateTypes supported)
} }
// Used by NicheGraphics implementations to check if a display supports a specific refresh operation. // Used by NicheGraphics implementations to check if a display supports a specific refresh operation.
// Whether or not the update type is supported is specified in the constructor // Whether or the update type is supported is specified in the constructor
bool EInk::supports(UpdateTypes type) bool EInk::supports(UpdateTypes type)
{ {
// The EInkUpdateTypes enum assigns each type a unique bit. We are checking if that bit is set. // The EInkUpdateTypes enum assigns each type a unique bit. We are checking if that bit is set.

View File

@@ -31,7 +31,7 @@ class EInk : private concurrency::OSThread
virtual void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1) = 0; virtual void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = -1) = 0;
virtual void update(uint8_t *imageData, UpdateTypes type) = 0; // Change the display image virtual void update(uint8_t *imageData, UpdateTypes type) = 0; // Change the display image
void await(); // Wait for an in-progress update to complete before proceeding void await(); // Wait for an in-progress update to complete before proceeding
bool supports(UpdateTypes type); // Can display perform a certain update type bool supports(UpdateTypes type); // Can display perfom a certain update type
bool busy() { return updateRunning; } // Display able to update right now? bool busy() { return updateRunning; } // Display able to update right now?
const uint16_t width; // Public so that NicheGraphics implementations can access. Safe because const. const uint16_t width; // Public so that NicheGraphics implementations can access. Safe because const.
@@ -47,8 +47,8 @@ class EInk : private concurrency::OSThread
const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class const UpdateTypes supportedUpdateTypes; // Capabilities of a derived display class
bool updateRunning = false; // see EInk::busy() bool updateRunning = false; // see EInk::busy()
uint32_t updateBegunAt = 0; // For initial pause before polling for update completion uint32_t updateBegunAt; // For initial pause before polling for update completion
uint32_t pollingInterval = 0; // How often to check if update complete (ms) uint32_t pollingInterval; // How often to check if update complete (ms)
}; };
} // namespace NicheGraphics::Drivers } // namespace NicheGraphics::Drivers

View File

@@ -4,7 +4,7 @@
using namespace NicheGraphics::Drivers; using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel // Map the display controller IC's output to the conected panel
void GDEY0154D67::configScanning() void GDEY0154D67::configScanning()
{ {
// "Driver output control" // "Driver output control"

View File

@@ -98,7 +98,6 @@ void LCMEN213EFC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t
reset(); reset();
} }
// Display an image on the display
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type) void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
{ {
this->updateType = type; this->updateType = type;
@@ -162,6 +161,13 @@ void LCMEN213EFC1::sendCommand(const uint8_t command)
void LCMEN213EFC1::sendData(uint8_t data) void LCMEN213EFC1::sendData(uint8_t data)
{ {
// spi->beginTransaction(spiSettings);
// digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
// digitalWrite(pin_cs, LOW);
// spi->transfer(data);
// digitalWrite(pin_cs, HIGH);
// digitalWrite(pin_dc, HIGH);
// spi->endTransaction();
sendData(&data, 1); sendData(&data, 1);
} }

View File

@@ -45,24 +45,21 @@ class LCMEN213EFC1 : public EInk
void configFull(); // Configure display for FULL refresh void configFull(); // Configure display for FULL refresh
void configFast(); // Configure display for FAST refresh void configFast(); // Configure display for FAST refresh
void writeNewImage(); void writeNewImage();
void writeOldImage(); // Used for "differential update", aka FAST refresh void writeOldImage();
void detachFromUpdate(); void detachFromUpdate();
bool isUpdateDone(); bool isUpdateDone();
void finalizeUpdate(); void finalizeUpdate();
protected: protected:
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring? uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes) uint8_t bufferRowSize; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
uint32_t bufferSize = 0; // In bytes. Rows * Columns uint32_t bufferSize; // In bytes. Rows * Columns
uint8_t *buffer = nullptr; uint8_t *buffer;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED; UpdateTypes updateType;
uint8_t pin_dc = -1; uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
uint8_t pin_cs = -1; SPIClass *spi;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
SPISettings spiSettings = SPISettings(6000000, MSBFIRST, SPI_MODE0); SPISettings spiSettings = SPISettings(6000000, MSBFIRST, SPI_MODE0);
}; };

View File

@@ -3,7 +3,7 @@
A driver for E-Ink SPI displays. Suitable for re-use by various NicheGraphics UIs. A driver for E-Ink SPI displays. Suitable for re-use by various NicheGraphics UIs.
Your UI should use the class `NicheGraphics::Drivers::EInk` . Your UI should use the class `NicheGraphics::Drivers::EInk` .
When you set up a hardware variant, you will use one of the specific display model classes, which extend the EInk class. When you set up a hardware variant, you will use one of specific display model classes, which extend the EInk class.
An example setup might look like this: An example setup might look like this:
@@ -30,7 +30,7 @@ void setupNicheGraphics()
## Methods ## Methods
### `update(uint8_t *imageData, UpdateTypes type)` ### `update(uint8_t *imageData, UpdateTypes type, bool async=true)`
Update the image on the display Update the image on the display
@@ -39,6 +39,7 @@ Update the image on the display
- `FULL` - `FULL`
- `FAST` - `FAST`
- (Other custom types may be possible) - (Other custom types may be possible)
- _`async`_ whether to wait for update to complete, or continue code execution
The imageData is a 1-bit image. X-Pixels are 8-per byte, with the MSB being the leftmost pixel. This was not an InkHUD design decision; it is the raw format accepted by the E-Ink display controllers ICs. The imageData is a 1-bit image. X-Pixels are 8-per byte, with the MSB being the leftmost pixel. This was not an InkHUD design decision; it is the raw format accepted by the E-Ink display controllers ICs.
@@ -62,10 +63,6 @@ uint8_t xBits = (7-x) % 8;
image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2 image[yByte + xByte] |= (1 << xBits); // Set pixel x=12, y=2
``` ```
### `await()`
Wait for an in-progress update to complete before continuing
### `supports(UpdateTypes type)` ### `supports(UpdateTypes type)`
Check if display supports a specific update type. `true` if supported. Check if display supports a specific update type. `true` if supported.
@@ -78,7 +75,7 @@ Check if display is already performing an `update()`. `true` if already updating
### `width()` ### `width()`
Width of the display, in pixels. Note: most displays are portrait. Your UI will need to implement rotation in software. Width of the display, in pixels. Note: most displays are portait. Your UI will need to implement rotation in software.
### `height()` ### `height()`

View File

@@ -30,7 +30,7 @@ void SSD16XX::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_b
pinMode(pin_busy, INPUT); pinMode(pin_busy, INPUT);
// If using a reset pin, hold high // If using a reset pin, hold high
// Reset is active low for Solomon Systech ICs // Reset is active low for solmon systech ICs
if (pin_rst != 0xFF) if (pin_rst != 0xFF)
pinMode(pin_rst, INPUT_PULLUP); pinMode(pin_rst, INPUT_PULLUP);
@@ -72,6 +72,13 @@ void SSD16XX::sendCommand(const uint8_t command)
void SSD16XX::sendData(uint8_t data) void SSD16XX::sendData(uint8_t data)
{ {
// spi->beginTransaction(spiSettings);
// digitalWrite(pin_dc, HIGH); // DC pin HIGH indicates data, instead of command
// digitalWrite(pin_cs, LOW);
// spi->transfer(data);
// digitalWrite(pin_cs, HIGH);
// digitalWrite(pin_dc, HIGH);
// spi->endTransaction();
sendData(&data, 1); sendData(&data, 1);
} }

View File

@@ -39,24 +39,21 @@ class SSD16XX : public EInk
virtual void configUpdateSequence(); // Tell controller IC which operations to run virtual void configUpdateSequence(); // Tell controller IC which operations to run
virtual void writeNewImage(); virtual void writeNewImage();
virtual void writeOldImage(); // Image which can be used at *next* update for "differential refresh" virtual void writeOldImage();
virtual void detachFromUpdate(); virtual void detachFromUpdate();
virtual bool isUpdateDone() override; virtual bool isUpdateDone() override;
virtual void finalizeUpdate() override; virtual void finalizeUpdate() override;
protected: protected:
uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring? uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
uint8_t bufferRowSize = 0; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes) uint8_t bufferRowSize; // In bytes. Rows store 8 pixels per byte. Rounded up to fit (e.g. 122px would require 16 bytes)
uint32_t bufferSize = 0; // In bytes. Rows * Columns uint32_t bufferSize; // In bytes. Rows * Columns
uint8_t *buffer = nullptr; uint8_t *buffer;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED; UpdateTypes updateType;
uint8_t pin_dc = -1; uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
uint8_t pin_cs = -1; SPIClass *spi;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0); SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
}; };

View File

@@ -1,3 +1,3 @@
# NicheGraphics - Drivers # NicheGraphics - Drivers
Common drivers which can be used by various NicheGraphics UIs Common drivers which can be used by various NicheGrapihcs UIs

View File

@@ -119,7 +119,7 @@ template <typename T> class FlashData
// Calculate a hash of the data // Calculate a hash of the data
uint32_t hash = getHash(data); uint32_t hash = getHash(data);
f.write((uint8_t *)data, sizeof(T)); // Write the actual data f.write((uint8_t *)data, sizeof(T)); // Write the actualy data
f.write((uint8_t *)&hash, sizeof(hash)); // Append the hash f.write((uint8_t *)&hash, sizeof(hash)); // Append the hash
// f.flush(); // f.flush();

View File

@@ -4,7 +4,7 @@ Uses Windows-1251 encoding to map translingual Cyrillic characters to range betw
https://en.wikipedia.org/wiki/Windows-1251 https://en.wikipedia.org/wiki/Windows-1251
Cyrillic characters present to the firmware as UTF8. Cyrillic characters present to the firmware as UTF8.
A NicheGraphics implementation needs to identify these, and substitute the appropriate Windows-1251 char value. A Niche Graphics implementation needs to identify these, and subsitute the appropriate Windows-1251 char value.
*/ */

View File

@@ -2,8 +2,6 @@
#include "./Applet.h" #include "./Applet.h"
#include "main.h"
#include "RTC.h" #include "RTC.h"
using namespace NicheGraphics; using namespace NicheGraphics;
@@ -18,15 +16,10 @@ InkHUD::Applet::Applet() : GFX(0, 0)
// The width and height will change dynamically, depending on Applet tiling // The width and height will change dynamically, depending on Applet tiling
// If you're getting a "divide by zero error", consider it an assert: // If you're getting a "divide by zero error", consider it an assert:
// WindowManager should be the only one controlling the rendering // WindowManager should be the only one controlling the rendering
inkhud = InkHUD::getInstance();
settings = &inkhud->persistence->settings;
latestMessage = &inkhud->persistence->latestMessage;
} }
// Draw a single pixel // The raw pixel output generated by AdafruitGFX drawing
// The raw pixel output generated by AdafruitGFX drawing all passes through here // Hand off to the applet's tile, which will in-turn pass to the window manager
// Hand off to the applet's tile, which will in-turn pass to the renderer
void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color) void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
{ {
// Only render pixels if they fall within user's cropped region // Only render pixels if they fall within user's cropped region
@@ -34,10 +27,9 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
assignedTile->handleAppletPixel(x, y, (Color)color); assignedTile->handleAppletPixel(x, y, (Color)color);
} }
// Link our applet to a tile // Sets which tile the applet renders for
// This can only be called by Tile::assignApplet
// The tile determines the applets dimensions
// Pixel output is passed to tile during render() // Pixel output is passed to tile during render()
// This should only be called by Tile::assignApplet
void InkHUD::Applet::setTile(Tile *t) void InkHUD::Applet::setTile(Tile *t)
{ {
// If we're setting (not clearing), make sure the link is "reciprocal" // If we're setting (not clearing), make sure the link is "reciprocal"
@@ -47,32 +39,25 @@ void InkHUD::Applet::setTile(Tile *t)
assignedTile = t; assignedTile = t;
} }
// The tile to which our applet is assigned // Which tile will the applet render() to?
InkHUD::Tile *InkHUD::Applet::getTile() InkHUD::Tile *InkHUD::Applet::getTile()
{ {
return assignedTile; return assignedTile;
} }
// Draw the applet
void InkHUD::Applet::render() void InkHUD::Applet::render()
{ {
assert(assignedTile); // Ensure that we have a tile assert(assignedTile); // Ensure that we have a tile
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
// WindowManager::update has now consumed the info about our update request wantRender = false; // Clear the flag set by requestUpdate
// Clear everything for future requests wantAutoshow = false; // If we're rendering now, it means our request was considered. It may or may not have been granted.
wantRender = false; // Flag set by requestUpdate wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Our requested type has been considered by now. Tidy up.
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
updateDimensions(); updateDimensions();
resetDrawingSpace(); resetDrawingSpace();
onRender(); // Derived applet's drawing takes place here onRender(); // Derived applet's drawing takes place here
// Handle "Tile Highlighting"
// Some devices may use an auxiliary button to switch between tiles
// When this happens, we temporarily highlight the newly focused tile with a border
// If our tile is (or was) highlighted, to indicate a change in focus // If our tile is (or was) highlighted, to indicate a change in focus
if (Tile::highlightTarget == assignedTile) { if (Tile::highlightTarget == assignedTile) {
// Draw the highlight // Draw the highlight
@@ -92,8 +77,7 @@ void InkHUD::Applet::render()
} }
// Does the applet want to render now? // Does the applet want to render now?
// Checks whether the applet called requestUpdate recently, in response to an event // Checks whether the applet called requestUpdate() recently, in response to an event
// Used by WindowManager::update
bool InkHUD::Applet::wantsToRender() bool InkHUD::Applet::wantsToRender()
{ {
return wantRender; return wantRender;
@@ -101,21 +85,18 @@ bool InkHUD::Applet::wantsToRender()
// Does the applet want to be moved to foreground before next render, to show new data? // Does the applet want to be moved to foreground before next render, to show new data?
// User specifies whether an applet has permission for this, using the on-screen menu // User specifies whether an applet has permission for this, using the on-screen menu
// Used by WindowManager::update
bool InkHUD::Applet::wantsToAutoshow() bool InkHUD::Applet::wantsToAutoshow()
{ {
return wantAutoshow; return wantAutoshow;
} }
// Which technique would this applet prefer that the display use to change the image? // Which technique would this applet prefer that the display use to change the image?
// Used by WindowManager::update
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType() Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
{ {
return wantUpdateType; return wantUpdateType;
} }
// Get size of the applet's drawing space from its tile // Get size of the applet's drawing space from its tile
// Performed immediately before derived applet's drawing code runs
void InkHUD::Applet::updateDimensions() void InkHUD::Applet::updateDimensions()
{ {
assert(assignedTile); assert(assignedTile);
@@ -132,20 +113,19 @@ void InkHUD::Applet::resetDrawingSpace()
setTextColor(BLACK); // Reset text params setTextColor(BLACK); // Reset text params
setCursor(0, 0); setCursor(0, 0);
setTextWrap(false); setTextWrap(false);
setFont(fontSmall); setFont(AppletFont()); // Restore the default AdafruitGFX font
} }
// Tell InkHUD::Renderer that we want to render now // Tell the window manager that we want to render now
// Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc // Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc
// When an applet decides it has heard something important, and wants to redraw, it calls this method // When an applet decides it has heard something important, and wants to redraw, it calls this method
// Once the renderer has given other applets a chance to process whatever event we just detected, // Once the window manager has given other applets a chance to process whatever event we just detected,
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground) // it will run Applet::render(), which may draw our applet to screen, if it is shown (forgeround)
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type) void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
{ {
wantRender = true; wantRender = true;
wantUpdateType = type; wantUpdateType = type;
inkhud->requestUpdate(); WindowManager::getInstance()->requestUpdate();
} }
// Ask window manager to move this applet to foreground at start of next render // Ask window manager to move this applet to foreground at start of next render
@@ -158,7 +138,7 @@ void InkHUD::Applet::requestAutoshow()
// Called when an Applet begins running // Called when an Applet begins running
// Active applets are considered "enabled" // Active applets are considered "enabled"
// They should now listen for events, and request their own updates // They should now listen for events, and request their own updates
// They may also be unexpectedly renderer at any time by other InkHUD components // They may also be force rendered by the window manager at any time
// Applets can be activated at run-time through the on-screen menu // Applets can be activated at run-time through the on-screen menu
void InkHUD::Applet::activate() void InkHUD::Applet::activate()
{ {
@@ -166,7 +146,7 @@ void InkHUD::Applet::activate()
active = true; active = true;
} }
// Called when an Applet stops running // Called when an Applet stop running
// Inactive applets are considered "disabled" // Inactive applets are considered "disabled"
// They should not listen for events, process data // They should not listen for events, process data
// They will not be rendered // They will not be rendered
@@ -193,7 +173,7 @@ bool InkHUD::Applet::isActive()
// Begin showing the Applet // Begin showing the Applet
// It will be rendered immediately to whichever tile it is assigned // It will be rendered immediately to whichever tile it is assigned
// The Renderer will also now honor requestUpdate() calls from this applet // The window manager will also now honor requestUpdate() calls from this applet
void InkHUD::Applet::bringToForeground() void InkHUD::Applet::bringToForeground()
{ {
if (!foreground) { if (!foreground) {
@@ -206,7 +186,7 @@ void InkHUD::Applet::bringToForeground()
// Stop showing the Applet // Stop showing the Applet
// Calls to requestUpdate() will no longer be honored // Calls to requestUpdate() will no longer be honored
// When one applet moves to background, another should move to foreground (exception: some system applets) // When one applet moves to background, another should move to foreground
void InkHUD::Applet::sendToBackground() void InkHUD::Applet::sendToBackground()
{ {
if (foreground) { if (foreground) {
@@ -216,10 +196,6 @@ void InkHUD::Applet::sendToBackground()
} }
// Is the applet currently displayed on a tile // Is the applet currently displayed on a tile
// Note: in some uncommon situations, an applet may be "foreground", and still not visible.
// This can occur when a system applet is covering the screen (e.g. during BLE pairing)
// This is not our applets responsibility to handle,
// as in those situations, the system applet will have "locked" rendering
bool InkHUD::Applet::isForeground() bool InkHUD::Applet::isForeground()
{ {
return foreground; return foreground;
@@ -272,7 +248,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
// Custom font // Custom font
// - set with AppletFont::addSubstitution // - set with AppletFont::addSubstitution
// - find certain UTF8 chars // - find certain UTF8 chars
// - replace with glyph from custom font (or suitable ASCII addSubstitution?) // - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
getFont().applySubstitutions(&text); getFont().applySubstitutions(&text);
// We do still have to run getTextBounds to find the width // We do still have to run getTextBounds to find the width
@@ -295,7 +271,8 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
break; break;
} }
// We're using a fixed line height, rather than sizing to text (getTextBounds) // We're using a fixed line height (getFontDimensions), rather than sizing to text (getTextBounds)
// Note: the FontDimensions values for this are unsigned
switch (va) { switch (va) {
case TOP: case TOP:
@@ -314,7 +291,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
} }
// Set which font should be used for subsequent drawing // Set which font should be used for subsequent drawing
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data // This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
void InkHUD::Applet::setFont(AppletFont f) void InkHUD::Applet::setFont(AppletFont f)
{ {
GFX::setFont(f.gfxFont); GFX::setFont(f.gfxFont);
@@ -322,12 +299,20 @@ void InkHUD::Applet::setFont(AppletFont f)
} }
// Get which font is currently being used for drawing // Get which font is currently being used for drawing
// This is AppletFont type, which is a wrapper for AdafruitGFX font, with some precalculated dimension data // This is AppletFont type, which is a wrapper for AdfruitGFX font, with some precalculated dimension data
InkHUD::AppletFont InkHUD::Applet::getFont() InkHUD::AppletFont InkHUD::Applet::getFont()
{ {
return currentFont; return currentFont;
} }
// Set two general-purpose fonts, which are reused by many applets
// Applets are also permitted to use other fonts, if they can justify the flash usage
void InkHUD::Applet::setDefaultFonts(AppletFont large, AppletFont small)
{
Applet::fontSmall = small;
Applet::fontLarge = large;
}
// Gets rendered width of a string // Gets rendered width of a string
// Wrapper for getTextBounds // Wrapper for getTextBounds
uint16_t InkHUD::Applet::getTextWidth(const char *text) uint16_t InkHUD::Applet::getTextWidth(const char *text)
@@ -342,7 +327,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text)
} }
// Gets rendered width of a string // Gets rendered width of a string
// Wrapper for getTextBounds // Wrappe for getTextBounds
uint16_t InkHUD::Applet::getTextWidth(std::string text) uint16_t InkHUD::Applet::getTextWidth(std::string text)
{ {
getFont().applySubstitutions(&text); getFont().applySubstitutions(&text);
@@ -353,7 +338,7 @@ uint16_t InkHUD::Applet::getTextWidth(std::string text)
// Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels // Evaluate SNR and RSSI to qualify signal strength at one of four discrete levels
// Roughly comparable to values used by the iOS app; // Roughly comparable to values used by the iOS app;
// I didn't actually go look up the code, just fit to a sample graphic I have of the iOS signal indicator // I didn't actually go look up the code, just fit to a sample graphic I have of the iOS signal indicator
InkHUD::Applet::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi) InkHUD::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
{ {
uint8_t score = 0; uint8_t score = 0;
@@ -391,14 +376,12 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
return std::string(nodeIdHex); return std::string(nodeIdHex);
} }
// Print text, with word wrapping
// Avoids splitting words in half, instead moving the entire word to a new line wherever possible
void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text) void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text)
{ {
// Custom font glyphs // Custom font glyphs
// - set with AppletFont::addSubstitution // - set with AppletFont::addSubstitution
// - find certain UTF8 chars // - find certain UTF8 chars
// - replace with glyph from custom font (or suitable ASCII addSubstitution?) // - replace with glpyh from custom font (or suitable ASCII addSubstitution?)
getFont().applySubstitutions(&text); getFont().applySubstitutions(&text);
// Place the AdafruitGFX cursor to suit our "top" coord // Place the AdafruitGFX cursor to suit our "top" coord
@@ -545,7 +528,7 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
#else #else
constexpr uint32_t validAfterEpoch = 1738368000 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to Feb 1, 2025 12:00:00 AM GMT constexpr uint32_t validAfterEpoch = 1727740800 - (SEC_PER_DAY * 30 * 6); // 6 Months prior to October 1, 2024 12:00:00 AM GMT
#endif #endif
uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true); uint32_t epochNow = getValidTime(RTCQuality::RTCQualityDevice, true);
@@ -555,17 +538,23 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
// Times are invalid: rtc is much older than when code was built // Times are invalid: rtc is much older than when code was built
// Don't give any human readable string // Don't give any human readable string
if (epochNow <= validAfterEpoch) if (epochNow <= validAfterEpoch) {
LOG_DEBUG("RTC prior to buildtime");
return ""; return "";
}
// Times are invalid: argument time is significantly ahead of RTC // Times are invalid: argument time is significantly ahead of RTC
// Don't give any human readable string // Don't give any human readable string
if (daysAgo < -2) if (daysAgo < -2) {
LOG_DEBUG("RTC in future");
return ""; return "";
}
// Times are probably invalid: more than 6 months ago // Times are probably invalid: more than 6 months ago
if (daysAgo > 6 * 30) if (daysAgo > 6 * 30) {
LOG_DEBUG("RTC val > 6 months old");
return ""; return "";
}
if (daysAgo > 1) if (daysAgo > 1)
return to_string(daysAgo) + " days ago"; return to_string(daysAgo) + " days ago";
@@ -613,7 +602,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// Check if heard recently, and not our own node // Check if heard recently, and not our own node
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum()) if (sinceLastSeen(node) < settings.recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
count++; count++;
} }
@@ -630,7 +619,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
// Resulting string // Resulting string
std::string localized; std::string localized;
// Imperial // Imeperial
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
uint32_t feet = meters * FEET_PER_METER; uint32_t feet = meters * FEET_PER_METER;
// Distant (miles, rounded) // Distant (miles, rounded)
@@ -662,7 +651,6 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
return localized; return localized;
} }
// Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly
void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY) void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY)
{ {
// How many times to draw along x axis // How many times to draw along x axis
@@ -715,24 +703,17 @@ void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string te
// Asked before a notification is shown via the NotificationApplet // Asked before a notification is shown via the NotificationApplet
// An applet might want to suppress a notification if the applet itself already displays this info // An applet might want to suppress a notification if the applet itself already displays this info
// Example: AllMessageApplet should not approve notifications for messages, if it is in foreground // Example: AllMessageApplet should not approve notifications for messages, if it is in foreground
bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n) bool InkHUD::Applet::approveNotification(InkHUD::Notification &n)
{ {
// By default, no objection // By default, no objection
return true; return true;
} }
// Draw the standard header, used by most Applets // Draw the standard header, used by most Applets
/*
┌───────────────────────────────┐
│ Applet::name here │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │
│ │
│ │
└───────────────────────────────┘
*/
void InkHUD::Applet::drawHeader(std::string text) void InkHUD::Applet::drawHeader(std::string text)
{ {
setFont(fontSmall);
// Y position for divider // Y position for divider
// - between header text and messages // - between header text and messages
constexpr int16_t padDivH = 2; constexpr int16_t padDivH = 2;
@@ -790,15 +771,6 @@ uint16_t InkHUD::Applet::getLogoHeight(uint16_t limitWidth, uint16_t limitHeight
// Draw a scalable Meshtastic logo // Draw a scalable Meshtastic logo
// Make sure to provide dimensions which have the correct aspect ratio (~2) // Make sure to provide dimensions which have the correct aspect ratio (~2)
// Three paths, drawn thick using quads, with one corner "radiused" // Three paths, drawn thick using quads, with one corner "radiused"
/*
- ^
/- /-\
// // \\
// // \\
// // \\
// // \\
*/
void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height) void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height)
{ {
struct Point { struct Point {
@@ -816,17 +788,6 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
int16_t logoB = logoT + logoH - 1; int16_t logoB = logoT + logoH - 1;
// Points for paths (a, b, and c) // Points for paths (a, b, and c)
/*
+-----------------------------+
--| a2 b2/c1 |
| |
| |
| |
--| a1 b1 c2 |
+-----------------------------+
| | | |
*/
Point a1 = {map(0, 0, 3, logoL, logoR), logoB}; Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
Point a2 = {map(1, 0, 3, logoL, logoR), logoT}; Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
Point b1 = {map(1, 0, 3, logoL, logoR), logoB}; Point b1 = {map(1, 0, 3, logoL, logoR), logoB};
@@ -834,72 +795,17 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
Point c1 = {map(2, 0, 3, logoL, logoR), logoT}; Point c1 = {map(2, 0, 3, logoL, logoR), logoT};
Point c2 = {map(3, 0, 3, logoL, logoR), logoB}; Point c2 = {map(3, 0, 3, logoL, logoR), logoB};
// Find angle of the path(s) // Find right-angle to the path
// Used to thicken the single pixel paths // Used to thicken the single pixel paths
/*
+-------------------------------+
| a2 |
| -| |
| -/ | |
| -/ | |
| -/# | |
| -/ # | |
| / # | |
| a1---------- |
+-------------------------------+
*/
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)}; Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
float angle = tanh((float)deltaA.y / deltaA.x); float angle = tanh((float)deltaA.y / deltaA.x);
// Distance (at right angle to the paths), which will give corners for our "quads" // Distance {at right angle from the paths), which will give corners for our "quads"
// The distance is unsigned. We will vary the signedness of the x and y components to suit the path and corner // The distance is unsigned. We will vary the signedness of the x and y components to suit the path and corner
/*
| a2
| .
| ..
| aq1 ..
| # ..
| | # ..
|fromPath.y | # ..
| +----a1
|
| fromPath.x
+--------------------------------
*/
Distance fromPath; Distance fromPath;
fromPath.x = cos(radians(90) - angle) * logoTh * 0.5; fromPath.x = cos(radians(90) - angle) * logoTh * 0.5;
fromPath.y = sin(radians(90) - angle) * logoTh * 0.5; fromPath.y = sin(radians(90) - angle) * logoTh * 0.5;
// Make the paths thick
// Corner points for the rectangles (quads):
/*
aq2
a2
/ aq3
/
/
aq1 /
a1
aq3
*/
// Filled as two triangles per quad:
/*
aq2 #
# ###
## # aq3
## ### -
## #### -/
## ### -/
## #### -/
aq1 ## -/
--- -/
\---aq4
*/
// Make the path thick: path a becomes quad a // Make the path thick: path a becomes quad a
Point aq1{a1.x - fromPath.x, a1.y - fromPath.y}; Point aq1{a1.x - fromPath.x, a1.y - fromPath.y};
Point aq2{a2.x - fromPath.x, a2.y - fromPath.y}; Point aq2{a2.x - fromPath.x, a2.y - fromPath.y};
@@ -916,7 +822,7 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
fillTriangle(bq1.x, bq1.y, bq2.x, bq2.y, bq3.x, bq3.y, BLACK); fillTriangle(bq1.x, bq1.y, bq2.x, bq2.y, bq3.x, bq3.y, BLACK);
fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, BLACK); fillTriangle(bq1.x, bq1.y, bq3.x, bq3.y, bq4.x, bq4.y, BLACK);
// Make the path thick: path c becomes quad c // Make the path hick: path c becomes quad c
Point cq1{c1.x - fromPath.x, c1.y + fromPath.y}; Point cq1{c1.x - fromPath.x, c1.y + fromPath.y};
Point cq2{c2.x - fromPath.x, c2.y + fromPath.y}; Point cq2{c2.x - fromPath.x, c2.y + fromPath.y};
Point cq3{c2.x + fromPath.x, c2.y - fromPath.y}; Point cq3{c2.x + fromPath.x, c2.y - fromPath.y};
@@ -925,21 +831,10 @@ void InkHUD::Applet::drawLogo(int16_t centerX, int16_t centerY, uint16_t width,
fillTriangle(cq1.x, cq1.y, cq3.x, cq3.y, cq4.x, cq4.y, BLACK); fillTriangle(cq1.x, cq1.y, cq3.x, cq3.y, cq4.x, cq4.y, BLACK);
// Radius the intersection of quad b and quad c // Radius the intersection of quad b and quad c
/*
b2 / c1
####
## ##
/ \
/ \/ \
/ /\ \
/ / \ \
*/
// Don't attempt if logo is tiny // Don't attempt if logo is tiny
if (logoTh > 3) { if (logoTh > 3) {
// The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding // The radius for the cap *should* be the same as logoTh, but it's not, due to accumulated rounding
// We get better results just re-deriving it // We get better results just rederiving it
int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2)); int16_t capRad = sqrt(pow(fromPath.x, 2) + pow(fromPath.y, 2));
fillCircle(b2.x, b2.y, capRad, BLACK); fillCircle(b2.x, b2.y, capRad, BLACK);
} }

View File

@@ -7,21 +7,103 @@
An applet is one "program" which may show info on the display. An applet is one "program" which may show info on the display.
===================================
Preliminary notes, for the curious
===================================
(This info to be streamlined, and moved to a more official documentation)
User Applets vs System Applets
-------------------------------
There are either "User Applets", or "System Applets".
This concept is only for our understanding; as far at the code is concerned, both are just "Applets"
User applets are the "normal" applets.
User applets are applets like "AllMessageApplet", or "MapApplet".
User applets may be enabled / disabled by user, via the on-screen menu.
Incorporating new UserApplets is easy: just add them during setupNicheGraphics
If a UserApplet is not added during setupNicheGraphics, it will not be built.
The set of available UserApplets is allowed to vary from device to device.
Examples of system applets include "NotificationApplet" and "MenuApplet".
For their own reasons, system applets each require some amount of special handling.
Drawing
--------
*All* drawing must be performed by an Applet.
Applets implement the onRender() method, where all drawing takes place.
Applets are told how wide and tall they are, and are expected to draw to suit this size.
When an applet draws, it uses co-ordinates in "Applet Space": between 0 and applet width/height.
Event-driven rendering
-----------------------
Applets don't render unless something on the display needs to change.
An applet is expected to determine for itself when it has new info to display.
It should interact with the firmware via the MeshModule API, via Observables, etc.
Please don't directly add hooks throughout the existing firmware code.
When an applet decides it would like to update the display, it should call requestUpdate()
The WindowManager will shortly call the onRender() method for all affected applets
An Applet may be unexpectedly asked to render at any point in time.
Applets should cache their data, but not their pixel output: they should re-render when onRender runs.
An Applet's dimensions are not know until onRender is called, so pre-rendering of UI elements is prohibited.
Tiles
-----
Applets are assigned to "Tiles".
Assigning an applet to a tile creates a reciprocal link between the two.
When an applet renders, it passes pixels to its tile.
The tile translates these to the correct position, to be placed into the fullscreen framebuffer.
User applets don't get to choose their own tile; the multiplexing is handled by the WindowManager.
System applets might do strange things though.
Foreground and Background
-------------------------
The user can cycle between applets by short-pressing the user button.
Any applets which are currently displayed on the display are "foreground".
When the user button is short pressed, and an applet is hidden, it becomes "background".
Although the WindowManager will not render background applets, they should still collect data,
so they are ready to display when they are brought to foreground again.
Even if they are in background, Applets should still request updates when an event affects them,
as the user may have given them permission to "autoshow"; bringing themselves foreground automatically
Applets can implement the onForeground and onBackground methods to handle this change in state.
They can also check their state by calling isForeground() at any time.
Active and Inactive
-------------------
The user can select which applets are available, using the onscreen applet selection menu.
Applets which are enabled in this menu are "active"; otherwise they are "inactive".
An inactive applet is expected not collect data; not to consume resources.
Applets are activated at boot, or when enabled via the menu.
They are deactivated at shutdown, or when disabled via the menu.
Applets can implement the onActivation and onDeactivation methods to handle this change in state.
*/ */
#pragma once #pragma once
#include "configuration.h" #include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib #include <GFX.h>
#include "mesh/MeshTypes.h"
#include "./AppletFont.h" #include "./AppletFont.h"
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet #include "./Applets/System/Notification/Notification.h"
#include "./InkHUD.h"
#include "./Persistence.h"
#include "./Tile.h" #include "./Tile.h"
#include "./Types.h"
#include "./WindowManager.h"
#include "graphics/niche/Drivers/EInk/EInk.h" #include "graphics/niche/Drivers/EInk/EInk.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
@@ -30,57 +112,37 @@ namespace NicheGraphics::InkHUD
using NicheGraphics::Drivers::EInk; using NicheGraphics::Drivers::EInk;
using std::to_string; using std::to_string;
class Tile;
class WindowManager;
class Applet : public GFX class Applet : public GFX
{ {
public: public:
// Which edge Applet::printAt will place on the Y parameter
enum VerticalAlignment : uint8_t {
TOP,
MIDDLE,
BOTTOM,
};
// Which edge Applet::printAt will place on the X parameter
enum HorizontalAlignment : uint8_t {
LEFT,
RIGHT,
CENTER,
};
// An easy-to-understand interpretation of SNR and RSSI
// Calculate with Applet::getSignalStrength
enum SignalStrength : int8_t {
SIGNAL_UNKNOWN = -1,
SIGNAL_NONE,
SIGNAL_BAD,
SIGNAL_FAIR,
SIGNAL_GOOD,
};
Applet(); Applet();
void setTile(Tile *t); // Should only be called via Tile::setApplet void setTile(Tile *t); // Applets draw via a tile (for multiplexing)
Tile *getTile(); // Tile with which this applet is linked Tile *getTile();
// Rendering void render();
bool wantsToRender(); // Check whether applet wants to render
void render(); // Draw the applet bool wantsToAutoshow(); // Check whether applets wants to become foreground, to show new data, if permitted
bool wantsToRender(); // Check whether applet wants to render
bool wantsToAutoshow(); // Check whether applet wants to become foreground
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
void updateDimensions(); // Get current size from tile void updateDimensions(); // Get current size from tile
void resetDrawingSpace(); // Makes sure every render starts with same parameters void resetDrawingSpace(); // Makes sure every render starts with same parameters
// State of the applet // Change the applet's state
void activate();
void deactivate();
void bringToForeground();
void sendToBackground();
// Info about applet's state
void activate(); // Begin running
void deactivate(); // Stop running
void bringToForeground(); // Show
void sendToBackground(); // Hide
bool isActive(); bool isActive();
bool isForeground(); bool isForeground();
// Event handlers // Allow derived applets to handle changes in state
virtual void onRender() = 0; // All drawing happens here virtual void onRender() = 0; // All drawing happens here
virtual void onActivate() {} virtual void onActivate() {}
@@ -88,62 +150,62 @@ class Applet : public GFX
virtual void onForeground() {} virtual void onForeground() {}
virtual void onBackground() {} virtual void onBackground() {}
virtual void onShutdown() {} virtual void onShutdown() {}
virtual void onButtonShortPress() {} // (System Applets only) virtual void onButtonShortPress() {} // For use by System Applets only
virtual void onButtonLongPress() {} // (System Applets only) virtual void onButtonLongPress() {} // For use by System Applets only
virtual void onLockAvailable() {} // For use by System Applets only
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
static uint16_t getHeaderHeight(); // How tall the "standard" applet header is static void setDefaultFonts(AppletFont large, AppletFont small); // Set the general purpose fonts
static uint16_t getHeaderHeight(); // How tall is the "standard" applet header
static AppletFont fontSmall, fontLarge; // The general purpose fonts, used by all applets const char *name = nullptr; // Shown in applet selection menu
const char *name = nullptr; // Shown in applet selection menu. Also used as an identifier by InkHUD::getSystemApplet
protected: protected:
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here // Place a single pixel. All drawing methods output through here
void drawPixel(int16_t x, int16_t y, uint16_t color) override;
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update // Tell WindowManager to update display
void requestAutoshow(); // Ask for applet to be moved to foreground void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED);
// Ask for applet to be moved to foreground
void requestAutoshow();
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0 uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0 uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
void resetCrop(); // Removes setCrop() void resetCrop(); // Removes setCrop()
// Text
void setFont(AppletFont f); void setFont(AppletFont f);
AppletFont getFont(); AppletFont getFont();
uint16_t getTextWidth(std::string text); uint16_t getTextWidth(std::string text);
uint16_t getTextWidth(const char *text); uint16_t getTextWidth(const char *text);
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped
void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY);
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
// Print text, with per-word line wrapping
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text);
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text);
void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
void drawHeader(std::string text); // Draw the standard applet header void drawHeader(std::string text); // Draw the standard applet header
// Meshtastic Logo
static constexpr float LOGO_ASPECT_RATIO = 1.9; // Width:Height for drawing the Meshtastic logo static constexpr float LOGO_ASPECT_RATIO = 1.9; // Width:Height for drawing the Meshtastic logo
uint16_t getLogoWidth(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region uint16_t getLogoWidth(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
uint16_t getLogoHeight(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region uint16_t getLogoHeight(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height); // Draw the meshtastic logo void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height); // Draw the meshtastic logo
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc std::string hexifyNodeNum(NodeNum num);
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
std::string getTimeString(uint32_t epochSeconds); // Human readable std::string getTimeString(uint32_t epochSeconds); // Human readable
std::string getTimeString(); // Current time, human readable std::string getTimeString(); // Current time, human readable
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
// Convenient references static AppletFont fontSmall, fontLarge; // General purpose fonts, used cross-applet
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
Persistence::LatestMessage *latestMessage = nullptr;
private: private:
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
@@ -161,10 +223,10 @@ class Applet : public GFX
AppletFont currentFont; // As passed to setFont AppletFont currentFont; // As passed to setFont
// As set by setCrop // As set by setCrop
int16_t cropLeft = 0; int16_t cropLeft;
int16_t cropTop = 0; int16_t cropTop;
uint16_t cropWidth = 0; uint16_t cropWidth;
uint16_t cropHeight = 0; uint16_t cropHeight;
}; };
}; // namespace NicheGraphics::InkHUD }; // namespace NicheGraphics::InkHUD

View File

@@ -12,7 +12,7 @@ InkHUD::AppletFont::AppletFont()
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont) InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont)
{ {
// AdafruitGFX fonts are drawn relative to a "cursor line"; // AdafruitGFX fonts are drawn relative to a "cursor line";
// they print as if the glyphs are resting on the line of piece of ruled paper. // they print as if the glyphs resting on the line of piece of ruled paper.
// The glyphs also each have a different height. // The glyphs also each have a different height.
// To simplify drawing, we will scan the entire font now, and determine an appropriate height for a line of text // To simplify drawing, we will scan the entire font now, and determine an appropriate height for a line of text
@@ -42,19 +42,6 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafru
spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance; spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance;
} }
/*
▲ ##### # ▲
│ # # │
lineHeight │ ### # │
│ # # # # │ heightAboveCursor
│ # # # # │
│ # # #### │
│ -----------------#----
│ # │ heightBelowCursor
▼ ### ▼
*/
uint8_t InkHUD::AppletFont::lineHeight() uint8_t InkHUD::AppletFont::lineHeight()
{ {
return this->height; return this->height;
@@ -91,7 +78,7 @@ void InkHUD::AppletFont::addSubstitution(const char *from, const char *to)
substitutions.push_back({.from = from, .to = to}); substitutions.push_back({.from = from, .to = to});
} }
// Run all registered substitutions on a string // Run all registered subtitutions on a string
// Used to swap out UTF8 special chars // Used to swap out UTF8 special chars
void InkHUD::AppletFont::applySubstitutions(std::string *text) void InkHUD::AppletFont::applySubstitutions(std::string *text)
{ {
@@ -100,7 +87,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
// Find and replace // Find and replace
// - search for Substitution::from // - search for Substitution::from
// - replace with Substitution::to // - replace with Subsitution::to
size_t i = text->find(s.from); size_t i = text->find(s.from);
while (i != std::string::npos) { while (i != std::string::npos) {
text->replace(i, strlen(s.from), s.to); text->replace(i, strlen(s.from), s.to);
@@ -110,7 +97,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
} }
// Apply a set of substitutions which remap UTF8 for a Windows-1251 font // Apply a set of substitutions which remap UTF8 for a Windows-1251 font
// Windows-1251 is an 8-bit character encoding, suitable for several languages which use the Cyrillic script // Windows-1251 is an 8-bit character encoding, designed to cover languages that use the Cyrillic script
void InkHUD::AppletFont::addSubstitutionsWin1251() void InkHUD::AppletFont::addSubstitutionsWin1251()
{ {
addSubstitution("Ђ", "\x80"); addSubstitution("Ђ", "\x80");

View File

@@ -15,7 +15,7 @@
#include "configuration.h" #include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib #include <GFX.h>
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
@@ -25,12 +25,11 @@ class AppletFont
{ {
public: public:
AppletFont(); AppletFont();
explicit AppletFont(const GFXfont &adafruitGFXFont); AppletFont(const GFXfont &adafruitGFXFont);
uint8_t lineHeight(); uint8_t lineHeight();
uint8_t heightAboveCursor(); uint8_t heightAboveCursor();
uint8_t heightBelowCursor(); uint8_t heightBelowCursor();
uint8_t widthBetweenWords(); // Width of the space character uint8_t widthBetweenWords();
void applySubstitutions(std::string *text); // Run all char-substitution operations, prior to printing void applySubstitutions(std::string *text); // Run all char-substitution operations, prior to printing
void addSubstitution(const char *from, const char *to); // Register a find-replace action, for remapping UTF8 chars void addSubstitution(const char *from, const char *to); // Register a find-replace action, for remapping UTF8 chars
@@ -51,7 +50,8 @@ class AppletFont
const char *to; const char *to;
}; };
std::vector<Substitution> substitutions; // List of all character substitutions to run, prior to printing a string // List of all character substitutions to run, prior to printing a string
std::vector<Substitution> substitutions;
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -6,6 +6,8 @@ using namespace NicheGraphics;
void InkHUD::MapApplet::onRender() void InkHUD::MapApplet::onRender()
{ {
setFont(fontSmall);
// Abort if no markers to render // Abort if no markers to render
if (!enoughMarkers()) { if (!enoughMarkers()) {
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE); printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
@@ -25,7 +27,6 @@ void InkHUD::MapApplet::onRender()
// Set the region shown on the map // Set the region shown on the map
// - default: fit all nodes, plus padding // - default: fit all nodes, plus padding
// - maybe overriden by derived applet // - maybe overriden by derived applet
// - getMapSize *sets* passed parameters (C-style)
getMapSize(&widthMeters, &heightMeters); getMapSize(&widthMeters, &heightMeters);
// Set the metersToPx conversion value // Set the metersToPx conversion value
@@ -70,7 +71,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
// - uses tan to find angles for lat / long degrees // - uses tan to find angles for lat / long degrees
// - longitude: triangle formed by x and y (on plane of the equator) // - longitude: triangle formed by x and y (on plane of the equator)
// - latitude: triangle formed by z (north south), // - latitude: triangle formed by z (north south),
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface // and the line along plane of equator which stetches from earth's axis to where point xyz intersects planet's surface
// Working totals, averaged after nodeDB processed // Working totals, averaged after nodeDB processed
uint32_t positionCount = 0; uint32_t positionCount = 0;
@@ -133,7 +134,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG; *lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
// Latitude from cartesian coords // Latitude from cartesian cooods
// (Angle from 3D coords describing a point on the globe's surface) // (Angle from 3D coords describing a point on the globe's surface)
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases. // As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step // Means we need to first find the hypotenuse which becomes base of our triangle in the second step
@@ -190,8 +191,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
// Longitude is trickier // Longitude is trickier
float lng = node->position.longitude_i * 1e-7; float lng = node->position.longitude_i * 1e-7;
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees travelled east from lngCenter to reach node
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees travelled west from lngCenter to reach node
if (degEastward < degWestward) if (degEastward < degWestward)
easternmost = max(easternmost, lngCenter + degEastward); easternmost = max(easternmost, lngCenter + degEastward);
else else
@@ -257,7 +258,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
// Find x and y position based on node's position in nodeDB // Find x and y position based on node's position in nodeDB
assert(nodeDB->hasValidPosition(node)); assert(nodeDB->hasValidPosition(node));
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away node->hops_away // Hops away
); );
@@ -287,7 +288,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
bool unknownHops = !node->has_hops_away && !isOurNode; bool unknownHops = !node->has_hops_away && !isOurNode;
// We will draw a left or right hand variant, to place text towards screen center // We will draw a left or right hand variant, to place text towards screen center
// Hopefully avoid text spilling off screen // Hopfully avoid text spilling off screen
// Most values are the same, regardless of left-right handedness // Most values are the same, regardless of left-right handedness
// Pick emblem style // Pick emblem style
@@ -387,7 +388,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
// Calculate marker and store it // Calculate marker and store it
markers.push_back( markers.push_back(
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away node->hops_away // Hops away
)); ));

View File

@@ -38,12 +38,13 @@ class MapApplet : public Applet
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
private: private:
// Position and size of a marker to be drawn // Position of markers to be drawn, relative to map center
// HopsAway info used to determine marker size
struct Marker { struct Marker {
float eastMeters = 0; // Meters east of map center. Negative if west. float eastMeters = 0; // Meters east of mapCenter. Negative if west.
float northMeters = 0; // Meters north of map center. Negative if south. float northMeters = 0; // Meters north of mapCenter. Negative if south.
bool hasHopsAway = false; bool hasHopsAway = false;
uint8_t hopsAway = 0; // Determines marker size uint8_t hopsAway = 0;
}; };
Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway); Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway);

View File

@@ -12,7 +12,7 @@ using namespace NicheGraphics;
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name) InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
{ {
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule // We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
// For all other packets, we manually act as if isPromiscuous=false, in wantPacket // For all other packets, we manually reimplement isPromiscuous=false in wantPacket
MeshModule::isPromiscuous = true; MeshModule::isPromiscuous = true;
} }
@@ -25,17 +25,17 @@ bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us, && (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo
// Note: special handling of NodeInfo is to match NodeInfoModule
// To match the behavior seen in the client apps: // To match the behavior seen in the client apps:
// - NodeInfoModule's ProtoBufModule base is "promiscuous" // - NodeInfoModule's ProtoBufModule base is "promiscuous"
// - All other activity is *not* promiscuous // - All other activity is *not* promiscuous
// To achieve this, our MeshModule *is* promiscious, and we're manually reimplementing non-promiscuous behavior here,
// To achieve this, our MeshModule *is* promiscuous, and we're manually reimplementing non-promiscuous behavior here,
// to match the code in MeshModule::callModules // to match the code in MeshModule::callModules
} }
// MeshModule packets arrive here // MeshModule packets arrive here
// Extract the info and pass it to the derived applet // Extract the info and pass it to the derived applet
// Derived applet will store the CardInfo, and perform any required sorting of the CardInfo collection // Derived applet will store the CardInfo and perform any required sorting of the CardInfo collection
// Derived applet might also need to keep other tallies (active nodes count?) // Derived applet might also need to keep other tallies (active nodes count?)
ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp)
{ {
@@ -76,8 +76,8 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
return ProcessMessage::CONTINUE; // Let others look at this message also if they want return ProcessMessage::CONTINUE; // Let others look at this message also if they want
} }
// Calculate maximum number of cards we may ever need to render, in our tallest layout config // Maximum number of cards we may ever need to render, in our tallest layout config
// Number might be slightly in excess of the true value: applet header text not accounted for // May be slightly in excess of the true value: header not accounted for
uint8_t InkHUD::NodeListApplet::maxCards() uint8_t InkHUD::NodeListApplet::maxCards()
{ {
// Cache result. Shouldn't change during execution // Cache result. Shouldn't change during execution
@@ -87,7 +87,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
const uint16_t height = Tile::maxDisplayDimension(); const uint16_t height = Tile::maxDisplayDimension();
// Use a loop instead of arithmetic, because it's easier for my brain to follow // Use a loop instead of arithmetic, because it's easier for my brain to follow
// Add cards one by one, until the latest card extends below screen // Add cards one by one, until the latest card (without margin) extends below screen
uint16_t y = cardH; // First card: no margin above uint16_t y = cardH; // First card: no margin above
cards = 1; cards = 1;
@@ -102,7 +102,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
return cards; return cards;
} }
// Draw, using info which derived applet placed into NodeListApplet::cards for us // Draw using info which derived applet placed into NodeListApplet::cards for us
void InkHUD::NodeListApplet::onRender() void InkHUD::NodeListApplet::onRender()
{ {
@@ -120,6 +120,9 @@ void InkHUD::NodeListApplet::onRender()
// Draw the main node list // Draw the main node list
// ======================== // ========================
// const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
// const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH;
// Imaginary vertical line dividing left-side and right-side info // Imaginary vertical line dividing left-side and right-side info
// Long-name will crop here // Long-name will crop here
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops"); const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
@@ -212,8 +215,9 @@ void InkHUD::NodeListApplet::onRender()
// Once we've run out of screen, stop drawing cards // Once we've run out of screen, stop drawing cards
// Depending on tiles / rotation, this may be before we hit maxCards // Depending on tiles / rotation, this may be before we hit maxCards
if (cardTopY > height()) if (cardTopY > height()) {
break; break;
}
} }
} }
@@ -242,20 +246,20 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
constexpr float paddingW = 0.1; // Either side constexpr float paddingW = 0.1; // Either side
constexpr float paddingH = 0.1; // Above and below constexpr float paddingH = 0.1; // Above and below
constexpr float gutterW = 0.1; // Between bars constexpr float gutterX = 0.1; // Between bars
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the tallest constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the talleest
constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count. constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count.
// Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions // Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterW) + paddingW)) / barCount; float barW = (1.0 - (paddingW + ((barCount - 1) * gutterX) + paddingW)) / barCount;
float barHMax = 1.0 - (paddingH + paddingH); float barHMax = 1.0 - (paddingH + paddingH);
// Draw signal bar rectangles, then placeholder lines once strength reached // Draw signal bar rectangles, then placeholder lines once strength reached
for (uint8_t i = 0; i < barCount; i++) { for (uint8_t i = 0; i < barCount; i++) {
// Coords for this specific bar // Co-ords for this specific bar
float barH = barHMax * barHRel[i]; float barH = barHMax * barHRel[i];
float barX = paddingW + (i * (gutterW + barW)); float barX = paddingW + (i * (gutterX + barW));
float barY = paddingH + (barHMax - barH); float barY = paddingH + (barHMax - barH);
// Rasterize to px coords at the last moment // Rasterize to px coords at the last moment

View File

@@ -23,16 +23,13 @@ Used by the "Recents" and "Heard" applets. Possibly more in future?
#include "graphics/niche/InkHUD/Applet.h" #include "graphics/niche/InkHUD/Applet.h"
#include "main.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class NodeListApplet : public Applet, public MeshModule class NodeListApplet : public Applet, public MeshModule
{ {
protected: protected:
// Info needed to draw a node card to the list // Info used to draw one card to the node list
// - generated each time we hear a node
struct CardInfo { struct CardInfo {
static constexpr uint8_t HOPS_UNKNOWN = -1; static constexpr uint8_t HOPS_UNKNOWN = -1;
static constexpr uint32_t DISTANCE_UNKNOWN = -1; static constexpr uint32_t DISTANCE_UNKNOWN = -1;
@@ -40,31 +37,31 @@ class NodeListApplet : public Applet, public MeshModule
NodeNum nodeNum = 0; NodeNum nodeNum = 0;
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN; SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
uint32_t distanceMeters = DISTANCE_UNKNOWN; uint32_t distanceMeters = DISTANCE_UNKNOWN;
uint8_t hopsAway = HOPS_UNKNOWN; uint8_t hopsAway = HOPS_UNKNOWN; // Unknown
}; };
public: public:
NodeListApplet(const char *name); NodeListApplet(const char *name);
void onRender() override; void onRender() override;
bool wantPacket(const meshtastic_MeshPacket *p) override; // MeshModule overrides
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
protected: protected:
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node virtual void handleParsed(CardInfo c) = 0; // Pass extracted info from a new packet to derived class, for sorting and storage
virtual std::string getHeaderText() = 0; // Ask derived class what the applet's title should be virtual std::string getHeaderText() = 0; // Title for the applet's header. Todo: get this info another way?
uint8_t maxCards(); // Max number of cards which could ever fit on screen uint8_t maxCards(); // Calculate the maximum number of cards an applet could ever display
std::deque<CardInfo> cards; // Cards to be rendered. Derived applet fills this. std::deque<CardInfo> cards; // Derived applet places cards here, for this base applet to render
private: private:
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, // UI element: a "mobile phone" style signal indicator
SignalStrength signal); // Draw a "mobile phone" style signal indicator void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength signal);
// Card Dimensions // Dimensions for drawing
// - for rendering and for maxCards calc // Used for render, and also for maxCards calc
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
}; };

View File

@@ -36,6 +36,8 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
// We should always be ready to draw // We should always be ready to draw
void InkHUD::NewMsgExampleApplet::onRender() void InkHUD::NewMsgExampleApplet::onRender()
{ {
setFont(fontSmall);
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0) printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
int16_t centerX = X(0.5); // Same as width() / 2 int16_t centerX = X(0.5); // Same as width() / 2

View File

@@ -53,7 +53,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
// Store info from handleReceived // Store info from handleReceived
bool haveMessage = false; bool haveMessage = false;
NodeNum fromWho = 0; NodeNum fromWho;
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -4,10 +4,10 @@
using namespace NicheGraphics; using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet() void InkHUD::BatteryIconApplet::onActivate()
{ {
// Show at boot, if user has previously enabled the feature // Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon) if (settings.optionalFeatures.batteryIcon)
bringToForeground(); bringToForeground();
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available // Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
@@ -15,6 +15,12 @@ InkHUD::BatteryIconApplet::BatteryIconApplet()
powerStatusObserver.observe(&powerStatus->onNewStatus); powerStatusObserver.observe(&powerStatus->onNewStatus);
} }
void InkHUD::BatteryIconApplet::onDeactivate()
{
// Stop having onPowerStatusUpdate called
powerStatusObserver.unobserve(&powerStatus->onNewStatus);
}
// We handle power status' even when the feature is disabled, // We handle power status' even when the feature is disabled,
// so that we have up to date data ready if the feature is enabled later. // so that we have up to date data ready if the feature is enabled later.
// Otherwise could be 30s before new status update, with weird battery value displayed // Otherwise could be 30s before new status update, with weird battery value displayed
@@ -35,7 +41,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
// If rounded value has changed, trigger a display update // If rounded value has changed, trigger a display update
// It's okay to requestUpdate before we store the new value, as the update won't run until next loop() // It's okay to requestUpdate before we store the new value, as the update won't run until next loop()
// Don't trigger an update if the feature is disabled // Don't trigger an update if the feature is disabled
if (this->socRounded != newSocRounded && settings->optionalFeatures.batteryIcon) if (this->socRounded != newSocRounded && settings.optionalFeatures.batteryIcon)
requestUpdate(); requestUpdate();
// Store the new value // Store the new value

View File

@@ -11,22 +11,24 @@ It should be optional, enabled by the on-screen menu
#include "configuration.h" #include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
#include "PowerStatus.h" #include "PowerStatus.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class BatteryIconApplet : public SystemApplet class BatteryIconApplet : public Applet
{ {
public: public:
BatteryIconApplet();
void onRender() override; void onRender() override;
void onActivate() override;
void onDeactivate() override;
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
private: protected:
// Get informed when new information about the battery is available (via onPowerStatusUpdate method) // Get informed when new information about the battery is available (via onPowerStatusUpdate method)
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver = CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate); CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);

View File

@@ -2,22 +2,15 @@
#include "./LogoApplet.h" #include "./LogoApplet.h"
#include "mesh/NodeDB.h"
using namespace NicheGraphics; using namespace NicheGraphics;
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet") InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
{ {
OSThread::setIntervalFromNow(8 * 1000UL); // Don't autostart the runOnce() timer
OSThread::enabled = true; OSThread::disable();
textLeft = ""; // Grab the WindowManager singleton, for convenience
textRight = ""; windowManager = WindowManager::getInstance();
textTitle = xstr(APP_VERSION_SHORT);
fontTitle = fontSmall;
bringToForeground();
// This is then drawn with a FULL refresh by Renderer::begin
} }
void InkHUD::LogoApplet::onRender() void InkHUD::LogoApplet::onRender()
@@ -55,24 +48,53 @@ void InkHUD::LogoApplet::onRender()
void InkHUD::LogoApplet::onForeground() void InkHUD::LogoApplet::onForeground()
{ {
SystemApplet::lockRendering = true; // If another applet has locked the display, ask it to exit
SystemApplet::lockRequests = true; Applet *other = windowManager->whoLocked();
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it. if (other != nullptr)
other->sendToBackground();
windowManager->claimFullscreen(this); // Take ownership of fullscreen tile
windowManager->lock(this); // Prevent other applets from requesting updates
} }
void InkHUD::LogoApplet::onBackground() void InkHUD::LogoApplet::onBackground()
{ {
SystemApplet::lockRendering = false; OSThread::disable(); // Disable auto-dismiss timer, in case applet was dismissed early (sendToBackground from outside class)
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false; windowManager->releaseFullscreen(); // Relinquish ownership of fullscreen tile
windowManager->unlock(this); // Allow normal user applet update requests to resume
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL); windowManager->forceUpdate(EInk::UpdateTypes::FULL);
}
int32_t InkHUD::LogoApplet::runOnce()
{
LOG_DEBUG("Sent to background by timer");
sendToBackground();
return OSThread::disable();
}
// Begin displaying the screen which is shown at startup
// Suggest EInk::await after calling this method
void InkHUD::LogoApplet::showBootScreen()
{
OSThread::setIntervalFromNow(8 * 1000UL);
OSThread::enabled = true;
textLeft = "";
textRight = "";
textTitle = xstr(APP_VERSION_SHORT);
fontTitle = fontSmall;
bringToForeground();
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
} }
// Begin displaying the screen which is shown at shutdown // Begin displaying the screen which is shown at shutdown
void InkHUD::LogoApplet::onShutdown() // Needs EInk::await after calling this method, to ensure display updates before shutdown
void InkHUD::LogoApplet::showShutdownScreen()
{ {
textLeft = ""; textLeft = "";
textRight = ""; textRight = "";
@@ -80,13 +102,7 @@ void InkHUD::LogoApplet::onShutdown()
fontTitle = fontLarge; fontTitle = fontLarge;
bringToForeground(); bringToForeground();
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
}
int32_t InkHUD::LogoApplet::runOnce()
{
sendToBackground();
return OSThread::disable();
} }
#endif #endif

View File

@@ -12,19 +12,24 @@
#include "configuration.h" #include "configuration.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class LogoApplet : public SystemApplet, public concurrency::OSThread class LogoApplet : public Applet, public concurrency::OSThread
{ {
public: public:
LogoApplet(); LogoApplet();
void onRender() override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onShutdown() override;
// Note: interacting directly with an applet like this is non-standard
// Only permitted because this is a "system applet", which has special behavior and interacts directly with WindowManager
void showBootScreen();
void showShutdownScreen();
protected: protected:
int32_t runOnce() override; int32_t runOnce() override;
@@ -33,6 +38,8 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
std::string textRight; std::string textRight;
std::string textTitle; std::string textTitle;
AppletFont fontTitle; AppletFont fontTitle;
WindowManager *windowManager = nullptr; // For convenience
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -2,11 +2,9 @@
#include "./MenuApplet.h" #include "./MenuApplet.h"
#include "PowerStatus.h"
#include "RTC.h" #include "RTC.h"
#include "airtime.h"
#include "power.h"
using namespace NicheGraphics; using namespace NicheGraphics;
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
@@ -19,16 +17,23 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
{ {
// No timer tasks at boot // No timer tasks at boot
OSThread::disable(); OSThread::disable();
}
void InkHUD::MenuApplet::onActivate()
{
// Grab pointers to some singleton components which the menu interacts with
// We could do this every time we needed them, in place,
// but this just makes the code tidier
this->windowManager = WindowManager::getInstance();
// Note: don't get instance if we're not actually using the backlight, // Note: don't get instance if we're not actually using the backlight,
// or else you will unintentionally instantiate it // or else you will unintentionally instantiate it
if (settings->optionalMenuItems.backlight) { if (settings.optionalMenuItems.backlight) {
backlight = Drivers::LatchingBacklight::getInstance(); backlight = Drivers::LatchingBacklight::getInstance();
} }
} }
void InkHUD::MenuApplet::onActivate() {}
void InkHUD::MenuApplet::onForeground() void InkHUD::MenuApplet::onForeground()
{ {
// We do need this before we render, but we can optimize by just calculating it once now // We do need this before we render, but we can optimize by just calculating it once now
@@ -40,23 +45,21 @@ void InkHUD::MenuApplet::onForeground()
// If device has a backlight which isn't controlled by aux button: // If device has a backlight which isn't controlled by aux button:
// backlight on always when menu opens. // backlight on always when menu opens.
// Courtesy to T-Echo users who removed the capacitive touch button // Courtesy to T-Echo users who removed the capacitive touch button
if (settings->optionalMenuItems.backlight) { if (settings.optionalMenuItems.backlight) {
assert(backlight); assert(backlight);
if (!backlight->isOn()) if (!backlight->isOn())
backlight->peek(); backlight->peek();
} }
// Prevent user applets requesting update while menu is open // Prevent user applets requested update while menu is open
// Handle button input with this applet windowManager->lock(this);
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true;
// Begin the auto-close timeout // Begin the auto-close timeout
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
OSThread::enabled = true; OSThread::enabled = true;
// Upgrade the refresh to FAST, for guaranteed responsiveness // Upgrade the refresh to FAST, for guaranteed responsiveness
inkhud->forceUpdate(EInk::UpdateTypes::FAST); windowManager->forceUpdate(EInk::UpdateTypes::FAST);
} }
void InkHUD::MenuApplet::onBackground() void InkHUD::MenuApplet::onBackground()
@@ -64,7 +67,7 @@ void InkHUD::MenuApplet::onBackground()
// If device has a backlight which isn't controlled by aux button: // If device has a backlight which isn't controlled by aux button:
// Item in options submenu allows keeping backlight on after menu is closed // Item in options submenu allows keeping backlight on after menu is closed
// If this item is deselected we will turn backlight off again, now that menu is closing // If this item is deselected we will turn backlight off again, now that menu is closing
if (settings->optionalMenuItems.backlight) { if (settings.optionalMenuItems.backlight) {
assert(backlight); assert(backlight);
if (!backlight->isLatched()) if (!backlight->isLatched())
backlight->off(); backlight->off();
@@ -74,8 +77,7 @@ void InkHUD::MenuApplet::onBackground()
OSThread::disable(); OSThread::disable();
// Resume normal rendering and button behavior of user applets // Resume normal rendering and button behavior of user applets
SystemApplet::lockRequests = false; windowManager->unlock(this);
SystemApplet::handleInput = false;
// Restore the user applet whose tile we borrowed // Restore the user applet whose tile we borrowed
if (borrowedTileOwner) if (borrowedTileOwner)
@@ -85,8 +87,8 @@ void InkHUD::MenuApplet::onBackground()
borrowedTileOwner = nullptr; borrowedTileOwner = nullptr;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// We're only updating here to upgrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu // We're only updating here to ugrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
inkhud->forceUpdate(EInk::UpdateTypes::FAST); windowManager->forceUpdate(EInk::UpdateTypes::FAST);
} }
// Open the menu // Open the menu
@@ -138,35 +140,43 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break; break;
case NEXT_TILE: case NEXT_TILE:
inkhud->nextTile(); // Note performed manually;
// WindowManager::nextTile is raised by aux button press only, and will interact poorly with the menu
settings.userTiles.focused = (settings.userTiles.focused + 1) % settings.userTiles.count;
windowManager->changeLayout();
cursor = 0; // No menu item selected, for quick exit after tile swap
cursorShown = false;
break; break;
case ROTATE: case ROTATE:
inkhud->rotate(); settings.rotation = (settings.rotation + 1) % 4;
windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
break; break;
case LAYOUT: case LAYOUT:
// Todo: smarter incrementing of tile count // Todo: smarter incrementing of tile count
settings->userTiles.count++; settings.userTiles.count++;
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet if (settings.userTiles.count == 3) // Skip 3 tiles: not done yet
settings->userTiles.count++; settings.userTiles.count++;
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high if (settings.userTiles.count > settings.userTiles.maxCount) // Loop around if tile count now too high
settings->userTiles.count = 1; settings.userTiles.count = 1;
inkhud->updateLayout(); windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
break; break;
case TOGGLE_APPLET: case TOGGLE_APPLET:
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor]; settings.userApplets.active[cursor] = !settings.userApplets.active[cursor];
inkhud->updateAppletSelection(); windowManager->changeActivatedApplets();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit // requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
break; break;
case ACTIVATE_APPLETS: case ACTIVATE_APPLETS:
// Todo: remove this action? Already handled by TOGGLE_APPLET? // Todo: remove this action? Already handled by TOGGLE_APPLET?
inkhud->updateAppletSelection(); windowManager->changeActivatedApplets();
break; break;
case TOGGLE_AUTOSHOW_APPLET: case TOGGLE_AUTOSHOW_APPLET:
@@ -175,14 +185,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break; break;
case TOGGLE_NOTIFICATIONS: case TOGGLE_NOTIFICATIONS:
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications; settings.optionalFeatures.notifications = !settings.optionalFeatures.notifications;
break; break;
case SET_RECENTS: case SET_RECENTS:
// Set value of settings.recentlyActiveSeconds // Set value of settings.recentlyActiveSeconds
// Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file) // Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file)
assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0])); assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]));
settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes settings.recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
break; break;
case SHUTDOWN: case SHUTDOWN:
@@ -192,7 +202,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break; break;
case TOGGLE_BATTERY_ICON: case TOGGLE_BATTERY_ICON:
inkhud->toggleBatteryIcon(); windowManager->toggleBatteryIcon();
break; break;
case TOGGLE_BACKLIGHT: case TOGGLE_BACKLIGHT:
@@ -223,13 +233,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
switch (page) { switch (page) {
case ROOT: case ROOT:
// Optional: next applet // Optional: next applet
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1) if (settings.optionalMenuItems.nextTile && settings.userTiles.count > 1)
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
// items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO // items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO
items.push_back(MenuItem("Options", MenuPage::OPTIONS)); items.push_back(MenuItem("Options", MenuPage::OPTIONS));
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO // items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Save & Shutdown", MenuAction::SHUTDOWN));
items.push_back(MenuItem("Exit", MenuPage::EXIT)); items.push_back(MenuItem("Exit", MenuPage::EXIT));
break; break;
@@ -242,7 +252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
case OPTIONS: case OPTIONS:
// Optional: backlight // Optional: backlight
if (settings->optionalMenuItems.backlight) { if (settings.optionalMenuItems.backlight) {
assert(backlight); assert(backlight);
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
MenuAction::TOGGLE_BACKLIGHT, // Action MenuAction::TOGGLE_BACKLIGHT, // Action
@@ -253,13 +263,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(MenuItem("Applets", MenuPage::APPLETS)); items.push_back(MenuItem("Applets", MenuPage::APPLETS));
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW)); items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS)); items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS));
if (settings->userTiles.maxCount > 1) if (settings.userTiles.maxCount > 1)
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS)); items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS)); items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS, items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
&settings->optionalFeatures.notifications)); &settings.optionalFeatures.notifications));
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, items.push_back(
&settings->optionalFeatures.batteryIcon)); MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings.optionalFeatures.batteryIcon));
// TODO - GPS and Wifi switches // TODO - GPS and Wifi switches
/* /*
@@ -319,6 +329,9 @@ void InkHUD::MenuApplet::onRender()
if (items.size() == 0) if (items.size() == 0)
LOG_ERROR("Empty Menu"); LOG_ERROR("Empty Menu");
// Testing only
setFont(fontSmall);
// Dimensions for the slots where we will draw menuItems // Dimensions for the slots where we will draw menuItems
const float padding = 0.05; const float padding = 0.05;
const uint16_t itemH = fontSmall.lineHeight() * 2; const uint16_t itemH = fontSmall.lineHeight() * 2;
@@ -384,7 +397,7 @@ void InkHUD::MenuApplet::onRender()
// Testing only: circle instead of check box // Testing only: circle instead of check box
if (item.checkState) { if (item.checkState) {
const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height const uint16_t cbWH = fontSmall.lineHeight(); // Checbox: width / height
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
const int16_t cbT = center - (cbWH / 2); // Checkbox : top const int16_t cbT = center - (cbWH / 2); // Checkbox : top
// Checkbox ticked // Checkbox ticked
@@ -450,9 +463,9 @@ void InkHUD::MenuApplet::populateAppletPage()
{ {
assert(items.size() == 0); assert(items.size() == 0);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) { for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
const char *name = inkhud->userApplets.at(i)->name; const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings->userApplets.active[i]); bool *isActive = &(settings.userApplets.active[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive)); items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
} }
} }
@@ -464,11 +477,11 @@ void InkHUD::MenuApplet::populateAutoshowPage()
{ {
assert(items.size() == 0); assert(items.size() == 0);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) { for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
// Only add a menu item if applet is active // Only add a menu item if applet is active
if (settings->userApplets.active[i]) { if (settings.userApplets.active[i]) {
const char *name = inkhud->userApplets.at(i)->name; const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings->userApplets.autoshow[i]); bool *isActive = &(settings.userApplets.autoshow[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive)); items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
} }
} }
@@ -586,10 +599,10 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
// Get the height of the the panel drawn at the top of the menu // Get the height of the the panel drawn at the top of the menu
// This is inefficient, as we do actually have to render the panel to determine the height // This is inefficient, as we do actually have to render the panel to determine the height
// It solves a catch-22 situation, where slotCount needs to know panel height, and panel height needs to know slotCount // It solves a catch-22 situtation, where slotCount needs to know panel height, and panel height needs to know slotCount
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight() uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
{ {
// Render *far* off screen // Render *waay* off screen
uint16_t height = 0; uint16_t height = 0;
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height); drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);

View File

@@ -3,9 +3,8 @@
#include "configuration.h" #include "configuration.h"
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" #include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
#include "graphics/niche/InkHUD/InkHUD.h" #include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/Persistence.h" #include "graphics/niche/InkHUD/WindowManager.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "./MenuItem.h" #include "./MenuItem.h"
#include "./MenuPage.h" #include "./MenuPage.h"
@@ -17,7 +16,7 @@ namespace NicheGraphics::InkHUD
class Applet; class Applet;
class MenuApplet : public SystemApplet, public concurrency::OSThread class MenuApplet : public Applet, public concurrency::OSThread
{ {
public: public:
MenuApplet(); MenuApplet();
@@ -31,8 +30,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void show(Tile *t); // Open the menu, onto a user tile void show(Tile *t); // Open the menu, onto a user tile
protected: protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
int32_t runOnce() override; int32_t runOnce() override;
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
@@ -44,7 +41,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
uint16_t *height = nullptr); // Info panel at top of root menu uint16_t *height = nullptr); // Info panel at top of root menu
MenuPage currentPage = MenuPage::ROOT; MenuPage currentPage;
uint8_t cursor = 0; // Which menu item is currently highlighted uint8_t cursor = 0; // Which menu item is currently highlighted
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection) bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
@@ -53,6 +50,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
WindowManager *windowManager = nullptr; // Convenient access to the InkHUD::WindowManager singleton
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -3,20 +3,22 @@
#include "./NotificationApplet.h" #include "./NotificationApplet.h"
#include "./Notification.h" #include "./Notification.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
#include "RTC.h" #include "RTC.h"
using namespace NicheGraphics; using namespace NicheGraphics;
InkHUD::NotificationApplet::NotificationApplet() void InkHUD::NotificationApplet::onActivate()
{ {
textMessageObserver.observe(textMessageModule); textMessageObserver.observe(textMessageModule);
} }
// Note: This applet probably won't ever be deactivated
void InkHUD::NotificationApplet::onDeactivate()
{
textMessageObserver.unobserve(textMessageModule);
}
// Collect meta-info about the text message, and ask for approval for the notification // Collect meta-info about the text message, and ask for approval for the notification
// No need to save the message itself; we can use the cached InkHUD::latestMessage data during render() // No need to save the message itself; we can use the cached InkHUD::latestMessage data during render()
int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p) int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
@@ -26,7 +28,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
// Abort if feature disabled // Abort if feature disabled
// This is a bit clumsy, but avoids complicated handling when the feature is enabled / disabled // This is a bit clumsy, but avoids complicated handling when the feature is enabled / disabled
if (!settings->optionalFeatures.notifications) if (!settings.optionalFeatures.notifications)
return 0; return 0;
// Abort if this is an outgoing message // Abort if this is an outgoing message
@@ -34,7 +36,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
return 0; return 0;
// Abort if message was only an "emoji reaction" // Abort if message was only an "emoji reaction"
// Possibly some implementation of this in future? // Possibly some implemetation of this in future?
if (p->decoded.emoji) if (p->decoded.emoji)
return 0; return 0;
@@ -53,16 +55,13 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
n.sender = p->from; n.sender = p->from;
} }
// Close an old notification, if shown
dismiss();
// Check if we should display the notification // Check if we should display the notification
// A foreground applet might already be displaying this info // A foreground applet might already be displaying this info
hasNotification = true; hasNotification = true;
currentNotification = n; currentNotification = n;
if (isApproved()) { if (isApproved()) {
bringToForeground(); bringToForeground();
inkhud->forceUpdate(); WindowManager::getInstance()->forceUpdate();
} else } else
hasNotification = false; // Clear the pending notification: it was rejected hasNotification = false; // Clear the pending notification: it was rejected
@@ -77,6 +76,8 @@ void InkHUD::NotificationApplet::onRender()
// We do need to do this with the battery though, as it is an "overlay" // We do need to do this with the battery though, as it is an "overlay"
fillRect(0, 0, width(), height(), WHITE); fillRect(0, 0, width(), height(), WHITE);
setFont(fontSmall);
// Padding (horizontal) // Padding (horizontal)
const uint16_t padW = 4; const uint16_t padW = 4;
@@ -136,28 +137,6 @@ void InkHUD::NotificationApplet::onRender()
printThick(textM, height() / 2, text, 2, 1); printThick(textM, height() / 2, text, 2, 1);
} }
void InkHUD::NotificationApplet::onForeground()
{
handleInput = true; // Intercept the button input for our applet, so we can dismiss the notification
}
void InkHUD::NotificationApplet::onBackground()
{
handleInput = false;
}
void InkHUD::NotificationApplet::onButtonShortPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onButtonLongPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification // Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
// Called internally when we first get a "notifiable event", and then again before render, // Called internally when we first get a "notifiable event", and then again before render,
// in case autoshow swapped which applet was displayed // in case autoshow swapped which applet was displayed
@@ -169,13 +148,7 @@ bool InkHUD::NotificationApplet::isApproved()
return false; return false;
} }
// Ask all visible user applets for approval return WindowManager::getInstance()->approveNotification(currentNotification);
for (Applet *ua : inkhud->userApplets) {
if (ua->isForeground() && !ua->approveNotification(currentNotification))
return false;
}
return true;
} }
// Mark that the notification should no-longer be rendered // Mark that the notification should no-longer be rendered
@@ -207,8 +180,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST; bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
// Pick source of message // Pick source of message
MessageStore::Message *message = MessageStore::Message *message = isBroadcast ? &latestMessage.broadcast : &latestMessage.dm;
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
// Find info about the sender // Find info about the sender
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender); meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);

View File

@@ -3,7 +3,7 @@
/* /*
Pop-up notification bar, on screen top edge Pop-up notification bar, on screen top edge
Displays information we feel is important, but which is not shown on currently focused applet(s) Displays information we feel is important, but which is not shown on currently focussed applet(s)
E.g.: messages, while viewing map, etc E.g.: messages, while viewing map, etc
Feature should be optional; enable disable via on-screen menu Feature should be optional; enable disable via on-screen menu
@@ -16,21 +16,17 @@ Feature should be optional; enable disable via on-screen menu
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class NotificationApplet : public SystemApplet class NotificationApplet : public Applet
{ {
public: public:
NotificationApplet();
void onRender() override; void onRender() override;
void onForeground() override; void onActivate() override;
void onBackground() override; void onDeactivate() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p); int onReceiveTextMessage(const meshtastic_MeshPacket *p);
@@ -44,8 +40,8 @@ class NotificationApplet : public SystemApplet
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
bool hasNotification = false; // Only used for assert. Todo: remove? bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render() Notification currentNotification; // Set when something notification-worthy happens. Used by render()
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -6,7 +6,8 @@ using namespace NicheGraphics;
InkHUD::PairingApplet::PairingApplet() InkHUD::PairingApplet::PairingApplet()
{ {
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); // Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
} }
void InkHUD::PairingApplet::onRender() void InkHUD::PairingApplet::onRender()
@@ -30,22 +31,34 @@ void InkHUD::PairingApplet::onRender()
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE); printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
} }
void InkHUD::PairingApplet::onActivate()
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onDeactivate()
{
bluetoothStatusObserver.unobserve(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onForeground() void InkHUD::PairingApplet::onForeground()
{ {
// Prevent most other applets from requesting update, and skip their rendering entirely // If another applet has locked the display, ask it to exit
// Another system applet with a higher precedence can potentially ignore this Applet *other = windowManager->whoLocked();
SystemApplet::lockRendering = true; if (other != nullptr)
SystemApplet::lockRequests = true; other->sendToBackground();
windowManager->claimFullscreen(this); // Take ownership of the fullscreen tile
windowManager->lock(this); // Prevent user applets from requesting update
} }
void InkHUD::PairingApplet::onBackground() void InkHUD::PairingApplet::onBackground()
{ {
// Allow normal update behavior to resume windowManager->releaseFullscreen(); // Relinquish ownership of the fullscreen tile
SystemApplet::lockRendering = false; windowManager->unlock(this); // Allow normal user applet update requests to resume
SystemApplet::lockRequests = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL); windowManager->forceUpdate(EInk::UpdateTypes::FULL);
} }
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status) int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
@@ -62,6 +75,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
// Store the passkey for rendering // Store the passkey for rendering
passkey = bluetoothStatus->getPasskey(); passkey = bluetoothStatus->getPasskey();
// Make sure no other system applets have a lock on the display
// Boot screen, menu, etc
Applet *lockOwner = windowManager->whoLocked();
if (lockOwner)
lockOwner->sendToBackground();
// Show pairing screen // Show pairing screen
bringToForeground(); bringToForeground();
} }

View File

@@ -10,19 +10,19 @@
#include "configuration.h" #include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
#include "main.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class PairingApplet : public SystemApplet class PairingApplet : public Applet
{ {
public: public:
PairingApplet(); PairingApplet();
void onRender() override; void onRender() override;
void onActivate() override;
void onDeactivate() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
@@ -34,6 +34,8 @@ class PairingApplet : public SystemApplet
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate); CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
WindowManager *windowManager = nullptr; // For convenience. Set in constructor.
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -4,6 +4,14 @@
using namespace NicheGraphics; using namespace NicheGraphics;
InkHUD::PlaceholderApplet::PlaceholderApplet()
{
// Because this applet sometimes gets processed as if it were a bonafide user applet,
// it's probably better that we do give it a human readable name, just in case it comes up later.
// For genuine user applets, this is set by WindowManager::addApplet
Applet::name = "Placeholder";
}
void InkHUD::PlaceholderApplet::onRender() void InkHUD::PlaceholderApplet::onRender()
{ {
// This placeholder applet fills its area with sparse diagonal lines // This placeholder applet fills its area with sparse diagonal lines

View File

@@ -9,19 +9,20 @@ Fills the area with diagonal lines
#include "configuration.h" #include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class PlaceholderApplet : public SystemApplet class PlaceholderApplet : public Applet
{ {
public: public:
PlaceholderApplet();
void onRender() override; void onRender() override;
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet. // Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
// The window manager decides when and where it should be rendered // The window manager decides when and where it should be rendered
// It may be drawn to several different tiles during an Renderer::render call // It may be drawn to several different tiles during on WindowManager::render call
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -2,44 +2,12 @@
#include "./TipsApplet.h" #include "./TipsApplet.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "main.h"
using namespace NicheGraphics; using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet() InkHUD::TipsApplet::TipsApplet()
{ {
// Decide which tips (if any) should be shown to user after the boot screen // Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
// Welcome screen
if (settings->tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// Antenna, region, timezone
// Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
tipQueue.push_back(Tip::FINISH_SETUP);
// Shutdown info
// Shown until user performs one valid shutdown
if (!settings->tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings->tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display
if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION);
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
if (!tipQueue.empty())
bringToForeground();
} }
void InkHUD::TipsApplet::onRender() void InkHUD::TipsApplet::onRender()
@@ -85,7 +53,7 @@ void InkHUD::TipsApplet::onRender()
setFont(fontSmall); setFont(fontSmall);
std::string shutdown; std::string shutdown;
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n"; shutdown += "Before removing power, please shutdown from InkHUD menu, or a client app. \n";
shutdown += "\n"; shutdown += "\n";
shutdown += "This ensures data is saved."; shutdown += "This ensures data is saved.";
printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown); printWrapped(0, fontLarge.lineHeight() * 1.5, width(), shutdown);
@@ -185,31 +153,51 @@ void InkHUD::TipsApplet::renderWelcome()
printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM); printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
} }
// Grab fullscreen tile, and lock the window manager, when applet is shown
void InkHUD::TipsApplet::onForeground() void InkHUD::TipsApplet::onForeground()
{ {
// Prevent most other applets from requesting update, and skip their rendering entirely windowManager->lock(this);
// Another system applet with a higher precedence can potentially ignore this windowManager->claimFullscreen(this);
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // Our applet should handle button input (unless another system applet grabs it first)
} }
void InkHUD::TipsApplet::onBackground() void InkHUD::TipsApplet::onBackground()
{ {
// Allow normal update behavior to resume windowManager->releaseFullscreen();
SystemApplet::lockRendering = false; windowManager->unlock(this);
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::TipsApplet::onActivate() {} void InkHUD::TipsApplet::onActivate()
{
// Decide which tips (if any) should be shown to user after the boot screen
// While our SystemApplet::handleInput flag is true // Welcome screen
if (settings.tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// Antenna, region, timezone
// Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
tipQueue.push_back(Tip::FINISH_SETUP);
// Shutdown info
// Shown until user performs one valid shutdown
if (!settings.tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings.tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display
if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION);
// Applet will be brought to foreground when boot screen closes, via TipsApplet::onLockAvailable
}
// While our applet has the window manager locked, we will receive the button input
void InkHUD::TipsApplet::onButtonShortPress() void InkHUD::TipsApplet::onButtonShortPress()
{ {
tipQueue.pop_front(); tipQueue.pop_front();
@@ -218,15 +206,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
if (tipQueue.empty()) { if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips // Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots // Don't show them on subsequent boots
if (settings->tips.firstBoot) { if (settings.tips.firstBoot) {
settings->tips.firstBoot = false; settings.tips.firstBoot = false;
inkhud->persistence->saveSettings(); saveDataToFlash();
} }
// Close applet, and full refresh to clean the screen // Close applet, and full refresh to clean the screen
// Need to force update, because our request would be ignored otherwise, as we are now background // Need to force update, because our request would be ignored otherwise, as we are now background
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL); windowManager->forceUpdate(EInk::UpdateTypes::FULL);
} }
// More tips left // More tips left
@@ -234,4 +222,13 @@ void InkHUD::TipsApplet::onButtonShortPress()
requestUpdate(); requestUpdate();
} }
// If the wm lock has just become availale (rendering, input), and we've still got tips, grab it!
// This situation would arise if bluetooth pairing occurs while TipsApplet was already shown (after pairing)
// Note: this event is only raised when *other* applets unlock the window manager
void InkHUD::TipsApplet::onLockAvailable()
{
if (!tipQueue.empty())
bringToForeground();
}
#endif #endif

View File

@@ -12,12 +12,12 @@
#include "configuration.h" #include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h" #include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class TipsApplet : public SystemApplet class TipsApplet : public Applet
{ {
protected: protected:
enum class Tip { enum class Tip {
@@ -37,6 +37,7 @@ class TipsApplet : public SystemApplet
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onButtonShortPress() override; void onButtonShortPress() override;
void onLockAvailable() override; // Reopen if interrupted by bluetooth pairing
protected: protected:
void renderWelcome(); // Very first screen of tutorial void renderWelcome(); // Very first screen of tutorial

View File

@@ -41,12 +41,14 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
void InkHUD::AllMessageApplet::onRender() void InkHUD::AllMessageApplet::onRender()
{ {
setFont(fontSmall);
// Find newest message, regardless of whether DM or broadcast // Find newest message, regardless of whether DM or broadcast
MessageStore::Message *message; MessageStore::Message *message;
if (latestMessage->wasBroadcast) if (latestMessage.wasBroadcast)
message = &latestMessage->broadcast; message = &latestMessage.broadcast;
else else
message = &latestMessage->dm; message = &latestMessage.dm;
// Short circuit: no text message // Short circuit: no text message
if (!message->sender) { if (!message->sender) {

View File

@@ -44,8 +44,10 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
void InkHUD::DMApplet::onRender() void InkHUD::DMApplet::onRender()
{ {
setFont(fontSmall);
// Abort if no text message // Abort if no text message
if (!latestMessage->dm.sender) { if (!latestMessage.dm.sender) {
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE); printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
return; return;
} }
@@ -61,7 +63,7 @@ void InkHUD::DMApplet::onRender()
// RX Time // RX Time
// - if valid // - if valid
std::string timeString = getTimeString(latestMessage->dm.timestamp); std::string timeString = getTimeString(latestMessage.dm.timestamp);
if (timeString.length() > 0) { if (timeString.length() > 0) {
header += timeString; header += timeString;
header += ": "; header += ": ";
@@ -70,14 +72,14 @@ void InkHUD::DMApplet::onRender()
// Sender's id // Sender's id
// - shortname, if available, or // - shortname, if available, or
// - node id // - node id
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender); meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage.dm.sender);
if (sender && sender->has_user) { if (sender && sender->has_user) {
header += sender->user.short_name; header += sender->user.short_name;
header += " ("; header += " (";
header += sender->user.long_name; header += sender->user.long_name;
header += ")"; header += ")";
} else } else
header += hexifyNodeNum(latestMessage->dm.sender); header += hexifyNodeNum(latestMessage.dm.sender);
// Draw a "standard" applet header // Draw a "standard" applet header
drawHeader(header); drawHeader(header);
@@ -101,14 +103,14 @@ void InkHUD::DMApplet::onRender()
// Determine size if printed large // Determine size if printed large
setFont(fontLarge); setFont(fontLarge);
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage->dm.text); uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage.dm.text);
// If too large, swap to small font // If too large, swap to small font
if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned) if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned)
setFont(fontSmall); setFont(fontSmall);
// Print text // Print text
printWrapped(0, textTop, width(), latestMessage->dm.text); printWrapped(0, textTop, width(), latestMessage.dm.text);
} }
// Don't show notifications for direct messages when our applet is displayed // Don't show notifications for direct messages when our applet is displayed

View File

@@ -29,13 +29,13 @@ class PositionsApplet : public MapApplet, public SinglePortModule
protected: protected:
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
NodeNum lastFrom = 0; // Sender of most recent (non-local) position packet NodeNum lastFrom; // Sender of most recent (non-local) position packet
float lastLat = 0.0; float lastLat;
float lastLng = 0.0; float lastLng;
float lastHopsAway = 0; float lastHopsAway;
float ourLastLat = 0.0; // Info about the most recent (non-local) position packet float ourLastLat; // Info about the most recent (non-local) position packet
float ourLastLng = 0.0; // Info about most recent *local* position float ourLastLng; // Info about most recent *local* position
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD

View File

@@ -122,7 +122,7 @@ bool InkHUD::RecentsListApplet::isActive(uint32_t seenAtMs)
uint32_t now = millis(); uint32_t now = millis();
uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe
return (secsAgo < settings->recentlyActiveSeconds); return (secsAgo < settings.recentlyActiveSeconds);
} }
// Text to be shown at top of applet // Text to be shown at top of applet
@@ -134,7 +134,7 @@ std::string InkHUD::RecentsListApplet::getHeaderText()
// Print the length of our "Recents" time-window // Print the length of our "Recents" time-window
text += "Last "; text += "Last ";
text += to_string(settings->recentlyActiveSeconds / 60); text += to_string(settings.recentlyActiveSeconds / 60);
text += " mins"; text += " mins";
// Print the node count // Print the node count

View File

@@ -23,6 +23,8 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) : cha
void InkHUD::ThreadedMessageApplet::onRender() void InkHUD::ThreadedMessageApplet::onRender()
{ {
setFont(fontSmall);
// ============= // =============
// Draw a header // Draw a header
// ============= // =============

View File

@@ -33,7 +33,7 @@ class Applet;
class ThreadedMessageApplet : public Applet class ThreadedMessageApplet : public Applet
{ {
public: public:
explicit ThreadedMessageApplet(uint8_t channelIndex); ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet() = delete; ThreadedMessageApplet() = delete;
void onRender() override; void onRender() override;

View File

@@ -1,179 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Events.h"
#include "RTC.h"
#include "modules/TextMessageModule.h"
#include "sleep.h"
#include "./Applet.h"
#include "./SystemApplet.h"
using namespace NicheGraphics;
InkHUD::Events::Events()
{
// Get convenient references
inkhud = InkHUD::getInstance();
settings = &inkhud->persistence->settings;
}
void InkHUD::Events::begin()
{
// Register our callbacks for the various events
deepSleepObserver.observe(&notifyDeepSleep);
rebootObserver.observe(&notifyReboot);
textMessageObserver.observe(textMessageModule);
#ifdef ARCH_ESP32
lightSleepObserver.observe(&notifyLightSleep);
#endif
}
void InkHUD::Events::onButtonShort()
{
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to cycle applets
if (consumer)
consumer->onButtonShortPress();
else
inkhud->nextApplet();
}
void InkHUD::Events::onButtonLong()
{
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to open the menu
if (consumer)
consumer->onButtonLongPress();
else
inkhud->openMenu();
}
// Callback for deepSleepObserver
// Returns 0 to signal that we agree to sleep now
int InkHUD::Events::beforeDeepSleep(void *unused)
{
// Notify all applets that we're shutting down
for (Applet *ua : inkhud->userApplets) {
ua->onDeactivate();
ua->onShutdown();
}
for (SystemApplet *sa : inkhud->systemApplets) {
// Note: no onDeactivate. System applets are always active.
sa->onShutdown();
}
// User has successful executed a safe shutdown
// We don't need to nag at boot anymore
settings->tips.safeShutdownSeen = true;
inkhud->persistence->saveSettings();
inkhud->persistence->saveLatestMessage();
// LogoApplet::onShutdown will have requested an update, to draw the shutdown screen
// Draw that now, and wait here until the update is complete
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
return 0; // We agree: deep sleep now
}
// Callback for rebootObserver
// Same as shutdown, without drawing the logoApplet
// Makes sure we don't lose message history / InkHUD config
int InkHUD::Events::beforeReboot(void *unused)
{
// Notify all applets that we're "shutting down"
// They don't need to know that it's really a reboot
for (Applet *a : inkhud->userApplets) {
a->onDeactivate();
a->onShutdown();
}
for (Applet *sa : inkhud->systemApplets) {
// Note: no onDeactivate. System applets are always active.
sa->onShutdown();
}
inkhud->persistence->saveSettings();
inkhud->persistence->saveLatestMessage();
// Note: no forceUpdate call here
// Because OSThread will not be given another chance to run before reboot, this means that no display update will occur
return 0; // No special status to report. Ignored anyway by this Observable
}
// Callback when a new text message is received
// Caches the most recently received message, for use by applets
// Rx does not trigger a save to flash, however the data *will* be saved alongside other during shutdown, etc.
// Note: this is different from devicestate.rx_text_message, which may contain an *outgoing* message
int InkHUD::Events::onReceiveTextMessage(const meshtastic_MeshPacket *packet)
{
// Short circuit: don't store outgoing messages
if (getFrom(packet) == nodeDB->getNodeNum())
return 0;
// Short circuit: don't store "emoji reactions"
// Possibly some implementation of this in future?
if (packet->decoded.emoji)
return 0;
// Determine whether the message is broadcast or a DM
// Store this info to prevent confusion after a reboot
// Avoids need to compare timestamps, because of situation where "future" messages block newly received, if time not set
inkhud->persistence->latestMessage.wasBroadcast = isBroadcast(packet->to);
// Pick the appropriate variable to store the message in
MessageStore::Message *storedMessage = inkhud->persistence->latestMessage.wasBroadcast
? &inkhud->persistence->latestMessage.broadcast
: &inkhud->persistence->latestMessage.dm;
// Store nodenum of the sender
// Applets can use this to fetch user data from nodedb, if they want
storedMessage->sender = packet->from;
// Store the time (epoch seconds) when message received
storedMessage->timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
// Store the channel
// - (potentially) used to determine whether notification shows
// - (potentially) used to determine which applet to focus
storedMessage->channelIndex = packet->channel;
// Store the text
// Need to specify manually how many bytes, because source not null-terminated
storedMessage->text =
std::string(&packet->decoded.payload.bytes[0], &packet->decoded.payload.bytes[packet->decoded.payload.size]);
return 0; // Tell caller to continue notifying other observers. (No reason to abort this event)
}
#ifdef ARCH_ESP32
// Callback for lightSleepObserver
// Make sure the display is not partway through an update when we begin light sleep
// This is because some displays require active input from us to terminate the update process, and protect the panel hardware
int InkHUD::Events::beforeLightSleep(void *unused)
{
inkhud->awaitUpdate();
return 0; // No special status to report. Ignored anyway by this Observable
}
#endif
#endif

View File

@@ -1,63 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#pragma once
/*
Handles non-specific events for InkHUD
Individual applets are responsible for listening for their own events via the module api etc,
however this class handles general events which concern InkHUD as a whole, e.g. shutdown
*/
#include "configuration.h"
#include "Observer.h"
#include "./InkHUD.h"
#include "./Persistence.h"
namespace NicheGraphics::InkHUD
{
class Events
{
public:
Events();
void begin();
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot
int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message
#ifdef ARCH_ESP32
int beforeLightSleep(void *unused); // Prepare for light sleep
#endif
private:
// For convenience
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
// Get notified when the system is shutting down
CallbackObserver<Events, void *> deepSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeDeepSleep);
// Get notified when the system is rebooting
CallbackObserver<Events, void *> rebootObserver = CallbackObserver<Events, void *>(this, &Events::beforeReboot);
// Cache *incoming* text messages, for use by applets
CallbackObserver<Events, const meshtastic_MeshPacket *> textMessageObserver =
CallbackObserver<Events, const meshtastic_MeshPacket *>(this, &Events::onReceiveTextMessage);
#ifdef ARCH_ESP32
// Get notified when the system is entering light sleep
CallbackObserver<Events, void *> lightSleepObserver = CallbackObserver<Events, void *>(this, &Events::beforeLightSleep);
#endif
};
} // namespace NicheGraphics::InkHUD
#endif

View File

@@ -1,218 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./InkHUD.h"
#include "./Applet.h"
#include "./Events.h"
#include "./Persistence.h"
#include "./Renderer.h"
#include "./SystemApplet.h"
#include "./Tile.h"
#include "./WindowManager.h"
using namespace NicheGraphics;
// Get or create the singleton
InkHUD::InkHUD *InkHUD::InkHUD::getInstance()
{
// Create the singleton instance of our class, if not yet done
static InkHUD *instance = nullptr;
if (!instance) {
instance = new InkHUD;
instance->persistence = new Persistence;
instance->windowManager = new WindowManager;
instance->renderer = new Renderer;
instance->events = new Events;
}
return instance;
}
// Connect the (fully set-up) E-Ink driver to InkHUD
// Should happen in your variant's nicheGraphics.h file, before InkHUD::begin is called
void InkHUD::InkHUD::setDriver(Drivers::EInk *driver)
{
renderer->setDriver(driver);
}
// Set the target number of FAST display updates in a row, before a FULL update is used for display health
// This value applies only to updates with an UNSPECIFIED update type
// If explicitly requested FAST updates exceed this target, the stressMultiplier parameter determines how many
// subsequent FULL updates will be performed, in an attempt to restore the display's health
void InkHUD::InkHUD::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
{
renderer->setDisplayResilience(fastPerFull, stressMultiplier);
}
// Register a user applet with InkHUD
// A variant's nicheGraphics.h file should instantiate your chosen applets, then pass them to this method
// Passing an applet to this method is all that is required to make it available to the user in your InkHUD build
void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, bool defaultAutoshow, uint8_t onTile)
{
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
}
// Start InkHUD!
// Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin()
{
persistence->loadSettings();
persistence->loadLatestMessage();
windowManager->begin();
events->begin();
renderer->begin();
// LogoApplet shows boot screen here
}
// Call this when your user button gets a short press
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
void InkHUD::InkHUD::shortpress()
{
events->onButtonShort();
}
// Call this when your user button gets a long press
// Should be connected to an input source in nicheGraphics.h (NicheGraphics::Inputs::TwoButton?)
void InkHUD::InkHUD::longpress()
{
events->onButtonLong();
}
// Cycle the next user applet to the foreground
// Only activated applets are cycled
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
void InkHUD::InkHUD::nextApplet()
{
windowManager->nextApplet();
}
// Show the menu (on the the focused tile)
// The applet previously displayed there will be restored once the menu closes
void InkHUD::InkHUD::openMenu()
{
windowManager->openMenu();
}
// In layouts where multiple applets are shown at once, change which tile is focused
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
void InkHUD::InkHUD::nextTile()
{
windowManager->nextTile();
}
// Rotate the display image by 90 degrees
void InkHUD::InkHUD::rotate()
{
windowManager->rotate();
}
// Show / hide the battery indicator in top-right
void InkHUD::InkHUD::toggleBatteryIcon()
{
windowManager->toggleBatteryIcon();
}
// An applet asking for the display to be updated
// This does not occur immediately
// Instead, rendering is scheduled ASAP, for the next Renderer::runOnce call
// This allows multiple applets to observe the same event, and then share the same opportunity to update
// Applets should requestUpdate, whether or not they are currently displayed ("foreground")
// This is because they *might* be automatically brought to foreground by WindowManager::autoshow
void InkHUD::InkHUD::requestUpdate()
{
renderer->requestUpdate();
}
// Demand that the display be updated
// Ignores all diplomacy:
// - the display *will* update
// - the specified update type *will* be used
// If the async parameter is false, code flow is blocked while the update takes place
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
{
renderer->forceUpdate(type, async);
}
// Wait for any in-progress display update to complete before continuing
void InkHUD::InkHUD::awaitUpdate()
{
renderer->awaitUpdate();
}
// Ask the window manager to potentially bring a different user applet to foreground
// An applet will be brought to foreground if it has just received new and relevant info
// For Example: AllMessagesApplet has just received a new text message
// Permission for this autoshow behavior is granted by the user, on an applet-by-applet basis
// If autoshow brings an applet to foreground, an InkHUD notification will not be generated for the same event
void InkHUD::InkHUD::autoshow()
{
windowManager->autoshow();
}
// Tell the window manager that the Persistence::Settings value for applet activation has changed,
// and that it should reconfigure accordingly.
// This is triggered at boot, or when the user enables / disabled applets via the on-screen menu
void InkHUD::InkHUD::updateAppletSelection()
{
windowManager->changeActivatedApplets();
}
// Tell the window manager that the Persistence::Settings value for layout or rotation has changed,
// and that it should reconfigure accordingly.
// This is triggered at boot, or by rotate / layout options in the on-screen menu
void InkHUD::InkHUD::updateLayout()
{
windowManager->changeLayout();
}
// Width of the display, in the context of the current rotation
uint16_t InkHUD::InkHUD::width()
{
return renderer->width();
}
// Height of the display, in the context of the current rotation
uint16_t InkHUD::InkHUD::height()
{
return renderer->height();
}
// A collection of any user tiles which do not have a valid user applet
// This can occur in various situations, such as when a user enables fewer applets than their layout has tiles
// The tiles (and which regions the occupy) are private information of the window manager
// The renderer needs to know which regions (if any) are empty,
// in order to fill them with a "placeholder" pattern.
// -- There may be a tidier way to accomplish this --
std::vector<InkHUD::Tile *> InkHUD::InkHUD::getEmptyTiles()
{
return windowManager->getEmptyTiles();
}
// Get a system applet by its name
// This isn't particularly elegant, but it does avoid:
// - passing around a big set of references
// - having two sets of references (systemApplet vector for iteration)
InkHUD::SystemApplet *InkHUD::InkHUD::getSystemApplet(const char *name)
{
for (SystemApplet *sa : systemApplets) {
if (strcmp(name, sa->name) == 0)
return sa;
}
assert(false); // Invalid name
}
// Place a pixel into the image buffer
// The x and y coordinates are in the context of the current display rotation
// - Applets pass "relative" pixels to tiles
// - Tiles pass translated pixels to this method
// - this methods (Renderer) places rotated pixels into the image buffer
// This method provides the final formatting step required. The image buffer is suitable for writing to display
void InkHUD::InkHUD::drawPixel(int16_t x, int16_t y, Color c)
{
renderer->handlePixel(x, y, c);
}
#endif

View File

@@ -1,110 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
InkHUD's main class
- singleton
- mediator between the various components
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
#include "./AppletFont.h"
#include <vector>
namespace NicheGraphics::InkHUD
{
// Color, understood by display controller IC (as bit values)
// Also suitable for use as AdafruitGFX colors
enum Color : uint8_t {
BLACK = 0,
WHITE = 1,
};
class Applet;
class Events;
class Persistence;
class Renderer;
class SystemApplet;
class Tile;
class WindowManager;
class InkHUD
{
public:
static InkHUD *getInstance(); // Access to this singleton class
// Configuration
// - before InkHUD::begin, in variant nicheGraphics.h,
void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
void begin();
// Handle user-button press
// - connected to an input source, in variant nicheGraphics.h
void shortpress();
void longpress();
// Trigger UI changes
// - called by various InkHUD components
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
void nextApplet();
void openMenu();
void nextTile();
void rotate();
void toggleBatteryIcon();
// Updating the display
// - called by various InkHUD components
void requestUpdate();
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
void awaitUpdate();
// (Re)configuring WindowManager
void autoshow(); // Bring an applet to foreground
void updateAppletSelection(); // Change which applets are active
void updateLayout(); // Change multiplexing (count, rotation)
// Information passed between components
uint16_t width(); // From E-Ink driver
uint16_t height(); // From E-Ink driver
std::vector<Tile *> getEmptyTiles(); // From WindowManager
// Applets
SystemApplet *getSystemApplet(const char *name);
std::vector<Applet *> userApplets;
std::vector<SystemApplet *> systemApplets;
// Pass drawing output to Renderer
void drawPixel(int16_t x, int16_t y, Color c);
// Shared data which persists between boots
Persistence *persistence = nullptr;
private:
InkHUD() {} // Constructor made private to force use of InkHUD::getInstance
Events *events = nullptr; // Handle non-specific firmware events
Renderer *renderer = nullptr; // Co-ordinate display updates
WindowManager *windowManager = nullptr; // Multiplexing of applets
};
} // namespace NicheGraphics::InkHUD
#endif

View File

@@ -31,7 +31,7 @@ class MessageStore
}; };
MessageStore() = delete; MessageStore() = delete;
explicit MessageStore(std::string label); // Label determines filename in flash MessageStore(std::string label); // Label determines filename in flash
void saveToFlash(); void saveToFlash();
void loadFromFlash(); void loadFromFlash();

View File

@@ -5,21 +5,17 @@
using namespace NicheGraphics; using namespace NicheGraphics;
// Load settings and latestMessage data // Load settings and latestMessage data
void InkHUD::Persistence::loadSettings() void InkHUD::loadDataFromFlash()
{ {
// Load the InkHUD settings from flash, and check version number // Load the InkHUD settings from flash, and check version number
// We should only consider the version number if the InkHUD flashdata component reports that we *did* actually load flash data // We should only consider the version number if the InkHUD flashdata component reports that we *did* actually load flash data
Settings loadedSettings; InkHUD::Settings loadedSettings;
bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings"); bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings");
if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0) if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0)
settings = loadedSettings; // Version matched, replace the defaults with the loaded values settings = loadedSettings; // Version matched, replace the defaults with the loaded values
else else
LOG_WARN("Settings version changed. Using defaults"); LOG_WARN("Settings version changed. Using defaults");
}
// Load settings and latestMessage data
void InkHUD::Persistence::loadLatestMessage()
{
// Load previous "latestMessages" data from flash // Load previous "latestMessages" data from flash
MessageStore store("latest"); MessageStore store("latest");
store.loadFromFlash(); store.loadFromFlash();
@@ -36,15 +32,12 @@ void InkHUD::Persistence::loadLatestMessage()
} }
} }
// Save the InkHUD settings to flash // Save settings and latestMessage data
void InkHUD::Persistence::saveSettings() void InkHUD::saveDataToFlash()
{ {
// Save the InkHUD settings to flash
FlashData<Settings>::save(&settings, "settings"); FlashData<Settings>::save(&settings, "settings");
}
// Save latestMessage data to flash
void InkHUD::Persistence::saveLatestMessage()
{
// Number of strings saved determines whether last message was broadcast or dm // Number of strings saved determines whether last message was broadcast or dm
MessageStore store("latest"); MessageStore store("latest");
store.messages.push_back(latestMessage.dm); store.messages.push_back(latestMessage.dm);
@@ -53,31 +46,14 @@ void InkHUD::Persistence::saveLatestMessage()
store.saveToFlash(); store.saveToFlash();
} }
/* // Holds InkHUD settings while running
void InkHUD::Persistence::printSettings(Settings *settings) // Saved back to Flash at shutdown
{ // Accessed by including persistence.h
if (SETTINGS_VERSION != 2) InkHUD::Settings InkHUD::settings;
LOG_WARN("Persistence::printSettings was written for SETTINGS_VERSION=2, current is %d", SETTINGS_VERSION);
LOG_DEBUG("meta.version=%d", settings->meta.version); // Holds copies of the most recent broadcast and DM messages while running
LOG_DEBUG("userTiles.count=%d", settings->userTiles.count); // Saved to Flash at shutdown
LOG_DEBUG("userTiles.maxCount=%d", settings->userTiles.maxCount); // Accessed by including persistence.h
LOG_DEBUG("userTiles.focused=%d", settings->userTiles.focused); InkHUD::LatestMessage InkHUD::latestMessage;
for (uint8_t i = 0; i < MAX_TILES_GLOBAL; i++)
LOG_DEBUG("userTiles.displayedUserApplet[%d]=%d", i, settings->userTiles.displayedUserApplet[i]);
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
LOG_DEBUG("userApplets.active[%d]=%d", i, settings->userApplets.active[i]);
for (uint8_t i = 0; i < MAX_USERAPPLETS_GLOBAL; i++)
LOG_DEBUG("userApplets.autoshow[%d]=%d", i, settings->userApplets.autoshow[i]);
LOG_DEBUG("optionalFeatures.notifications=%d", settings->optionalFeatures.notifications);
LOG_DEBUG("optionalFeatures.batteryIcon=%d", settings->optionalFeatures.batteryIcon);
LOG_DEBUG("optionalMenuItems.nextTile=%d", settings->optionalMenuItems.nextTile);
LOG_DEBUG("optionalMenuItems.backlight=%d", settings->optionalMenuItems.backlight);
LOG_DEBUG("tips.firstBoot=%d", settings->tips.firstBoot);
LOG_DEBUG("tips.safeShutdownSeen=%d", settings->tips.safeShutdownSeen);
LOG_DEBUG("rotation=%d", settings->rotation);
LOG_DEBUG("recentlyActiveSeconds=%d", settings->recentlyActiveSeconds);
}
*/
#endif #endif

View File

@@ -14,119 +14,110 @@ The save / load mechanism is a shared NicheGraphics feature.
#include "configuration.h" #include "configuration.h"
#include "./InkHUD.h"
#include "graphics/niche/FlashData.h" #include "graphics/niche/FlashData.h"
#include "graphics/niche/InkHUD/MessageStore.h" #include "graphics/niche/InkHUD/MessageStore.h"
namespace NicheGraphics::InkHUD namespace NicheGraphics::InkHUD
{ {
class Persistence constexpr uint8_t MAX_TILES_GLOBAL = 4;
{ constexpr uint8_t MAX_USERAPPLETS_GLOBAL = 16;
public:
static constexpr uint8_t MAX_TILES_GLOBAL = 4;
static constexpr uint8_t MAX_USERAPPLETS_GLOBAL = 16;
// Used to invalidate old settings, if needed // Used to invalidate old settings, if needed
// Version 0 is reserved for testing, and will always load defaults // Version 0 is reserved for testing, and will always load defaults
static constexpr uint32_t SETTINGS_VERSION = 2; constexpr uint32_t SETTINGS_VERSION = 2;
struct Settings { struct Settings {
struct Meta { struct Meta {
// Used to invalidate old savefiles, if we make breaking changes // Used to invalidate old savefiles, if we make breaking changes
uint32_t version = SETTINGS_VERSION; uint32_t version = SETTINGS_VERSION;
} meta; } meta;
struct UserTiles { struct UserTiles {
// How many tiles are shown // How many tiles are shown
uint8_t count = 1; uint8_t count = 1;
// Maximum amount of tiles for this display // Maximum amount of tiles for this display
uint8_t maxCount = 4; uint8_t maxCount = 4;
// Which tile is focused (responding to user button input) // Which tile is focused (responding to user button input)
uint8_t focused = 0; uint8_t focused = 0;
// Which applet is displayed on which tile // Which applet is displayed on which tile
// Index of array: which tile, as indexed in WindowManager::userTiles // Index of array: which tile, as indexed in WindowManager::tiles
// Value of array: which applet, as indexed in InkHUD::userApplets // Value of array: which applet, as indexed in WindowManager::activeApplets
uint8_t displayedUserApplet[MAX_TILES_GLOBAL] = {0, 1, 2, 3}; uint8_t displayedUserApplet[MAX_TILES_GLOBAL] = {0, 1, 2, 3};
} userTiles; } userTiles;
struct UserApplets { struct UserApplets {
// Which applets are running (either displayed, or in the background) // Which applets are running (either displayed, or in the background)
// Index of array: which applet, as indexed in InkHUD::userApplets // Index of array: which applet, as indexed in WindowManager::applets
// Initial value is set by the "activeByDefault" parameter of InkHUD::addApplet, in setupNicheGraphics method // Initial value is set by the "activeByDefault" parameter of WindowManager::addApplet, in setupNicheGraphics()
bool active[MAX_USERAPPLETS_GLOBAL]{false}; bool active[MAX_USERAPPLETS_GLOBAL];
// Which user applets should be automatically shown when they have important data to show // Which user applets should be automatically shown when they have important data to show
// If none set, foreground applets should remain foreground without manual user input // If none set, foreground applets should remain foreground without manual user input
// If multiple applets request this at once, // If multiple applets request this at once,
// priority is the order which they were passed to InkHUD::addApplets, in setupNicheGraphics method // priority is the order which they were passed to WindowManager::addApplets, in setupNicheGraphics()
bool autoshow[MAX_USERAPPLETS_GLOBAL]{false}; bool autoshow[MAX_USERAPPLETS_GLOBAL]{false};
} userApplets; } userApplets;
// Features which the user can enable / disable via the on-screen menu // Features which the use can enable / disable via the on-screen menu
struct OptionalFeatures { struct OptionalFeatures {
bool notifications = true; bool notifications = true;
bool batteryIcon = false; bool batteryIcon = false;
} optionalFeatures; } optionalFeatures;
// Some menu items may not be required, based on device / configuration // Some menu items may not be required, based on device / configuration
// We can enable them only when needed, to de-clutter the menu // We can enable them only when needed, to de-clutter the menu
struct OptionalMenuItems { struct OptionalMenuItems {
// If aux button is used to swap between tiles, we have no need for this menu item // If aux button is used to swap between tiles, we have to need for this menu item
bool nextTile = true; bool nextTile = true;
// Used if backlight present, and not controlled by AUX button // Used if backlight present, and not controlled by AUX button
// If this item is added to menu: backlight is always active when menu is open // If this item is added to menu: backlight is always active when menu is open
// The added menu items then allows the user to "Keep Backlight On", globally. // The added menu items then allows the user to "Keep Backlight On", globally.
bool backlight = false; bool backlight = false;
} optionalMenuItems; } optionalMenuItems;
// Allows tips to be run once only // Allows tips to be run once only
struct Tips { struct Tips {
// Enables the longer "tutorial" shown only on first boot // Enables the longer "tutorial" shown only on first boot
// Once tutorial has been completed, it is no longer shown // Once tutorial has been completed, it is no longer shown
bool firstBoot = true; bool firstBoot = true;
// User is advised to shut down before removing device power // User is advised to shutdown before removing device power
// Once user executes a shutdown (either via menu or client app), // Once user executes a shutdown (either via menu or client app),
// this tip is no longer shown // this tip is no longer shown
bool safeShutdownSeen = false; bool safeShutdownSeen = false;
} tips; } tips;
// Rotation of the display // Rotation of the display
// Multiples of 90 degrees clockwise // Multiples of 90 degrees clockwise
// Most commonly: rotation is 0 when flex connector is oriented below display // Most commonly: rotation is 0 when flex connector is oriented below display
uint8_t rotation = 1; uint8_t rotation = 1;
// How long do we consider another node to be "active"? // How long do we consider another node to be "active"?
// Used when applets want to filter for "active nodes" only // Used when applets want to filter for "active nodes" only
uint32_t recentlyActiveSeconds = 2 * 60; uint32_t recentlyActiveSeconds = 2 * 60;
};
// Most recently received text message
// Value is updated by InkHUD::WindowManager, as a courtesy to applets
// Note: different from devicestate.rx_text_message,
// which may contain an *outgoing message* to broadcast
struct LatestMessage {
MessageStore::Message broadcast; // Most recent message received broadcast
MessageStore::Message dm; // Most recent received DM
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
};
void loadSettings();
void saveSettings();
void loadLatestMessage();
void saveLatestMessage();
// void printSettings(Settings *settings); // Debugging use only
Settings settings;
LatestMessage latestMessage;
}; };
// Most recently received text message
// Value is updated by InkHUD::WindowManager, as a courtesty to applets
// Note: different from devicestate.rx_text_message,
// which may contain an *outgoing message* to broadcast
struct LatestMessage {
MessageStore::Message broadcast; // Most recent message received broadcast
MessageStore::Message dm; // Most recent received DM
bool wasBroadcast; // True if most recent broadcast is newer than most recent dm
};
extern Settings settings;
extern LatestMessage latestMessage;
void loadDataFromFlash();
void saveDataToFlash();
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD
#endif #endif

View File

@@ -1,7 +1,6 @@
[inkhud] [inkhud]
build_src_filter = board_level = extra
+<graphics/niche/>; Include the nicheGraphics directory build_src_filter = +<../variants/$PIOENV> ; Include nicheGraphics.h
+<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder
build_flags = build_flags =
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics -D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
-D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI) -D MESHTASTIC_INCLUDE_INKHUD ; Use InkHUD (a NicheGraphics UI)

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