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 \
gnupg2 \
libusb-1.0-0-dev \
libuv1-dev \
libi2c-dev \
libxcb-xkb-dev \
libxkbcommon-dev \
libinput-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN pipx install platformio

View File

@@ -1,6 +1,3 @@
#!/usr/bin/env sh
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
*.cmd text eol=crlf
*.bat text eol=crlf
*.ps1 text eol=crlf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.{sh,[sS][hH]} text eol=lf

View File

@@ -20,7 +20,7 @@ runs:
shell: bash
run: |
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
uses: actions/setup-python@v5

View File

@@ -11,4 +11,4 @@ runs:
- name: Install libs needed for native build
shell: bash
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
time: "05:00"
timezone: US/Pacific
ignore:
- dependency-name: protobufs
- package-ecosystem: github-actions
directory: /.github/workflows
schedule:

View File

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

View File

@@ -136,7 +136,6 @@ jobs:
secrets: inherit
package-pio-deps-native-tft:
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/package_pio_deps.yml
with:
pio_env: native-tft
@@ -330,13 +329,13 @@ jobs:
with:
pattern: platformio-deps-native-tft-${{ steps.version.outputs.long }}
merge-multiple: true
path: ./output/pio-deps-native-tft
path: ./output/pio-deps-native
- name: Zip linux sources
working-directory: output
run: |
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
- name: Display structure of downloaded files
@@ -345,10 +344,32 @@ jobs:
- name: Add linux sources to release
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/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:
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:
strategy:
fail-fast: false

View File

@@ -43,49 +43,3 @@ jobs:
copr_project: |-
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
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:
- cron: 0 1 * * 6
permissions:
actions: read
contents: read
security-events: write
permissions: read-all
jobs:
semgrep-full:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
container:
image: semgrep/semgrep

View File

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

View File

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

3
.gitmodules vendored
View File

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

View File

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

View File

@@ -7,8 +7,5 @@
"cmake.configureOnOpen": false,
"[cpp]": {
"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
RUN apt-get update && apt-get install --no-install-recommends -y \
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 \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
@@ -38,7 +38,7 @@ ENV TZ=Etc/UTC
USER root
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/* \
&& mkdir -p /var/lib/meshtasticd \
&& 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
RUN apk --no-cache add \
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/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
@@ -32,7 +32,7 @@ FROM alpine:3.21
USER root
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/* \
&& mkdir -p /var/lib/meshtasticd \
&& 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).
[portduino_base]
platform = https://github.com/Jorropo/platform-native.git#17fa89daec4402af491512f75278a7fec8a5818c
platform = https://github.com/meshtastic/platform-native.git#562d189828f09fbf4c4093b3c0104bae9d8e9ff9
framework = arduino
build_src_filter =
@@ -34,12 +34,10 @@ build_flags =
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE
-DHAS_UDP_MULTICAST
-lpthread
-lstdc++fs
-lbluetooth
-lgpiod
-lyaml-cpp
-li2c
-luv
-std=c++17

View File

@@ -1,8 +1,8 @@
; Common settings for rp2040 Processor based targets
[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
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.filesystem_size = 0.5m

View File

@@ -1,296 +1,72 @@
@ECHO OFF
SETLOCAL EnableDelayedExpansion
TITLE Meshtastic device-install
SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=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"
set PYTHON=python
set WEB_APP=0
GOTO getopts
:help
ECHO Flash image file to device, but first erasing and writing system information.
ECHO.
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python] (--web)
ECHO.
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
:: Determine the correct esptool command to use
where esptool >nul 2>&1
if %ERRORLEVEL% EQU 0 (
set "ESPTOOL_CMD=esptool"
) else (
set "ESPTOOL_CMD=%PYTHON% -m esptool"
)
:version
ECHO %SCRIPT_NAME% [Version 2.6.0]
ECHO Meshtastic
GOTO eof
goto GETOPTS
:HELP
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME] [--web]
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
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"=="-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"
:GETOPTS
if /I "%1"=="-h" goto HELP
if /I "%1"=="--help" goto HELP
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
if /I "%1"=="--web" set WEB_APP=1 & SHIFT
SHIFT
GOTO getopts
:endopts
IF NOT "__%1__"=="____" goto GETOPTS
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
IF "__!FILENAME!__"=="____" (
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
GOTO help
) ELSE (
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!"
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
GOTO help
IF "__%FILENAME%__" == "____" (
echo "Missing FILENAME"
goto HELP
)
IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
echo Trying to flash update %FILENAME%, but first erasing and writing system information"
%ESPTOOL_CMD% --baud 115200 erase_flash
%ESPTOOL_CMD% --baud 115200 write_flash 0x00 %FILENAME%
@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!__" (
CALL :LOG_MESSAGE ERROR "Filename must be a firmware-* file."
GOTO help
IF %WEB_APP%==1 (
for %%f in (littlefswebui-*.bin) do (
%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.
SET "FILENAME=!FILENAME:.\=!"
SET "FILENAME=!FILENAME:./=!"
) else (
echo "Invalid file: %FILENAME%"
goto HELP
) else (
echo "Invalid file: %FILENAME%"
goto HELP
)
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 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
:EOF

View File

@@ -1,21 +1,18 @@
#!/bin/bash
#!/bin/sh
PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
WEB_APP=false
TFT8=false
TFT16=false
TFT_BUILD=false
# Determine the correct esptool command to use
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
ESPTOOL_CMD="esptool"
ESPTOOL_CMD="esptool"
elif command -v esptool.py >/dev/null 2>&1; then
ESPTOOL_CMD="esptool.py"
ESPTOOL_CMD="esptool.py"
else
echo "Error: esptool not found"
exit 1
echo "Error: esptool not found"
exit 1
fi
set -e
@@ -23,138 +20,75 @@ set -e
# Usage info
show_help() {
cat <<EOF
Usage: $(basename $0) [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME] [--web]
Flash image file to device, but first erasing and writing system information.
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"
-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 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.
--web Enable WebUI. (Default: false)
-f FILENAME The .bin file to flash. Custom to your device type and region.
--web Flash WEB APP.
EOF
}
# Parse arguments using a single while loop
while [ $# -gt 0 ]; do
case "$1" in
-h | --help)
# Preprocess long options like --web
for arg in "$@"; do
case "$arg" in
--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
exit 0
;;
-p)
ESPTOOL_PORT="$2"
shift # Shift past the option argument
p)
export ESPTOOL_PORT=${OPTARG}
;;
-P)
PYTHON="$2"
shift
P)
PYTHON=${OPTARG}
;;
-f)
FILENAME="$2"
shift
;;
--web)
WEB_APP=true
;;
--) # Stop parsing options
shift
break
f)
FILENAME=${OPTARG}
;;
*)
echo "Unknown argument: $1" >&2
echo "Invalid flag."
show_help >&2
exit 1
;;
esac
shift # Move to the next argument
done
shift "$((OPTIND - 1))"
[ -z "$FILENAME" -a -n "$1" ] && {
FILENAME=$1
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
# 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"
$ESPTOOL_CMD erase_flash
$ESPTOOL_CMD write_flash 0x00 "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
$ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
# 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
$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
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
SETLOCAL EnableDelayedExpansion
TITLE Meshtastic device-update
SET "SCRIPT_NAME=%~nx0"
SET "DEBUG=0"
SET "PYTHON="
SET "ESPTOOL_BAUD=115200"
SET "ESPTOOL_CMD="
SET "LOGCOUNTER=0"
set PYTHON=python
GOTO getopts
:help
ECHO Flash image file to device, but leave existing system intact.
ECHO.
ECHO Usage: %SCRIPT_NAME% -f filename [-p PORT] [-P python]
ECHO.
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
:: Determine the correct esptool command to use
where esptool >nul 2>&1
if %ERRORLEVEL% EQU 0 (
set "ESPTOOL_CMD=esptool"
) else (
set "ESPTOOL_CMD=%PYTHON% -m esptool"
)
:version
ECHO %SCRIPT_NAME% [Version 2.6.0]
ECHO Meshtastic
GOTO eof
goto GETOPTS
:HELP
echo Usage: %~nx0 [-h] [-p ESPTOOL_PORT] [-P PYTHON] [-f FILENAME^|FILENAME]
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
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"=="-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
:GETOPTS
if /I "%1"=="-h" goto HELP
if /I "%1"=="--help" goto HELP
if /I "%1"=="-F" set "FILENAME=%2" & SHIFT
if /I "%1"=="-p" set ESPTOOL_PORT=%2 & SHIFT
if /I "%1"=="-P" set PYTHON=%2 & SHIFT
SHIFT
GOTO getopts
:endopts
IF NOT "__%1__"=="____" goto GETOPTS
CALL :LOG_MESSAGE DEBUG "Checking FILENAME parameter..."
IF "__!FILENAME!__"=="____" (
CALL :LOG_MESSAGE DEBUG "Missing -f filename input."
GOTO help
) ELSE (
IF NOT "__!FILENAME: =!__"=="__!FILENAME!__" (
CALL :LOG_MESSAGE ERROR "Filename containing spaces are not supported."
GOTO help
)
@REM Remove ".\" or "./" file prefix if present.
SET "FILENAME=!FILENAME:.\=!"
SET "FILENAME=!FILENAME:./=!"
IF "__%FILENAME%__" == "____" (
echo "Missing FILENAME"
goto HELP
)
IF EXIST %FILENAME% IF NOT x%FILENAME:update=%==x%FILENAME% (
echo Trying to flash update %FILENAME%
%ESPTOOL_CMD% --baud 115200 write_flash 0x10000 %FILENAME%
) else (
echo "Invalid file: %FILENAME%"
goto HELP
) else (
echo "Invalid file: %FILENAME%"
goto HELP
)
CALL :LOG_MESSAGE DEBUG "Filename: !FILENAME!"
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
:EOF

View File

@@ -125,9 +125,4 @@ for flag in flags:
projenv.Append(
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
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%
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

View File

@@ -1,124 +1,2 @@
@ECHO OFF
SETLOCAL EnableDelayedExpansion
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
@echo off
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)

View File

@@ -7,15 +7,13 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DBOARD_HAS_PSRAM"
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "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,
libusb-1.0-0-dev,
libi2c-dev,
libuv1-dev,
openssl,
libssl-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(libusb-1.0)
BuildRequires: libi2c-devel
BuildRequires: pkgconfig(libuv)
# Web components:
BuildRequires: pkgconfig(openssl)
BuildRequires: pkgconfig(liborcania)

View File

@@ -7,8 +7,6 @@ default_envs = tbeam
extra_configs =
arch/*/*.ini
variants/*/platformio.ini
src/graphics/niche/InkHUD/PlatformioConfig.ini
description = Meshtastic
[env]
@@ -60,7 +58,7 @@ lib_deps =
mathertel/OneButton@2.6.1
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
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
erriez/ErriezCRC32@1.0.1
@@ -79,7 +77,7 @@ lib_deps =
${env.lib_deps}
end2endzone/NonBlockingRTTTL@1.3.0
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
[networking_base]
@@ -92,10 +90,6 @@ lib_deps =
lib_deps =
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
; (not included in native / portduino)
[environmental_base]
@@ -106,7 +100,6 @@ lib_deps =
adafruit/Adafruit BMP085 Library@1.2.4
adafruit/Adafruit BME280 Library@2.2.4
adafruit/Adafruit BMP3XX Library@2.1.5
adafruit/Adafruit DPS310@1.1.5
adafruit/Adafruit MCP9808 Library@2.0.2
adafruit/Adafruit INA260 Library@1.5.2
adafruit/Adafruit INA219@1.2.3

View File

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

View File

@@ -121,15 +121,10 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN
#define defaultBLEPin 123456
#if HAS_ETHERNET && !defined(USE_WS5500)
#if HAS_ETHERNET
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#endif // HAS_ETHERNET
#if HAS_WIFI
#include <WiFi.h>
#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)));
};
#endif // HAS_NETWORKING
#endif // HAS_ETHERNET || HAS_WIFI

View File

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

View File

@@ -7,6 +7,7 @@ static File openFile(const char *filename, bool fullAtomic)
{
concurrency::LockGuard g(spiLock);
LOG_DEBUG("Opening %s, fullAtomic=%d", filename, fullAtomic);
#ifdef ARCH_NRF52
FSCom.remove(filename);
return FSCom.open(filename, FILE_O_WRITE);
@@ -18,10 +19,10 @@ static File openFile(const char *filename, bool fullAtomic)
String filenameTmp = filename;
filenameTmp += ".tmp";
// FIXME: If we are doing a full atomic write, we may need to remove the old tmp file now
// if (fullAtomic) {
// FSCom.remove(filename);
// }
// If we are doing a full atomic write, remove the old tmp file now
if (fullAtomic) {
FSCom.remove(filename);
}
// clear any previous LFS errors
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_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA0031_ADDR 0x12
#define QMA6100P_ADDR 0x12
#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 MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
// -----------------------------------------------------------------------------
// ACCELEROMETER

View File

@@ -67,8 +67,6 @@ class ScanI2C
INA226,
NXP_SE050,
DFROBOT_RAIN,
DPS310,
LTR390UV,
} DeviceType;
// 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);
type = BMP_085;
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:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
switch (registerValue) {
@@ -349,8 +339,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
case SHT31_4x_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
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(VEML7700_ADDR, VEML7700, "VEML7700", (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(NAU7802_ADDR, NAU7802, "NAU7802", (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(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
#ifdef HAS_TPS65233
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
#endif

View File

@@ -1,7 +1,3 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS
#include "Default.h"
@@ -1104,16 +1100,12 @@ int32_t GPS::runOnce()
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()
{
#ifdef ARCH_ESP32
_serial_gps->flush(false);
#else
int x = _serial_gps->available();
while (x--)
_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
@@ -1125,7 +1117,7 @@ int GPS::prepareDeepSleep(void *unused)
}
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, ...) \
do { \
@@ -1133,22 +1125,11 @@ static const char *DETECTED_MESSAGE = "%s detected";
clearBuffer(); \
_serial_gps->write(TOWRITE "\r\n"); \
if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
LOG_INFO(DETECTED_MESSAGE, CHIP); \
LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \
return DRIVER; \
} \
} 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)
{
#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
@@ -1179,34 +1160,31 @@ GnssModel_t GPS::probe(int serialSpeed)
delay(20);
// 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_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
std::vector<ChipInfo> atgm = {
{"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H},
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */
{"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}};
PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500);
PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 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);
/* 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);
/* 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,3,0*3D\r\n"); // GSV OFF to reduce volume
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
std::vector<ChipInfo> airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}};
PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000);
PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500);
PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", 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);
// 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");
delay(20);
std::vector<ChipInfo> mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B},
{"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S},
{"LS20031", "MC-1513", GNSS_MODEL_LS20031}};
PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500);
PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
PROBE_SIMPLE("LS20031", "$PMTK605*31", "MC-1513", GNSS_MODEL_LS20031, 500);
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate));
@@ -1303,38 +1281,6 @@ GnssModel_t GPS::probe(int serialSpeed)
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()
{
int8_t _rx_gpio = config.position.rx_gpio;

View File

@@ -48,11 +48,6 @@ enum GPSPowerState : uint8_t {
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
*
@@ -235,8 +230,6 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
// Get GNSS model
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) || \
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
hspi = new SPIClass(HSPI);
@@ -182,9 +182,6 @@ bool EInkDisplay::connect()
// Init GxEPD2
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
#if defined(CROWPANEL_ESP32S3_5_EPAPER)
adafruitDisplay->setRotation(0);
#endif
}
#elif defined(PCA10059) || defined(ME25LS01)
{

View File

@@ -68,7 +68,7 @@ class EInkDisplay : public OLEDDisplay
// If display uses HSPI
#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;
#endif
@@ -77,4 +77,4 @@ class EInkDisplay : public OLEDDisplay
uint32_t lastDrawMsec = 0;
};
#endif
#endif

View File

@@ -324,14 +324,6 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
if (refresh != UNSPECIFIED)
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 (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
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();
// 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
enum frameFlagTypes : uint8_t {
BACKGROUND = (1 << 0), // For frames via display()
@@ -34,7 +30,6 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
COSMETIC = (1 << 2), // For splashes
DEMAND_FAST = (1 << 3), // Special case only
BLOCKING = (1 << 4), // Modifier - block while refresh runs
UNLIMITED_FAST = (1 << 5)
};
void addFrameFlag(frameFlagTypes flag);

View File

@@ -73,16 +73,6 @@
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#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 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
int LatchingBacklight::beforeDeepSleep(void *unused)
{
// Contingency only
// - pin wasn't set
// We shouldn't need to guard the block like this
// Contingency for:
// - settings corruption: settings.optionalMenuItems.backlight guards backlight code in MenuApplet
// - improper use in the future
if (pin != (uint8_t)-1) {
off();
pinMode(pin, INPUT); // High impedance - unnecessary?
pinMode(pin, INPUT); // High impedence - unnecessary?
} else
LOG_WARN("LatchingBacklight instantiated, but pin not set");
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.
// 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)
{
// 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 update(uint8_t *imageData, UpdateTypes type) = 0; // Change the display image
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?
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
bool updateRunning = false; // see EInk::busy()
uint32_t updateBegunAt = 0; // For initial pause before polling for update completion
uint32_t pollingInterval = 0; // How often to check if update complete (ms)
uint32_t updateBegunAt; // For initial pause before polling for update completion
uint32_t pollingInterval; // How often to check if update complete (ms)
};
} // namespace NicheGraphics::Drivers

View File

@@ -4,7 +4,7 @@
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()
{
// "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();
}
// Display an image on the display
void LCMEN213EFC1::update(uint8_t *imageData, UpdateTypes type)
{
this->updateType = type;
@@ -162,6 +161,13 @@ void LCMEN213EFC1::sendCommand(const uint8_t command)
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);
}

View File

@@ -45,24 +45,21 @@ class LCMEN213EFC1 : public EInk
void configFull(); // Configure display for FULL refresh
void configFast(); // Configure display for FAST refresh
void writeNewImage();
void writeOldImage(); // Used for "differential update", aka FAST refresh
void writeOldImage();
void detachFromUpdate();
bool isUpdateDone();
void finalizeUpdate();
protected:
uint8_t bufferOffsetX = 0; // 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)
uint32_t bufferSize = 0; // In bytes. Rows * Columns
uint8_t *buffer = nullptr;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
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; // In bytes. Rows * Columns
uint8_t *buffer;
UpdateTypes updateType;
uint8_t pin_dc = -1;
uint8_t pin_cs = -1;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
SPIClass *spi;
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.
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:
@@ -30,7 +30,7 @@ void setupNicheGraphics()
## Methods
### `update(uint8_t *imageData, UpdateTypes type)`
### `update(uint8_t *imageData, UpdateTypes type, bool async=true)`
Update the image on the display
@@ -39,6 +39,7 @@ Update the image on the display
- `FULL`
- `FAST`
- (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.
@@ -62,10 +63,6 @@ uint8_t xBits = (7-x) % 8;
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)`
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 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()`

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);
// 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)
pinMode(pin_rst, INPUT_PULLUP);
@@ -72,6 +72,13 @@ void SSD16XX::sendCommand(const uint8_t command)
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);
}

View File

@@ -39,24 +39,21 @@ class SSD16XX : public EInk
virtual void configUpdateSequence(); // Tell controller IC which operations to run
virtual void writeNewImage();
virtual void writeOldImage(); // Image which can be used at *next* update for "differential refresh"
virtual void writeOldImage();
virtual void detachFromUpdate();
virtual bool isUpdateDone() override;
virtual void finalizeUpdate() override;
protected:
uint8_t bufferOffsetX = 0; // 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)
uint32_t bufferSize = 0; // In bytes. Rows * Columns
uint8_t *buffer = nullptr;
UpdateTypes updateType = UpdateTypes::UNSPECIFIED;
uint8_t bufferOffsetX; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring?
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; // In bytes. Rows * Columns
uint8_t *buffer;
UpdateTypes updateType;
uint8_t pin_dc = -1;
uint8_t pin_cs = -1;
uint8_t pin_busy = -1;
uint8_t pin_rst = -1;
SPIClass *spi = nullptr;
uint8_t pin_dc, pin_cs, pin_busy, pin_rst;
SPIClass *spi;
SPISettings spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
};

View File

@@ -1,3 +1,3 @@
# 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
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.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
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 "main.h"
#include "RTC.h"
using namespace NicheGraphics;
@@ -18,15 +16,10 @@ InkHUD::Applet::Applet() : GFX(0, 0)
// The width and height will change dynamically, depending on Applet tiling
// If you're getting a "divide by zero error", consider it an assert:
// 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 all passes through here
// Hand off to the applet's tile, which will in-turn pass to the renderer
// The raw pixel output generated by AdafruitGFX drawing
// Hand off to the applet's tile, which will in-turn pass to the window manager
void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color)
{
// 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);
}
// Link our applet to a tile
// This can only be called by Tile::assignApplet
// The tile determines the applets dimensions
// Sets which tile the applet renders for
// Pixel output is passed to tile during render()
// This should only be called by Tile::assignApplet
void InkHUD::Applet::setTile(Tile *t)
{
// If we're setting (not clearing), make sure the link is "reciprocal"
@@ -47,32 +39,25 @@ void InkHUD::Applet::setTile(Tile *t)
assignedTile = t;
}
// The tile to which our applet is assigned
// Which tile will the applet render() to?
InkHUD::Tile *InkHUD::Applet::getTile()
{
return assignedTile;
}
// Draw the applet
void InkHUD::Applet::render()
{
assert(assignedTile); // Ensure that we have a 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
// Clear everything for future requests
wantRender = false; // Flag set by requestUpdate
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.
wantRender = false; // Clear the flag set by requestUpdate
wantAutoshow = false; // If we're rendering now, it means our request was considered. It may or may not have been granted.
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Our requested type has been considered by now. Tidy up.
updateDimensions();
resetDrawingSpace();
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 (Tile::highlightTarget == assignedTile) {
// Draw the highlight
@@ -92,8 +77,7 @@ void InkHUD::Applet::render()
}
// Does the applet want to render now?
// Checks whether the applet called requestUpdate recently, in response to an event
// Used by WindowManager::update
// Checks whether the applet called requestUpdate() recently, in response to an event
bool InkHUD::Applet::wantsToRender()
{
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?
// User specifies whether an applet has permission for this, using the on-screen menu
// Used by WindowManager::update
bool InkHUD::Applet::wantsToAutoshow()
{
return wantAutoshow;
}
// Which technique would this applet prefer that the display use to change the image?
// Used by WindowManager::update
Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
{
return wantUpdateType;
}
// Get size of the applet's drawing space from its tile
// Performed immediately before derived applet's drawing code runs
void InkHUD::Applet::updateDimensions()
{
assert(assignedTile);
@@ -132,20 +113,19 @@ void InkHUD::Applet::resetDrawingSpace()
setTextColor(BLACK); // Reset text params
setCursor(0, 0);
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
// 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,
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
// 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 (forgeround)
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
{
wantRender = true;
wantUpdateType = type;
inkhud->requestUpdate();
WindowManager::getInstance()->requestUpdate();
}
// 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
// Active applets are considered "enabled"
// 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
void InkHUD::Applet::activate()
{
@@ -166,7 +146,7 @@ void InkHUD::Applet::activate()
active = true;
}
// Called when an Applet stops running
// Called when an Applet stop running
// Inactive applets are considered "disabled"
// They should not listen for events, process data
// They will not be rendered
@@ -193,7 +173,7 @@ bool InkHUD::Applet::isActive()
// Begin showing the Applet
// 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()
{
if (!foreground) {
@@ -206,7 +186,7 @@ void InkHUD::Applet::bringToForeground()
// Stop showing the Applet
// 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()
{
if (foreground) {
@@ -216,10 +196,6 @@ void InkHUD::Applet::sendToBackground()
}
// 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()
{
return foreground;
@@ -272,7 +248,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalA
// Custom font
// - set with AppletFont::addSubstitution
// - 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);
// 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;
}
// 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) {
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
// 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)
{
GFX::setFont(f.gfxFont);
@@ -322,12 +299,20 @@ void InkHUD::Applet::setFont(AppletFont f)
}
// 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()
{
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
// Wrapper for getTextBounds
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
// Wrapper for getTextBounds
// Wrappe for getTextBounds
uint16_t InkHUD::Applet::getTextWidth(std::string 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
// 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
InkHUD::Applet::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
InkHUD::SignalStrength InkHUD::Applet::getSignalStrength(float snr, float rssi)
{
uint8_t score = 0;
@@ -391,14 +376,12 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num)
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)
{
// Custom font glyphs
// - set with AppletFont::addSubstitution
// - 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);
// Place the AdafruitGFX cursor to suit our "top" coord
@@ -545,7 +528,7 @@ std::string InkHUD::Applet::getTimeString(uint32_t epochSeconds)
#ifdef BUILD_EPOCH
constexpr uint32_t validAfterEpoch = BUILD_EPOCH - (SEC_PER_DAY * 30 * 6); // 6 Months prior to build
#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
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
// Don't give any human readable string
if (epochNow <= validAfterEpoch)
if (epochNow <= validAfterEpoch) {
LOG_DEBUG("RTC prior to buildtime");
return "";
}
// Times are invalid: argument time is significantly ahead of RTC
// Don't give any human readable string
if (daysAgo < -2)
if (daysAgo < -2) {
LOG_DEBUG("RTC in future");
return "";
}
// 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 "";
}
if (daysAgo > 1)
return to_string(daysAgo) + " days ago";
@@ -613,7 +602,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
// 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++;
}
@@ -630,7 +619,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
// Resulting string
std::string localized;
// Imperial
// Imeperial
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
uint32_t feet = meters * FEET_PER_METER;
// Distant (miles, rounded)
@@ -662,7 +651,6 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters)
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)
{
// 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
// 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
bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n)
bool InkHUD::Applet::approveNotification(InkHUD::Notification &n)
{
// By default, no objection
return true;
}
// Draw the standard header, used by most Applets
/*
┌───────────────────────────────┐
│ Applet::name here │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │
│ │
│ │
└───────────────────────────────┘
*/
void InkHUD::Applet::drawHeader(std::string text)
{
setFont(fontSmall);
// Y position for divider
// - between header text and messages
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
// Make sure to provide dimensions which have the correct aspect ratio (~2)
// 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)
{
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;
// Points for paths (a, b, and c)
/*
+-----------------------------+
--| a2 b2/c1 |
| |
| |
| |
--| a1 b1 c2 |
+-----------------------------+
| | | |
*/
Point a1 = {map(0, 0, 3, logoL, logoR), logoB};
Point a2 = {map(1, 0, 3, logoL, logoR), logoT};
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 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
/*
+-------------------------------+
| a2 |
| -| |
| -/ | |
| -/ | |
| -/# | |
| -/ # | |
| / # | |
| a1---------- |
+-------------------------------+
*/
Distance deltaA = {abs(a2.x - a1.x), abs(a2.y - a1.y)};
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
/*
| a2
| .
| ..
| aq1 ..
| # ..
| | # ..
|fromPath.y | # ..
| +----a1
|
| fromPath.x
+--------------------------------
*/
Distance fromPath;
fromPath.x = cos(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
Point aq1{a1.x - fromPath.x, a1.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, 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 cq2{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);
// Radius the intersection of quad b and quad c
/*
b2 / c1
####
## ##
/ \
/ \/ \
/ /\ \
/ / \ \
*/
// Don't attempt if logo is tiny
if (logoTh > 3) {
// 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));
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.
===================================
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
#include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib
#include "mesh/MeshTypes.h"
#include <GFX.h>
#include "./AppletFont.h"
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
#include "./InkHUD.h"
#include "./Persistence.h"
#include "./Applets/System/Notification/Notification.h"
#include "./Tile.h"
#include "./Types.h"
#include "./WindowManager.h"
#include "graphics/niche/Drivers/EInk/EInk.h"
namespace NicheGraphics::InkHUD
@@ -30,57 +112,37 @@ namespace NicheGraphics::InkHUD
using NicheGraphics::Drivers::EInk;
using std::to_string;
class Tile;
class WindowManager;
class Applet : public GFX
{
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();
void setTile(Tile *t); // Should only be called via Tile::setApplet
Tile *getTile(); // Tile with which this applet is linked
void setTile(Tile *t); // Applets draw via a tile (for multiplexing)
Tile *getTile();
// Rendering
void render(); // Draw the applet
bool wantsToRender(); // Check whether applet wants to render
bool wantsToAutoshow(); // Check whether applet wants to become foreground
void render();
bool wantsToRender(); // Check whether applet wants to render
bool wantsToAutoshow(); // Check whether applets wants to become foreground, to show new data, if permitted
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
void updateDimensions(); // Get current size from tile
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 isForeground();
// Event handlers
// Allow derived applets to handle changes in state
virtual void onRender() = 0; // All drawing happens here
virtual void onActivate() {}
@@ -88,62 +150,62 @@ class Applet : public GFX
virtual void onForeground() {}
virtual void onBackground() {}
virtual void onShutdown() {}
virtual void onButtonShortPress() {} // (System Applets only)
virtual void onButtonLongPress() {} // (System Applets only)
virtual void onButtonShortPress() {} // For use by 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
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. Also used as an identifier by InkHUD::getSystemApplet
const char *name = nullptr; // Shown in applet selection menu
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
void requestAutoshow(); // Ask for applet to be moved to foreground
// Tell WindowManager to update display
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 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 resetCrop(); // Removes setCrop()
// Text
void setFont(AppletFont f);
AppletFont getFont();
uint16_t getTextWidth(std::string 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, 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 printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY);
// 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 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
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
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
std::string getTimeString(uint32_t epochSeconds); // Human readable
std::string getTimeString(); // Current time, human readable
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
// Convenient references
InkHUD *inkhud = nullptr;
Persistence::Settings *settings = nullptr;
Persistence::LatestMessage *latestMessage = nullptr;
static AppletFont fontSmall, fontLarge; // General purpose fonts, used cross-applet
private:
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
// As set by setCrop
int16_t cropLeft = 0;
int16_t cropTop = 0;
uint16_t cropWidth = 0;
uint16_t cropHeight = 0;
int16_t cropLeft;
int16_t cropTop;
uint16_t cropWidth;
uint16_t cropHeight;
};
}; // namespace NicheGraphics::InkHUD

View File

@@ -12,7 +12,7 @@ InkHUD::AppletFont::AppletFont()
InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont) : gfxFont(&adafruitGFXFont)
{
// 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.
// 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;
}
/*
▲ ##### # ▲
│ # # │
lineHeight │ ### # │
│ # # # # │ heightAboveCursor
│ # # # # │
│ # # #### │
│ -----------------#----
│ # │ heightBelowCursor
▼ ### ▼
*/
uint8_t InkHUD::AppletFont::lineHeight()
{
return this->height;
@@ -91,7 +78,7 @@ void InkHUD::AppletFont::addSubstitution(const char *from, const char *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
void InkHUD::AppletFont::applySubstitutions(std::string *text)
{
@@ -100,7 +87,7 @@ void InkHUD::AppletFont::applySubstitutions(std::string *text)
// Find and replace
// - search for Substitution::from
// - replace with Substitution::to
// - replace with Subsitution::to
size_t i = text->find(s.from);
while (i != std::string::npos) {
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
// 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()
{
addSubstitution("Ђ", "\x80");

View File

@@ -15,7 +15,7 @@
#include "configuration.h"
#include <GFX.h> // GFXRoot drawing lib
#include <GFX.h>
namespace NicheGraphics::InkHUD
{
@@ -25,12 +25,11 @@ class AppletFont
{
public:
AppletFont();
explicit AppletFont(const GFXfont &adafruitGFXFont);
AppletFont(const GFXfont &adafruitGFXFont);
uint8_t lineHeight();
uint8_t heightAboveCursor();
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 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;
};
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

View File

@@ -6,6 +6,8 @@ using namespace NicheGraphics;
void InkHUD::MapApplet::onRender()
{
setFont(fontSmall);
// Abort if no markers to render
if (!enoughMarkers()) {
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
// - default: fit all nodes, plus padding
// - maybe overriden by derived applet
// - getMapSize *sets* passed parameters (C-style)
getMapSize(&widthMeters, &heightMeters);
// 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
// - longitude: triangle formed by x and y (on plane of the equator)
// - 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
uint32_t positionCount = 0;
@@ -133,7 +134,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
*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)
// 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
@@ -190,8 +191,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
// Longitude is trickier
float lng = node->position.longitude_i * 1e-7;
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west 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 travelled west from lngCenter to reach node
if (degEastward < degWestward)
easternmost = max(easternmost, lngCenter + degEastward);
else
@@ -257,7 +258,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
// Find x and y position based on node's position in nodeDB
assert(nodeDB->hasValidPosition(node));
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->hops_away // Hops away
);
@@ -287,7 +288,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
bool unknownHops = !node->has_hops_away && !isOurNode;
// 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
// Pick emblem style
@@ -387,7 +388,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
// Calculate marker and store it
markers.push_back(
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->hops_away // Hops away
));

View File

@@ -38,12 +38,13 @@ class MapApplet : public Applet
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
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 {
float eastMeters = 0; // Meters east of map center. Negative if west.
float northMeters = 0; // Meters north of map center. Negative if south.
float eastMeters = 0; // Meters east of mapCenter. Negative if west.
float northMeters = 0; // Meters north of mapCenter. Negative if south.
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);

View File

@@ -12,7 +12,7 @@ using namespace NicheGraphics;
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
{
// 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;
}
@@ -25,17 +25,17 @@ bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
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:
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
// - All other activity is *not* promiscuous
// To achieve this, our MeshModule *is* promiscuous, and we're manually reimplementing non-promiscuous behavior here,
// To achieve this, our MeshModule *is* promiscious, and we're manually reimplementing non-promiscuous behavior here,
// to match the code in MeshModule::callModules
}
// MeshModule packets arrive here
// 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?)
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
}
// Calculate 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
// Maximum number of cards we may ever need to render, in our tallest layout config
// May be slightly in excess of the true value: header not accounted for
uint8_t InkHUD::NodeListApplet::maxCards()
{
// Cache result. Shouldn't change during execution
@@ -87,7 +87,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
const uint16_t height = Tile::maxDisplayDimension();
// 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
cards = 1;
@@ -102,7 +102,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
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()
{
@@ -120,6 +120,9 @@ void InkHUD::NodeListApplet::onRender()
// 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
// Long-name will crop here
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
// Depending on tiles / rotation, this may be before we hit maxCards
if (cardTopY > height())
if (cardTopY > height()) {
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 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.
// 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);
// Draw signal bar rectangles, then placeholder lines once strength reached
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 barX = paddingW + (i * (gutterW + barW));
float barX = paddingW + (i * (gutterX + barW));
float barY = paddingH + (barHMax - barH);
// 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 "main.h"
namespace NicheGraphics::InkHUD
{
class NodeListApplet : public Applet, public MeshModule
{
protected:
// Info needed to draw a node card to the list
// - generated each time we hear a node
// Info used to draw one card to the node list
struct CardInfo {
static constexpr uint8_t HOPS_UNKNOWN = -1;
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
@@ -40,31 +37,31 @@ class NodeListApplet : public Applet, public MeshModule
NodeNum nodeNum = 0;
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
uint32_t distanceMeters = DISTANCE_UNKNOWN;
uint8_t hopsAway = HOPS_UNKNOWN;
uint8_t hopsAway = HOPS_UNKNOWN; // Unknown
};
public:
NodeListApplet(const char *name);
void onRender() override;
bool wantPacket(const meshtastic_MeshPacket *p) override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
// MeshModule overrides
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
protected:
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node
virtual std::string getHeaderText() = 0; // Ask derived class what the applet's title should be
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; // 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:
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h,
SignalStrength signal); // Draw a "mobile phone" style signal indicator
// UI element: a "mobile phone" style signal indicator
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength signal);
// Card Dimensions
// - for rendering and for maxCards calc
// Dimensions for drawing
// Used for render, and also for maxCards calc
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
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
void InkHUD::NewMsgExampleApplet::onRender()
{
setFont(fontSmall);
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

View File

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

View File

@@ -4,10 +4,10 @@
using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet()
void InkHUD::BatteryIconApplet::onActivate()
{
// Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon)
if (settings.optionalFeatures.batteryIcon)
bringToForeground();
// 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);
}
void InkHUD::BatteryIconApplet::onDeactivate()
{
// Stop having onPowerStatusUpdate called
powerStatusObserver.unobserve(&powerStatus->onNewStatus);
}
// 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.
// 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
// 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
if (this->socRounded != newSocRounded && settings->optionalFeatures.batteryIcon)
if (this->socRounded != newSocRounded && settings.optionalFeatures.batteryIcon)
requestUpdate();
// Store the new value

View File

@@ -11,22 +11,24 @@ It should be optional, enabled by the on-screen menu
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "PowerStatus.h"
namespace NicheGraphics::InkHUD
{
class BatteryIconApplet : public SystemApplet
class BatteryIconApplet : public Applet
{
public:
BatteryIconApplet();
void onRender() override;
void onActivate() override;
void onDeactivate() override;
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)
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);

View File

@@ -2,22 +2,15 @@
#include "./LogoApplet.h"
#include "mesh/NodeDB.h"
using namespace NicheGraphics;
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
{
OSThread::setIntervalFromNow(8 * 1000UL);
OSThread::enabled = true;
// Don't autostart the runOnce() timer
OSThread::disable();
textLeft = "";
textRight = "";
textTitle = xstr(APP_VERSION_SHORT);
fontTitle = fontSmall;
bringToForeground();
// This is then drawn with a FULL refresh by Renderer::begin
// Grab the WindowManager singleton, for convenience
windowManager = WindowManager::getInstance();
}
void InkHUD::LogoApplet::onRender()
@@ -55,24 +48,53 @@ void InkHUD::LogoApplet::onRender()
void InkHUD::LogoApplet::onForeground()
{
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it.
// If another applet has locked the display, ask it to exit
Applet *other = windowManager->whoLocked();
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()
{
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
OSThread::disable(); // Disable auto-dismiss timer, in case applet was dismissed early (sendToBackground from outside class)
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
// 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
void InkHUD::LogoApplet::onShutdown()
// Needs EInk::await after calling this method, to ensure display updates before shutdown
void InkHUD::LogoApplet::showShutdownScreen()
{
textLeft = "";
textRight = "";
@@ -80,13 +102,7 @@ void InkHUD::LogoApplet::onShutdown()
fontTitle = fontLarge;
bringToForeground();
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update
}
int32_t InkHUD::LogoApplet::runOnce()
{
sendToBackground();
return OSThread::disable();
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
}
#endif

View File

@@ -12,19 +12,24 @@
#include "configuration.h"
#include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD
{
class LogoApplet : public SystemApplet, public concurrency::OSThread
class LogoApplet : public Applet, public concurrency::OSThread
{
public:
LogoApplet();
void onRender() override;
void onForeground() 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:
int32_t runOnce() override;
@@ -33,6 +38,8 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
std::string textRight;
std::string textTitle;
AppletFont fontTitle;
WindowManager *windowManager = nullptr; // For convenience
};
} // namespace NicheGraphics::InkHUD

View File

@@ -2,11 +2,9 @@
#include "./MenuApplet.h"
#include "PowerStatus.h"
#include "RTC.h"
#include "airtime.h"
#include "power.h"
using namespace NicheGraphics;
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
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,
// or else you will unintentionally instantiate it
if (settings->optionalMenuItems.backlight) {
if (settings.optionalMenuItems.backlight) {
backlight = Drivers::LatchingBacklight::getInstance();
}
}
void InkHUD::MenuApplet::onActivate() {}
void InkHUD::MenuApplet::onForeground()
{
// 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:
// backlight on always when menu opens.
// Courtesy to T-Echo users who removed the capacitive touch button
if (settings->optionalMenuItems.backlight) {
if (settings.optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isOn())
backlight->peek();
}
// Prevent user applets requesting update while menu is open
// Handle button input with this applet
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true;
// Prevent user applets requested update while menu is open
windowManager->lock(this);
// Begin the auto-close timeout
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
OSThread::enabled = true;
// Upgrade the refresh to FAST, for guaranteed responsiveness
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onBackground()
@@ -64,7 +67,7 @@ void InkHUD::MenuApplet::onBackground()
// If device has a backlight which isn't controlled by aux button:
// 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 (settings->optionalMenuItems.backlight) {
if (settings.optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isLatched())
backlight->off();
@@ -74,8 +77,7 @@ void InkHUD::MenuApplet::onBackground()
OSThread::disable();
// Resume normal rendering and button behavior of user applets
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
windowManager->unlock(this);
// Restore the user applet whose tile we borrowed
if (borrowedTileOwner)
@@ -85,8 +87,8 @@ void InkHUD::MenuApplet::onBackground()
borrowedTileOwner = nullptr;
// 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
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
// We're only updating here to ugrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
}
// Open the menu
@@ -138,35 +140,43 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
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;
case ROTATE:
inkhud->rotate();
settings.rotation = (settings.rotation + 1) % 4;
windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
break;
case LAYOUT:
// Todo: smarter incrementing of tile count
settings->userTiles.count++;
settings.userTiles.count++;
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet
settings->userTiles.count++;
if (settings.userTiles.count == 3) // Skip 3 tiles: not done yet
settings.userTiles.count++;
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high
settings->userTiles.count = 1;
if (settings.userTiles.count > settings.userTiles.maxCount) // Loop around if tile count now too high
settings.userTiles.count = 1;
inkhud->updateLayout();
windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
break;
case TOGGLE_APPLET:
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor];
inkhud->updateAppletSelection();
settings.userApplets.active[cursor] = !settings.userApplets.active[cursor];
windowManager->changeActivatedApplets();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
break;
case ACTIVATE_APPLETS:
// Todo: remove this action? Already handled by TOGGLE_APPLET?
inkhud->updateAppletSelection();
windowManager->changeActivatedApplets();
break;
case TOGGLE_AUTOSHOW_APPLET:
@@ -175,14 +185,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case TOGGLE_NOTIFICATIONS:
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications;
settings.optionalFeatures.notifications = !settings.optionalFeatures.notifications;
break;
case SET_RECENTS:
// Set value of settings.recentlyActiveSeconds
// 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]));
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;
case SHUTDOWN:
@@ -192,7 +202,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case TOGGLE_BATTERY_ICON:
inkhud->toggleBatteryIcon();
windowManager->toggleBatteryIcon();
break;
case TOGGLE_BACKLIGHT:
@@ -223,13 +233,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
switch (page) {
case ROOT:
// 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("Send", MenuPage::SEND)); // TODO
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
// 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));
break;
@@ -242,7 +252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
case OPTIONS:
// Optional: backlight
if (settings->optionalMenuItems.backlight) {
if (settings.optionalMenuItems.backlight) {
assert(backlight);
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
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("Auto-show", MenuPage::AUTOSHOW));
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("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
&settings->optionalFeatures.notifications));
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
&settings->optionalFeatures.batteryIcon));
&settings.optionalFeatures.notifications));
items.push_back(
MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings.optionalFeatures.batteryIcon));
// TODO - GPS and Wifi switches
/*
@@ -319,6 +329,9 @@ void InkHUD::MenuApplet::onRender()
if (items.size() == 0)
LOG_ERROR("Empty Menu");
// Testing only
setFont(fontSmall);
// Dimensions for the slots where we will draw menuItems
const float padding = 0.05;
const uint16_t itemH = fontSmall.lineHeight() * 2;
@@ -384,7 +397,7 @@ void InkHUD::MenuApplet::onRender()
// Testing only: circle instead of check box
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 cbT = center - (cbWH / 2); // Checkbox : top
// Checkbox ticked
@@ -450,9 +463,9 @@ void InkHUD::MenuApplet::populateAppletPage()
{
assert(items.size() == 0);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.active[i]);
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings.userApplets.active[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
}
}
@@ -464,11 +477,11 @@ void InkHUD::MenuApplet::populateAutoshowPage()
{
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
if (settings->userApplets.active[i]) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.autoshow[i]);
if (settings.userApplets.active[i]) {
const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings.userApplets.autoshow[i]);
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
// 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()
{
// Render *far* off screen
// Render *waay* off screen
uint16_t height = 0;
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);

View File

@@ -3,9 +3,8 @@
#include "configuration.h"
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/WindowManager.h"
#include "./MenuItem.h"
#include "./MenuPage.h"
@@ -17,7 +16,7 @@ namespace NicheGraphics::InkHUD
class Applet;
class MenuApplet : public SystemApplet, public concurrency::OSThread
class MenuApplet : public Applet, public concurrency::OSThread
{
public:
MenuApplet();
@@ -31,8 +30,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void show(Tile *t); // Open the menu, onto a user tile
protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
int32_t runOnce() override;
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,
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
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
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

View File

@@ -3,20 +3,22 @@
#include "./NotificationApplet.h"
#include "./Notification.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
#include "RTC.h"
using namespace NicheGraphics;
InkHUD::NotificationApplet::NotificationApplet()
void InkHUD::NotificationApplet::onActivate()
{
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
// 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)
@@ -26,7 +28,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
// Abort if feature 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;
// Abort if this is an outgoing message
@@ -34,7 +36,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
return 0;
// 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)
return 0;
@@ -53,16 +55,13 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
n.sender = p->from;
}
// Close an old notification, if shown
dismiss();
// Check if we should display the notification
// A foreground applet might already be displaying this info
hasNotification = true;
currentNotification = n;
if (isApproved()) {
bringToForeground();
inkhud->forceUpdate();
WindowManager::getInstance()->forceUpdate();
} else
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"
fillRect(0, 0, width(), height(), WHITE);
setFont(fontSmall);
// Padding (horizontal)
const uint16_t padW = 4;
@@ -136,28 +137,6 @@ void InkHUD::NotificationApplet::onRender()
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
// Called internally when we first get a "notifiable event", and then again before render,
// in case autoshow swapped which applet was displayed
@@ -169,13 +148,7 @@ bool InkHUD::NotificationApplet::isApproved()
return false;
}
// Ask all visible user applets for approval
for (Applet *ua : inkhud->userApplets) {
if (ua->isForeground() && !ua->approveNotification(currentNotification))
return false;
}
return true;
return WindowManager::getInstance()->approveNotification(currentNotification);
}
// 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;
// Pick source of message
MessageStore::Message *message =
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
MessageStore::Message *message = isBroadcast ? &latestMessage.broadcast : &latestMessage.dm;
// Find info about the sender
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);

View File

@@ -3,7 +3,7 @@
/*
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
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 "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD
{
class NotificationApplet : public SystemApplet
class NotificationApplet : public Applet
{
public:
NotificationApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
void onActivate() override;
void onDeactivate() override;
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
bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render()
bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification; // Set when something notification-worthy happens. Used by render()
};
} // namespace NicheGraphics::InkHUD

View File

@@ -6,7 +6,8 @@ using namespace NicheGraphics;
InkHUD::PairingApplet::PairingApplet()
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
// Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
}
void InkHUD::PairingApplet::onRender()
@@ -30,22 +31,34 @@ void InkHUD::PairingApplet::onRender()
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()
{
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
// If another applet has locked the display, ask it to exit
Applet *other = windowManager->whoLocked();
if (other != nullptr)
other->sendToBackground();
windowManager->claimFullscreen(this); // Take ownership of the fullscreen tile
windowManager->lock(this); // Prevent user applets from requesting update
}
void InkHUD::PairingApplet::onBackground()
{
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
windowManager->releaseFullscreen(); // Relinquish ownership of the 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
// 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)
@@ -62,6 +75,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
// Store the passkey for rendering
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
bringToForeground();
}

View File

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

View File

@@ -4,6 +4,14 @@
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()
{
// 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 "graphics/niche/InkHUD/SystemApplet.h"
#include "graphics/niche/InkHUD/Applet.h"
namespace NicheGraphics::InkHUD
{
class PlaceholderApplet : public SystemApplet
class PlaceholderApplet : public Applet
{
public:
PlaceholderApplet();
void onRender() override;
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
// 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

View File

@@ -2,44 +2,12 @@
#include "./TipsApplet.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "main.h"
using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet()
{
// Decide which tips (if any) should be shown to user after the boot screen
// 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();
// Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
}
void InkHUD::TipsApplet::onRender()
@@ -85,7 +53,7 @@ void InkHUD::TipsApplet::onRender()
setFont(fontSmall);
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 += "This ensures data is saved.";
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);
}
// Grab fullscreen tile, and lock the window manager, when applet is shown
void InkHUD::TipsApplet::onForeground()
{
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // Our applet should handle button input (unless another system applet grabs it first)
windowManager->lock(this);
windowManager->claimFullscreen(this);
}
void InkHUD::TipsApplet::onBackground()
{
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
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);
windowManager->releaseFullscreen();
windowManager->unlock(this);
}
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()
{
tipQueue.pop_front();
@@ -218,15 +206,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots
if (settings->tips.firstBoot) {
settings->tips.firstBoot = false;
inkhud->persistence->saveSettings();
if (settings.tips.firstBoot) {
settings.tips.firstBoot = false;
saveDataToFlash();
}
// 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
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
}
// More tips left
@@ -234,4 +222,13 @@ void InkHUD::TipsApplet::onButtonShortPress()
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

View File

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

View File

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

View File

@@ -44,8 +44,10 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
void InkHUD::DMApplet::onRender()
{
setFont(fontSmall);
// Abort if no text message
if (!latestMessage->dm.sender) {
if (!latestMessage.dm.sender) {
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
return;
}
@@ -61,7 +63,7 @@ void InkHUD::DMApplet::onRender()
// RX Time
// - if valid
std::string timeString = getTimeString(latestMessage->dm.timestamp);
std::string timeString = getTimeString(latestMessage.dm.timestamp);
if (timeString.length() > 0) {
header += timeString;
header += ": ";
@@ -70,14 +72,14 @@ void InkHUD::DMApplet::onRender()
// Sender's id
// - shortname, if available, or
// - node id
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage.dm.sender);
if (sender && sender->has_user) {
header += sender->user.short_name;
header += " (";
header += sender->user.long_name;
header += ")";
} else
header += hexifyNodeNum(latestMessage->dm.sender);
header += hexifyNodeNum(latestMessage.dm.sender);
// Draw a "standard" applet header
drawHeader(header);
@@ -101,14 +103,14 @@ void InkHUD::DMApplet::onRender()
// Determine size if printed large
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 (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned)
setFont(fontSmall);
// 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

View File

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

View File

@@ -122,7 +122,7 @@ bool InkHUD::RecentsListApplet::isActive(uint32_t seenAtMs)
uint32_t now = millis();
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
@@ -134,7 +134,7 @@ std::string InkHUD::RecentsListApplet::getHeaderText()
// Print the length of our "Recents" time-window
text += "Last ";
text += to_string(settings->recentlyActiveSeconds / 60);
text += to_string(settings.recentlyActiveSeconds / 60);
text += " mins";
// Print the node count

View File

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

View File

@@ -33,7 +33,7 @@ class Applet;
class ThreadedMessageApplet : public Applet
{
public:
explicit ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet() = delete;
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;
explicit MessageStore(std::string label); // Label determines filename in flash
MessageStore(std::string label); // Label determines filename in flash
void saveToFlash();
void loadFromFlash();

View File

@@ -5,21 +5,17 @@
using namespace NicheGraphics;
// Load settings and latestMessage data
void InkHUD::Persistence::loadSettings()
void InkHUD::loadDataFromFlash()
{
// 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
Settings loadedSettings;
InkHUD::Settings loadedSettings;
bool loadSucceeded = FlashData<Settings>::load(&loadedSettings, "settings");
if (loadSucceeded && loadedSettings.meta.version == SETTINGS_VERSION && loadedSettings.meta.version != 0)
settings = loadedSettings; // Version matched, replace the defaults with the loaded values
else
LOG_WARN("Settings version changed. Using defaults");
}
// Load settings and latestMessage data
void InkHUD::Persistence::loadLatestMessage()
{
// Load previous "latestMessages" data from flash
MessageStore store("latest");
store.loadFromFlash();
@@ -36,15 +32,12 @@ void InkHUD::Persistence::loadLatestMessage()
}
}
// Save the InkHUD settings to flash
void InkHUD::Persistence::saveSettings()
// Save settings and latestMessage data
void InkHUD::saveDataToFlash()
{
// Save the InkHUD settings to flash
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
MessageStore store("latest");
store.messages.push_back(latestMessage.dm);
@@ -53,31 +46,14 @@ void InkHUD::Persistence::saveLatestMessage()
store.saveToFlash();
}
/*
void InkHUD::Persistence::printSettings(Settings *settings)
{
if (SETTINGS_VERSION != 2)
LOG_WARN("Persistence::printSettings was written for SETTINGS_VERSION=2, current is %d", SETTINGS_VERSION);
// Holds InkHUD settings while running
// Saved back to Flash at shutdown
// Accessed by including persistence.h
InkHUD::Settings InkHUD::settings;
LOG_DEBUG("meta.version=%d", settings->meta.version);
LOG_DEBUG("userTiles.count=%d", settings->userTiles.count);
LOG_DEBUG("userTiles.maxCount=%d", settings->userTiles.maxCount);
LOG_DEBUG("userTiles.focused=%d", settings->userTiles.focused);
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);
}
*/
// Holds copies of the most recent broadcast and DM messages while running
// Saved to Flash at shutdown
// Accessed by including persistence.h
InkHUD::LatestMessage InkHUD::latestMessage;
#endif

View File

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

View File

@@ -1,7 +1,6 @@
[inkhud]
build_src_filter =
+<graphics/niche/>; Include the nicheGraphics directory
+<../variants/$PIOENV>; Include nicheGraphics.h from our variant folder
board_level = extra
build_src_filter = +<../variants/$PIOENV> ; Include nicheGraphics.h
build_flags =
-D MESHTASTIC_INCLUDE_NICHE_GRAPHICS ; Use NicheGraphics
-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