mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-21 18:22:32 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dec6af5dc | ||
|
|
11444621ae | ||
|
|
98f1b3296c | ||
|
|
26c43e7091 | ||
|
|
9b9447858a | ||
|
|
3151cfb064 | ||
|
|
c327fee986 | ||
|
|
a4f53270e8 | ||
|
|
a7456a1126 | ||
|
|
8381512ce4 | ||
|
|
20a669029b | ||
|
|
f2e6c6de58 | ||
|
|
8fa44c3590 | ||
|
|
f5b7c33d4e | ||
|
|
be8e663d39 | ||
|
|
0d4a9748e3 | ||
|
|
bd477f0fb2 | ||
|
|
5317895a5e | ||
|
|
542b8b26ce | ||
|
|
64da384fc1 | ||
|
|
a595fc4642 | ||
|
|
ac135be8cd | ||
|
|
5ea59a1c4d | ||
|
|
8bafd87b76 | ||
|
|
6b40e9a5e0 | ||
|
|
a2f6fd9298 | ||
|
|
bc604fc9ba | ||
|
|
9baaa13897 | ||
|
|
65e53be8b0 | ||
|
|
7f5283e95d | ||
|
|
ae4ab48ddc | ||
|
|
afccf1da02 | ||
|
|
fc07c7c01f | ||
|
|
aeb906414f | ||
|
|
a6c6b45576 | ||
|
|
13806cce93 | ||
|
|
e7eee0995a | ||
|
|
5687bd09c6 | ||
|
|
f3db895832 | ||
|
|
dd2ffe5d14 | ||
|
|
ec10e784e1 | ||
|
|
649faa1d93 | ||
|
|
669f96b367 | ||
|
|
996821d18e | ||
|
|
2e172b019e | ||
|
|
3df05cd5c1 | ||
|
|
8bb85cdc69 | ||
|
|
9f6e23754c | ||
|
|
5a7cfdffb3 | ||
|
|
caafddfdfa | ||
|
|
ee0e31be97 | ||
|
|
f1da6469a3 | ||
|
|
68e57dd3a7 | ||
|
|
2504311671 | ||
|
|
aa29315624 | ||
|
|
e3bcb87cf0 | ||
|
|
2530dc44c7 | ||
|
|
70a8fe30b7 | ||
|
|
30e538e5ed | ||
|
|
58dbc3c702 | ||
|
|
210c904604 | ||
|
|
27fdab7c8d | ||
|
|
ba3e1abb5e | ||
|
|
ce194e2162 | ||
|
|
595166db8e | ||
|
|
38d9e34a66 | ||
|
|
c1e0977db3 | ||
|
|
b4f32e7645 | ||
|
|
6bb0c95c95 | ||
|
|
4e958c9230 | ||
|
|
64cf1890f2 | ||
|
|
6a857b00db | ||
|
|
6d60a061bc | ||
|
|
6a09ddef18 | ||
|
|
d48e803b7b | ||
|
|
9061b3d8c3 | ||
|
|
2839539c6c | ||
|
|
f77a1798f3 | ||
|
|
664b558a36 | ||
|
|
c1865f127c | ||
|
|
3a69539192 | ||
|
|
cfcaf28ace | ||
|
|
d9e93f3944 | ||
|
|
60470211e5 | ||
|
|
c5851a4a0c | ||
|
|
0c0b2446b7 | ||
|
|
ce9352fd23 | ||
|
|
375ae5fe77 | ||
|
|
665d35196d | ||
|
|
9757f9ae53 | ||
|
|
e45d0c4dcf | ||
|
|
5c9f22bc18 | ||
|
|
d8287e9cdb | ||
|
|
c66e064f42 | ||
|
|
364fc84aaa | ||
|
|
fe4f86bc84 | ||
|
|
982b2e33ff | ||
|
|
8190098bb8 | ||
|
|
c99411311b | ||
|
|
da8a048dce | ||
|
|
b4de495154 | ||
|
|
aaa6af3f38 | ||
|
|
b11b322581 | ||
|
|
60b38215ce | ||
|
|
53e68515dd | ||
|
|
7676ae9184 | ||
|
|
b385feefa5 | ||
|
|
fc50b4abc0 | ||
|
|
0104a2eb3a | ||
|
|
23fdc9d1a9 | ||
|
|
ea4a678308 | ||
|
|
6566c4f15e | ||
|
|
6f39f1740f | ||
|
|
7b61bdbfd2 | ||
|
|
8e65db19b0 |
38
.github/ISSUE_TEMPLATE/bug-report-or-feature-proposal.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug-report-or-feature-proposal.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report or feature proposal
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please - if you just have a question (i.e. not a bug report or a feature proposal), post in our [forum](https://meshtastic.discourse.group/) instead.
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Device info:**
|
||||||
|
- Device model: [e.g. TBEAM]
|
||||||
|
- Software Version [e.g. 0.7.8]
|
||||||
|
|
||||||
|
**Smartphone information (if relevant):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- App Version [e.g. 0.7.2]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## Thank you for sending in a pull request, here's some tips to get started!
|
||||||
|
|
||||||
|
(Please delete all these tips and replace with your text)
|
||||||
|
|
||||||
|
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
|
||||||
|
to say "hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
|
||||||
|
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
|
||||||
|
- Please do not check in files that don't have real changes
|
||||||
|
- Please do not reformat lines that you didn't have to change the code on
|
||||||
|
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor,
|
||||||
|
because automatically follows our indentation rules and it's auto reformatting will not cause spurious changes to lines.
|
||||||
|
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
|
||||||
|
- If your other co-developers have comments on your PR please tweak as needed.
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -59,6 +59,7 @@
|
|||||||
"cfsr",
|
"cfsr",
|
||||||
"descs",
|
"descs",
|
||||||
"ocrypto",
|
"ocrypto",
|
||||||
"protobufs"
|
"protobufs",
|
||||||
|
"wifi"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
11
README.md
11
README.md
@@ -24,20 +24,23 @@ We currently support three models of radios.
|
|||||||
|
|
||||||
- TTGO T-Beam
|
- TTGO T-Beam
|
||||||
|
|
||||||
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended)
|
- [T-Beam V1.0 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
||||||
- [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html)
|
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
||||||
- 3D printable cases
|
- 3D printable cases
|
||||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||||
|
|
||||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||||
|
|
||||||
|
- 3D printable case
|
||||||
|
- [TTGO LORA32 v1](https://www.thingiverse.com/thing:3385109)
|
||||||
|
|
||||||
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
||||||
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
||||||
|
|
||||||
**Make sure to get the frequency for your country**
|
**Make sure to get the frequency for your country**
|
||||||
|
|
||||||
- US/JP/AU/NZ - 915MHz
|
- US/JP/AU/NZ/CA - 915MHz
|
||||||
- CN - 470MHz
|
- CN - 470MHz
|
||||||
- EU - 868MHz, 433MHz
|
- EU - 868MHz, 433MHz
|
||||||
|
|
||||||
@@ -47,6 +50,8 @@ Getting a version that includes a screen is optional, but highly recommended.
|
|||||||
|
|
||||||
Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
||||||
|
|
||||||
|
Be **very careful** to install the correct load for your board. In particular the popular 'T-BEAM' radio from TTGO is not called 'TTGO-Lora' (that is a different board). So don't install the 'TTGO-Lora' build on a TBEAM, it won't work correctly.
|
||||||
|
|
||||||
Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
|
Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
|
||||||
|
|
||||||
### Installing from a GUI - Windows and Mac
|
### Installing from a GUI - Windows and Mac
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export VERSION=0.7.7
|
export VERSION=0.7.10
|
||||||
@@ -39,6 +39,7 @@ This software is 100% open source and developed by a group of hobbyist experimen
|
|||||||
|
|
||||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||||
|
|
||||||
|
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 13 languages. Fairly stable and we are working through bugs to get to 1.0.
|
||||||
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
||||||
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
||||||
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
||||||
@@ -63,8 +64,9 @@ If you'd like to help with development, the source code is [on github](https://g
|
|||||||
|
|
||||||
## Supported hardware
|
## Supported hardware
|
||||||
|
|
||||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Make
|
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4001178678568.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Also, the version of the T-Beam we link to is shipped with Meshtastic **preinstalled** by TTGO, so you don't have to install it yourself.
|
||||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
|
||||||
|
Make sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
||||||
|
|
||||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This project is still pretty young but moving at a pretty good pace. Not all fea
|
|||||||
Most of these problems should be solved by the beta release (within three months):
|
Most of these problems should be solved by the beta release (within three months):
|
||||||
|
|
||||||
- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
|
||||||
- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us)
|
- The encryption implementation is good but see this list of [caveats](software/crypto.md#summary-of-strengthsweaknesses-of-our-current-implementation) to determine risks you might face.
|
||||||
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
|
||||||
- The Android API needs to be documented better
|
- The Android API needs to be documented better
|
||||||
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
|
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
|
||||||
|
|||||||
@@ -2,37 +2,23 @@
|
|||||||
|
|
||||||
You probably don't care about this section - skip to the next one.
|
You probably don't care about this section - skip to the next one.
|
||||||
|
|
||||||
- make new android release
|
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors (plan for geofence mode and battery save mode)
|
||||||
- check in our modified arduino binaries
|
|
||||||
- post bug on esp32-arduino
|
|
||||||
- router mode
|
|
||||||
- let users set arbitary params in android
|
|
||||||
- encryption review findings writeup
|
|
||||||
- NRF52 BLE support
|
- NRF52 BLE support
|
||||||
- DSR
|
|
||||||
|
|
||||||
# Medium priority
|
# Medium priority
|
||||||
|
|
||||||
Items to complete before the first beta release.
|
Items to complete before 1.0.
|
||||||
|
|
||||||
- turn on watchdog timer (because lib code seems buggy)
|
# Post 1.0 ideas
|
||||||
- show battery level as % full
|
|
||||||
- rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
|
||||||
|
|
||||||
# Pre-beta priority
|
|
||||||
|
|
||||||
During the beta timeframe the following improvements 'would be nice'
|
|
||||||
|
|
||||||
- finish DSR for unicast
|
- finish DSR for unicast
|
||||||
- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/ . Might need to add enforcement for europe though.
|
- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/ . Might need to add enforcement for europe though.
|
||||||
- pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user.
|
|
||||||
- make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
- make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
||||||
- if radio params change fundamentally, discard the nodedb
|
- if radio params change fundamentally, discard the nodedb
|
||||||
- re-enable the bluetooth battery level service on the T-BEAM
|
- re-enable the bluetooth battery level service on the T-BEAM
|
||||||
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
|
|
||||||
- provide generalized (but slow) internet message forwarding service if one of our nodes has internet connectivity (MQTT) [ Not a requirement but a personal interest ]
|
- provide generalized (but slow) internet message forwarding service if one of our nodes has internet connectivity (MQTT) [ Not a requirement but a personal interest ]
|
||||||
|
|
||||||
# Low priority
|
# Low priority ideas
|
||||||
|
|
||||||
Items after the first final candidate release.
|
Items after the first final candidate release.
|
||||||
|
|
||||||
@@ -62,11 +48,17 @@ Items after the first final candidate release.
|
|||||||
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||||
- read the PMU battery fault indicators and blink/led/warn user on screen
|
- read the PMU battery fault indicators and blink/led/warn user on screen
|
||||||
- discard very old nodedb records (> 1wk)
|
- discard very old nodedb records (> 1wk)
|
||||||
- add a watchdog timer
|
|
||||||
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||||
- report esp32 device code bugs back to the mothership via android
|
- report esp32 device code bugs back to the mothership via android
|
||||||
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
|
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
|
||||||
|
|
||||||
|
Changes related to wifi support on ESP32:
|
||||||
|
|
||||||
|
- iram space: https://esp32.com/viewtopic.php?t=8460
|
||||||
|
- set https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html spi ram bss
|
||||||
|
- figure out if iram or bluetooth classic caused ble problems
|
||||||
|
- post bug on esp32-arduino with BLE bug findings
|
||||||
|
|
||||||
# Spinoff project ideas
|
# Spinoff project ideas
|
||||||
|
|
||||||
- an open source version of https://www.burnair.ch/skynet/
|
- an open source version of https://www.burnair.ch/skynet/
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ in these instructions I describe use of their command line tool.
|
|||||||
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
2. Install [PlatformIO](https://platformio.org/platformio-ide)
|
||||||
3. Download this git repo and cd into it
|
3. Download this git repo and cd into it
|
||||||
4. Run `git submodule update --init --recursive` to pull in dependencies this project needs.
|
4. Run `git submodule update --init --recursive` to pull in dependencies this project needs.
|
||||||
5. If you are outside the USA, edit [platformio.ini](/platformio.ini) to set the correct frequency range for your country. The line you need to change starts with `hw_version` and instructions are provided above that line. Options are provided for `EU433`, `EU835`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries.
|
5. If you are outside the USA, run "export COUNTRY=EU865" (or whatever) to set the correct frequency range for your country. Options are provided for `EU433`, `EU865`, `CN`, `JP` and `US` (default). Pull-requests eagerly accepted for other countries.
|
||||||
6. Plug the radio into your USB port
|
6. Plug the radio into your USB port
|
||||||
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`).
|
7. Type `pio run --environment XXX -t upload` (This command will fetch dependencies, build the project and install it on the board via USB). For XXX, use the board type you have (either `tbeam`, `heltec`, `ttgo-lora32-v1`, `ttgo-lora32-v2`).
|
||||||
8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it.
|
8. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it.
|
||||||
|
|||||||
@@ -5,33 +5,47 @@ the project developers are not cryptography experts. Therefore we ask two things
|
|||||||
|
|
||||||
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our
|
- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our
|
||||||
notes below and offering advice? We will happily give as much or as little credit as you wish ;-).
|
notes below and offering advice? We will happily give as much or as little credit as you wish ;-).
|
||||||
- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary. But until
|
- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary
|
||||||
it is reviewed by someone smarter than us, assume it might have flaws.
|
(but we can't yet make a more confident statement).
|
||||||
|
|
||||||
## Notes on implementation
|
## Summary of strengths/weaknesses of our current implementation
|
||||||
|
|
||||||
|
Based on comments from reviewers (see below), here's some tips for usage of these radios. So you can know the level of protection offered:
|
||||||
|
|
||||||
|
* It is pretty likely that the AES256 security is implemented 'correctly' and an observer will not be able to decode your messages.
|
||||||
|
* Warning: If an attacker is able to get one of the radios in their position, they could either a) extract the channel key from that device or b) use that radio to listen to new communications.
|
||||||
|
* Warning: If an attacker is able to get the "Channel QR code/URL" that you share with others - that attacker could then be able to read any messages sent on the channel (either tomorrow or in the past - if they kept a raw copy of those broadcast packets)
|
||||||
|
|
||||||
|
Possible future areas of work (if there is enough interest - post in our [forum](https://meshtastic.discourse.group) if you want this):
|
||||||
|
|
||||||
|
1. Optionally requiring users to provide a PIN to regain access to the mesh. This could be based on: intentionally locking the device, time since last use, or any member could force all members to reauthenticate,
|
||||||
|
2. Until a device reauthenticates, any other access via BLE or USB would be blocked (this would protect against attackers who are not prepared to write custom software to extract and reverse engineer meshtastic flash memory)
|
||||||
|
3. Turning on read-back protection in the device fuse-bits (this would extend protection in #2 to block all but **extremely** advanced attacks involving chip disassembly)
|
||||||
|
4. Time limiting keys used for message transmission and automatically cycling them on a schedule. This would protect past messages from being decoded even if an attacker learns the current key.
|
||||||
|
|
||||||
|
### Notes for reviewers
|
||||||
|
|
||||||
|
If you are reviewing our implementation, this is a brief statement of our method.
|
||||||
|
|
||||||
- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
|
- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
|
||||||
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
|
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
|
||||||
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
|
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.
|
||||||
|
|
||||||
Parameters for our CTR implementation:
|
|
||||||
|
|
||||||
- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
|
- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
|
||||||
- Each SubPacket will be sent as a series of 16 byte BLOCKS.
|
- The node number concatenated with the packet number is used as the NONCE. This nonce will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero.
|
||||||
- The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent
|
- The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. (Cleartext is acceptable because it merely provides IV for each encryption run)
|
||||||
in cleartext with each packet. The node number can be derived from the "from" field of each packet.
|
- Each 16 byte BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
|
||||||
- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
|
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Since our packets are small counter portion will really never be higher than 32 (five bits).
|
||||||
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits).
|
|
||||||
|
|
||||||
```
|
## Comments from reviewer #1
|
||||||
You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally.
|
|
||||||
For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key.
|
|
||||||
|
|
||||||
The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key.
|
This reviewer is a cryptography professional, but would like to remain anonymous. We thank them for their comments ;-):
|
||||||
|
|
||||||
Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes.
|
I'm assuming that meshtastic is being used to hike in places where someone capable is trying to break it - like you were going to walk around DefCon using these. I spent about an hour reviewing the encryption, and have the following notes:
|
||||||
```
|
|
||||||
|
|
||||||
## Remaining todo
|
* The write-up isn't quite as clear as the code.
|
||||||
|
* The code is using AES-CTR mode correctly to ensure confidentiality.
|
||||||
|
* The comment for initNonce really covers the necessary information.
|
||||||
|
* I think the bigger encryption question is "what does the encryption need to do"? As it stands, an attacker who has yet to capture any of the devices cannot reasonably capture text or location data. An attacker who captures any device in the channel/mesh can read everything going to that device, everything stored on that device, and any other communication within the channel that they captured in encrypted form. If that capability basically matches your expectations, it is suitable for whatever adventures this was intended for, then, based on information publicly available or widely disclosed, the encryption is good. If those properties are distressing (like, device history is deliberately limited and you don't want a device captured today to endanger the information sent over the channel yesterday) we could talk about ways to achieve that (most likely synchronizing time and replacing the key with its own SHA256 every X hours, and ensuring the old key is not retained unnecessarily).
|
||||||
|
* Two other things to keep in mind are that AES-CTR does not itself provide authenticity (e.g. an attacker can flip bits in replaying data and scramble the resulting plaintext), and that the current scheme gives some hints about transmission in the size. So, if you worry about an adversary deliberately messing-up messages or knowing the length of a text message, it looks like those might be possible.
|
||||||
|
|
||||||
- Have the app change the crypto key when the user generates a new channel
|
I'm guessing that the network behaves somewhat like a store-and-forward network - or, at least, that the goal is to avoid establishing a two-way connection to transmit data. I'm afraid I haven't worked with mesh networks much, but remember studying them briefly in school about ten years ago.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
We build our own custom version of esp32-arduino, in order to get some fixes we've made but haven't yet been merged in master.
|
We build our own custom version of esp32-arduino, in order to get some fixes we've made but haven't yet been merged in master.
|
||||||
|
|
||||||
These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because
|
These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because
|
||||||
you'll automatically get our fixed libraries.
|
you'll automatically get our fixed libraries.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -12,5 +12,5 @@ you'll automatically get our fixed libraries.
|
|||||||
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html
|
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html
|
||||||
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin
|
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin
|
||||||
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
||||||
cp -ar components/arduino/ ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -19,20 +19,21 @@ reliable messaging tasks (stage one for DSR):
|
|||||||
- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender
|
- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender
|
||||||
- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service)
|
- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service)
|
||||||
- DONE test one hop ack/nak with the python framework
|
- DONE test one hop ack/nak with the python framework
|
||||||
- Do stress test with acks
|
- DONE Do stress test with acks
|
||||||
|
|
||||||
dsr tasks
|
dsr tasks
|
||||||
|
|
||||||
- oops I might have broken message reception
|
- DONE oops I might have broken message reception
|
||||||
- DONE Don't use broadcasts for the network pings (close open github issue)
|
- DONE Don't use broadcasts for the network pings (close open github issue)
|
||||||
- DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders
|
- DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders
|
||||||
- test multihop delivery with the python framework
|
- DONE test multihop delivery with the python framework
|
||||||
|
|
||||||
optimizations / low priority:
|
optimizations / low priority:
|
||||||
|
|
||||||
|
- read this [this](http://pages.cs.wisc.edu/~suman/pubs/nadv-mobihoc05.pdf) paper and others and make our naive flood routing less naive
|
||||||
- read @cyclomies long email with good ideas on optimizations and reply
|
- read @cyclomies long email with good ideas on optimizations and reply
|
||||||
- Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
|
- DONE Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
|
||||||
- make android app warn if firmware is too old or too new to talk to
|
- DONE make android app warn if firmware is too old or too new to talk to
|
||||||
- change nodenums and packetids in protobuf to be fixed32
|
- change nodenums and packetids in protobuf to be fixed32
|
||||||
- low priority: think more careful about reliable retransmit intervals
|
- low priority: think more careful about reliable retransmit intervals
|
||||||
- make ReliableRouter.pending threadsafe
|
- make ReliableRouter.pending threadsafe
|
||||||
|
|||||||
27
docs/software/pinetab.md
Normal file
27
docs/software/pinetab.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Pinetab
|
||||||
|
|
||||||
|
These are **preliminary** notes on support for Meshtastic in the Pinetab.
|
||||||
|
|
||||||
|
A RF95 is connected via a CS341 USB-SPI chip.
|
||||||
|
|
||||||
|
Pin assignments:
|
||||||
|
CS0 from RF95 goes to CS0 on CS341
|
||||||
|
DIO0 from RF95 goes to INT on CS341
|
||||||
|
RST from RF95 goes to RST on CS341
|
||||||
|
|
||||||
|
This linux driver claims to provide USB-SPI support: https://github.com/gschorcht/spi-ch341-usb
|
||||||
|
Notes here on using that driver: https://www.linuxquestions.org/questions/linux-hardware-18/ch341-usb-to-spi-adaptor-driver-doesn%27t-work-4175622736/
|
||||||
|
|
||||||
|
Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/
|
||||||
|
|
||||||
|
## Task list
|
||||||
|
|
||||||
|
* Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only
|
||||||
|
Use ARM linux: https://platformio.org/platforms/linux_arm
|
||||||
|
And linux native: https://platformio.org/platforms/native
|
||||||
|
|
||||||
|
* Test cs341 driver - just test reading/writing a register and detecting interrupts, confirm can see rf95
|
||||||
|
* Make a radiolib spi module that targets the cs341 (and builds on linux)
|
||||||
|
* use new radiolib module to hook pinebook lora to meshtastic, confirm mesh discovery works
|
||||||
|
* Make a subclass of StreamAPI that works as a posix TCP server
|
||||||
|
* Use new TCP endpoint from meshtastic-python
|
||||||
18
linker/esp32.extram.bss.ld
Normal file
18
linker/esp32.extram.bss.ld
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/* This section is only included if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
|
||||||
|
is set, to link some sections to BSS in PSRAM */
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
/* external memory bss, from any global variable with EXT_RAM_ATTR attribute*/
|
||||||
|
.ext_ram.bss (NOLOAD) :
|
||||||
|
{
|
||||||
|
_ext_ram_bss_start = ABSOLUTE(.);
|
||||||
|
*(.ext_ram.bss*)
|
||||||
|
*libnet80211.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||||
|
*libpp.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||||
|
*liblwip.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||||
|
*libbt.a:(EXCLUDE_FILE (libbtdm_app.a) .dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
_ext_ram_bss_end = ABSOLUTE(.);
|
||||||
|
} > extern_ram_seg
|
||||||
|
}
|
||||||
@@ -17,12 +17,14 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR
|
|||||||
; REQUIRED environment variables - if not set the specified default will be sued
|
; REQUIRED environment variables - if not set the specified default will be sued
|
||||||
; The following environment variables must be set in the shell if you'd like to override them.
|
; The following environment variables must be set in the shell if you'd like to override them.
|
||||||
; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish"
|
; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish"
|
||||||
; HW_VERSION (default US)
|
; COUNTRY (default US), i.e. "export COUNTRY=EU865"
|
||||||
; APP_VERSION (default emptystring)
|
; APP_VERSION (default emptystring)
|
||||||
; HW_VERSION (default emptystring)
|
; HW_VERSION (default emptystring)
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
|
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
; customize the partition table
|
; customize the partition table
|
||||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||||
board_build.partitions = partition-table.csv
|
board_build.partitions = partition-table.csv
|
||||||
@@ -67,7 +69,7 @@ debug_tool = jlink
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||||
SPI
|
SPI
|
||||||
; 1260 ; OneButton - not used yet
|
1260 ; OneButton library for non-blocking button debounce
|
||||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||||
https://github.com/meshtastic/arduino-fsm.git
|
https://github.com/meshtastic/arduino-fsm.git
|
||||||
@@ -78,16 +80,17 @@ lib_deps =
|
|||||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||||
[esp32_base]
|
[esp32_base]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
framework = arduino
|
|
||||||
src_filter =
|
src_filter =
|
||||||
${env.src_filter} -<nrf52/>
|
${env.src_filter} -<nrf52/>
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags} -Wall -Wextra -Isrc/esp32
|
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue
|
||||||
|
# Hmm - this doesn't work yet
|
||||||
|
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||||
lib_ignore = segger_rtt
|
lib_ignore = segger_rtt
|
||||||
platform_packages =
|
platform_packages =
|
||||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git
|
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#f26c4f96fefd13ed0ed042e27954f8aba6328f6b
|
||||||
|
|
||||||
; The 1.0 release of the TBEAM board
|
; The 1.0 release of the TBEAM board
|
||||||
[env:tbeam]
|
[env:tbeam]
|
||||||
@@ -96,6 +99,7 @@ board = ttgo-t-beam
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${env.lib_deps}
|
${env.lib_deps}
|
||||||
https://github.com/meshtastic/AXP202X_Library.git
|
https://github.com/meshtastic/AXP202X_Library.git
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${esp32_base.build_flags} -D TBEAM_V10
|
${esp32_base.build_flags} -D TBEAM_V10
|
||||||
|
|
||||||
@@ -130,7 +134,6 @@ build_flags =
|
|||||||
; For more details see my post in the forum.
|
; For more details see my post in the forum.
|
||||||
[env:cubecellplus]
|
[env:cubecellplus]
|
||||||
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
||||||
framework = arduino
|
|
||||||
board = cubecell_board_plus
|
board = cubecell_board_plus
|
||||||
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
||||||
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||||
@@ -140,7 +143,6 @@ src_filter =
|
|||||||
; Common settings for NRF52 based targets
|
; Common settings for NRF52 based targets
|
||||||
[nrf52_base]
|
[nrf52_base]
|
||||||
platform = nordicnrf52
|
platform = nordicnrf52
|
||||||
framework = arduino
|
|
||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
build_type = debug ; I'm debugging with ICE a lot now
|
build_type = debug ; I'm debugging with ICE a lot now
|
||||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||||
|
|||||||
2
proto
2
proto
Submodule proto updated: 72cbde93ff...ab281311c4
108
src/GPSStatus.h
Normal file
108
src/GPSStatus.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the GPS system.
|
||||||
|
class GPSStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||||
|
|
||||||
|
bool hasLock = false; // default to false, until we complete our first read
|
||||||
|
bool isConnected = false; // Do we have a GPS we are talking to
|
||||||
|
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||||
|
int32_t altitude = 0;
|
||||||
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
GPSStatus() {
|
||||||
|
statusType = STATUS_TYPE_GPS;
|
||||||
|
}
|
||||||
|
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop ) : Status()
|
||||||
|
{
|
||||||
|
this->hasLock = hasLock;
|
||||||
|
this->isConnected = isConnected;
|
||||||
|
this->latitude = latitude;
|
||||||
|
this->longitude = longitude;
|
||||||
|
this->altitude = altitude;
|
||||||
|
this->dop = dop;
|
||||||
|
}
|
||||||
|
GPSStatus(const GPSStatus &);
|
||||||
|
GPSStatus &operator=(const GPSStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const GPSStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasLock() const
|
||||||
|
{
|
||||||
|
return hasLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getIsConnected() const
|
||||||
|
{
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getLatitude() const
|
||||||
|
{
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getLongitude() const
|
||||||
|
{
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getAltitude() const
|
||||||
|
{
|
||||||
|
return altitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getDOP() const
|
||||||
|
{
|
||||||
|
return dop;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const GPSStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->hasLock != hasLock ||
|
||||||
|
newStatus->isConnected != isConnected ||
|
||||||
|
newStatus->latitude != latitude ||
|
||||||
|
newStatus->longitude != longitude ||
|
||||||
|
newStatus->altitude != altitude ||
|
||||||
|
newStatus->dop != dop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const GPSStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
hasLock = newStatus->hasLock;
|
||||||
|
isConnected = newStatus->isConnected;
|
||||||
|
latitude = newStatus->latitude;
|
||||||
|
longitude = newStatus->longitude;
|
||||||
|
altitude = newStatus->altitude;
|
||||||
|
dop = newStatus->dop;
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::GPSStatus *gpsStatus;
|
||||||
73
src/NodeStatus.h
Normal file
73
src/NodeStatus.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the NodeDB system.
|
||||||
|
class NodeStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver = CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||||
|
|
||||||
|
uint8_t numOnline = 0;
|
||||||
|
uint8_t numTotal = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
NodeStatus() {
|
||||||
|
statusType = STATUS_TYPE_NODE;
|
||||||
|
}
|
||||||
|
NodeStatus( uint8_t numOnline, uint8_t numTotal ) : Status()
|
||||||
|
{
|
||||||
|
this->numOnline = numOnline;
|
||||||
|
this->numTotal = numTotal;
|
||||||
|
}
|
||||||
|
NodeStatus(const NodeStatus &);
|
||||||
|
NodeStatus &operator=(const NodeStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const NodeStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getNumOnline() const
|
||||||
|
{
|
||||||
|
return numOnline;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getNumTotal() const
|
||||||
|
{
|
||||||
|
return numTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const NodeStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->getNumOnline() != numOnline ||
|
||||||
|
newStatus->getNumTotal() != numTotal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const NodeStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
numOnline = newStatus->getNumOnline();
|
||||||
|
numTotal = newStatus->getNumTotal();
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("Node status update: %d online, %d total\n", numOnline, numTotal);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::NodeStatus *nodeStatus;
|
||||||
180
src/Power.cpp
Normal file
180
src/Power.cpp
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#include "power.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "sleep.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
|
||||||
|
// FIXME. nasty hack cleanup how we load axp192
|
||||||
|
#undef AXP192_SLAVE_ADDRESS
|
||||||
|
#include "axp20x.h"
|
||||||
|
AXP20X_Class axp;
|
||||||
|
bool pmu_irq = false;
|
||||||
|
|
||||||
|
Power *power;
|
||||||
|
|
||||||
|
bool Power::setup()
|
||||||
|
{
|
||||||
|
|
||||||
|
axp192Init();
|
||||||
|
PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||||
|
setPeriod(1);
|
||||||
|
|
||||||
|
return axp192_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads power status to powerStatus singleton.
|
||||||
|
//
|
||||||
|
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||||
|
void Power::readPowerStatus()
|
||||||
|
{
|
||||||
|
bool hasBattery = axp.isBatteryConnect();
|
||||||
|
int batteryVoltageMv = 0;
|
||||||
|
uint8_t batteryChargePercent = 0;
|
||||||
|
if (hasBattery) {
|
||||||
|
batteryVoltageMv = axp.getBattVoltage();
|
||||||
|
// If the AXP192 returns a valid battery percentage, use it
|
||||||
|
if (axp.getBattPercentage() >= 0) {
|
||||||
|
batteryChargePercent = axp.getBattPercentage();
|
||||||
|
} else {
|
||||||
|
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||||
|
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
||||||
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
||||||
|
newStatus.notifyObservers(&powerStatus);
|
||||||
|
|
||||||
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||||
|
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
||||||
|
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||||
|
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Power::doTask()
|
||||||
|
{
|
||||||
|
readPowerStatus();
|
||||||
|
|
||||||
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||||
|
if(statusHandler && statusHandler->isInitialized())
|
||||||
|
setPeriod(1000 * 20);
|
||||||
|
}
|
||||||
|
#endif // TBEAM_V10
|
||||||
|
|
||||||
|
#ifdef AXP192_SLAVE_ADDRESS
|
||||||
|
/**
|
||||||
|
* Init the power manager chip
|
||||||
|
*
|
||||||
|
* axp192 power
|
||||||
|
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
||||||
|
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
||||||
|
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||||
|
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||||
|
*/
|
||||||
|
void Power::axp192Init()
|
||||||
|
{
|
||||||
|
if (axp192_found) {
|
||||||
|
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||||
|
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||||
|
|
||||||
|
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||||
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("----------------------------------------\n");
|
||||||
|
|
||||||
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||||
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
||||||
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||||
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||||
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||||
|
axp.setDCDC1Voltage(3300); // for the OLED power
|
||||||
|
|
||||||
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
|
|
||||||
|
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
// Not connected
|
||||||
|
//val = 0xfc;
|
||||||
|
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
||||||
|
|
||||||
|
//not used
|
||||||
|
//val = 0x46;
|
||||||
|
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
||||||
|
#endif
|
||||||
|
axp.debugCharging();
|
||||||
|
|
||||||
|
#ifdef PMU_IRQ
|
||||||
|
pinMode(PMU_IRQ, INPUT);
|
||||||
|
attachInterrupt(
|
||||||
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||||
|
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
||||||
|
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
||||||
|
1);
|
||||||
|
|
||||||
|
axp.clearIRQ();
|
||||||
|
#endif
|
||||||
|
readPowerStatus();
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 Begin FAIL\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Power::loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef PMU_IRQ
|
||||||
|
if (pmu_irq) {
|
||||||
|
pmu_irq = false;
|
||||||
|
axp.readIRQ();
|
||||||
|
|
||||||
|
DEBUG_MSG("pmu irq!\n");
|
||||||
|
|
||||||
|
if (axp.isChargingIRQ()) {
|
||||||
|
DEBUG_MSG("Battery start charging\n");
|
||||||
|
}
|
||||||
|
if (axp.isChargingDoneIRQ()) {
|
||||||
|
DEBUG_MSG("Battery fully charged\n");
|
||||||
|
}
|
||||||
|
if (axp.isVbusRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("USB unplugged\n");
|
||||||
|
}
|
||||||
|
if (axp.isVbusPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("USB plugged In\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("Battery inserted\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("Battery removed\n");
|
||||||
|
}
|
||||||
|
if (axp.isPEKShortPressIRQ()) {
|
||||||
|
DEBUG_MSG("PEK short button press\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
readPowerStatus();
|
||||||
|
axp.clearIRQ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // T_BEAM_V10
|
||||||
|
|
||||||
|
}
|
||||||
103
src/PowerStatus.h
Normal file
103
src/PowerStatus.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the GPS system.
|
||||||
|
class PowerStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver = CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||||
|
|
||||||
|
/// Whether we have a battery connected
|
||||||
|
bool hasBattery;
|
||||||
|
/// Battery voltage in mV, valid if haveBattery is true
|
||||||
|
int batteryVoltageMv;
|
||||||
|
/// Battery charge percentage, either read directly or estimated
|
||||||
|
uint8_t batteryChargePercent;
|
||||||
|
/// Whether USB is connected
|
||||||
|
bool hasUSB;
|
||||||
|
/// Whether we are charging the battery
|
||||||
|
bool isCharging;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
PowerStatus() {
|
||||||
|
statusType = STATUS_TYPE_POWER;
|
||||||
|
}
|
||||||
|
PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
|
||||||
|
{
|
||||||
|
this->hasBattery = hasBattery;
|
||||||
|
this->hasUSB = hasUSB;
|
||||||
|
this->isCharging = isCharging;
|
||||||
|
this->batteryVoltageMv = batteryVoltageMv;
|
||||||
|
this->batteryChargePercent = batteryChargePercent;
|
||||||
|
}
|
||||||
|
PowerStatus(const PowerStatus &);
|
||||||
|
PowerStatus &operator=(const PowerStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const PowerStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasBattery() const
|
||||||
|
{
|
||||||
|
return hasBattery;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasUSB() const
|
||||||
|
{
|
||||||
|
return hasUSB;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getIsCharging() const
|
||||||
|
{
|
||||||
|
return isCharging;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBatteryVoltageMv() const
|
||||||
|
{
|
||||||
|
return batteryVoltageMv;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getBatteryChargePercent() const
|
||||||
|
{
|
||||||
|
return batteryChargePercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const PowerStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->getHasBattery() != hasBattery ||
|
||||||
|
newStatus->getHasUSB() != hasUSB ||
|
||||||
|
newStatus->getBatteryVoltageMv() != batteryVoltageMv
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const PowerStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
hasBattery = newStatus->getHasBattery();
|
||||||
|
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||||
|
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||||
|
hasUSB = newStatus->getHasUSB();
|
||||||
|
isCharging = newStatus->getIsCharging();
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::PowerStatus *powerStatus;
|
||||||
72
src/Status.h
Normal file
72
src/Status.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Observer.h"
|
||||||
|
|
||||||
|
// Constants for the various status types, so we can tell subclass instances apart
|
||||||
|
#define STATUS_TYPE_BASE 0
|
||||||
|
#define STATUS_TYPE_POWER 1
|
||||||
|
#define STATUS_TYPE_GPS 2
|
||||||
|
#define STATUS_TYPE_NODE 3
|
||||||
|
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
// A base class for observable status
|
||||||
|
class Status
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
// Allows us to observe an Observable
|
||||||
|
CallbackObserver<Status, const Status *> statusObserver = CallbackObserver<Status, const Status *>(this, &Status::updateStatus);
|
||||||
|
bool initialized = false;
|
||||||
|
// Workaround for no typeid support
|
||||||
|
int statusType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Allows us to generate observable events
|
||||||
|
Observable<const Status *> onNewStatus;
|
||||||
|
|
||||||
|
// Enable polymorphism ?
|
||||||
|
virtual ~Status() = default;
|
||||||
|
|
||||||
|
Status() {
|
||||||
|
if (!statusType)
|
||||||
|
{
|
||||||
|
statusType = STATUS_TYPE_BASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent object copy/move
|
||||||
|
Status(const Status &) = delete;
|
||||||
|
Status &operator=(const Status &) = delete;
|
||||||
|
|
||||||
|
// Start observing a source of data
|
||||||
|
void observe(Observable<const Status *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines whether or not existing data matches the data in another Status instance
|
||||||
|
bool matches(const Status *otherStatus) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInitialized() const
|
||||||
|
{
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatusType() const
|
||||||
|
{
|
||||||
|
return statusType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the Observable we're observing generates a new notification
|
||||||
|
int updateStatus(const Status *newStatus)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
0
src/StatusHandler.h
Normal file
0
src/StatusHandler.h
Normal file
@@ -17,8 +17,15 @@ void Thread::callRun(void *_this)
|
|||||||
|
|
||||||
void WorkerThread::doRun()
|
void WorkerThread::doRun()
|
||||||
{
|
{
|
||||||
|
startWatchdog();
|
||||||
|
|
||||||
while (!wantExit) {
|
while (!wantExit) {
|
||||||
|
stopWatchdog();
|
||||||
block();
|
block();
|
||||||
|
startWatchdog();
|
||||||
|
|
||||||
|
// no need - startWatchdog is guaranteed to give us one full watchdog interval
|
||||||
|
// serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
#ifdef DEBUG_STACK
|
||||||
static uint32_t lastPrint = 0;
|
static uint32_t lastPrint = 0;
|
||||||
@@ -30,6 +37,8 @@ void WorkerThread::doRun()
|
|||||||
|
|
||||||
loop();
|
loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopWatchdog();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include "esp_task_wdt.h"
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef HAS_FREE_RTOS
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
@@ -26,6 +27,23 @@ class Thread
|
|||||||
*/
|
*/
|
||||||
virtual void doRun() = 0;
|
virtual void doRun() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
|
||||||
|
*
|
||||||
|
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
|
||||||
|
*/
|
||||||
|
void serviceWatchdog() { esp_task_wdt_reset(); }
|
||||||
|
void startWatchdog()
|
||||||
|
{
|
||||||
|
auto r = esp_task_wdt_add(taskHandle);
|
||||||
|
assert(r == ESP_OK);
|
||||||
|
}
|
||||||
|
void stopWatchdog()
|
||||||
|
{
|
||||||
|
auto r = esp_task_wdt_delete(taskHandle);
|
||||||
|
assert(r == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void callRun(void *_this);
|
static void callRun(void *_this);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -144,7 +144,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define I2C_SDA 21
|
#define I2C_SDA 21
|
||||||
#define I2C_SCL 22
|
#define I2C_SCL 22
|
||||||
|
|
||||||
#define BUTTON_PIN 38
|
#define BUTTON_PIN 38 // The middle button GPIO on the T-Beam
|
||||||
|
#define BUTTON_PIN_ALT 13 // Alternate GPIO for an external button if needed
|
||||||
|
|
||||||
#ifndef USE_JTAG
|
#ifndef USE_JTAG
|
||||||
#define RESET_GPIO 14
|
#define RESET_GPIO 14
|
||||||
@@ -213,6 +214,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#elif defined(TTGO_LORA_V1)
|
#elif defined(TTGO_LORA_V1)
|
||||||
// This string must exactly match the case used in release file names or the android updater won't work
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
#define HW_VENDOR "ttgo-lora32-v1"
|
#define HW_VENDOR "ttgo-lora32-v1"
|
||||||
|
#undef GPS_RX_PIN
|
||||||
|
#undef GPS_TX_PIN
|
||||||
|
#define GPS_RX_PIN 36
|
||||||
|
#define GPS_TX_PIN 37
|
||||||
|
|
||||||
#define I2C_SDA 4 // I2C pins for this board
|
#define I2C_SDA 4 // I2C pins for this board
|
||||||
#define I2C_SCL 15
|
#define I2C_SCL 15
|
||||||
@@ -231,6 +236,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// This string must exactly match the case used in release file names or the android updater won't work
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
#define HW_VENDOR "ttgo-lora32-v2"
|
#define HW_VENDOR "ttgo-lora32-v2"
|
||||||
|
|
||||||
|
#undef GPS_RX_PIN
|
||||||
|
#undef GPS_TX_PIN
|
||||||
|
#define GPS_RX_PIN 36
|
||||||
|
#define GPS_TX_PIN 13 // per @eugene
|
||||||
|
|
||||||
#define I2C_SDA 21 // I2C pins for this board
|
#define I2C_SDA 21 // I2C pins for this board
|
||||||
#define I2C_SCL 22
|
#define I2C_SCL 22
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class DataCharacteristic : public CallbackCharacteristic
|
|||||||
crc.update(data, len);
|
crc.update(data, len);
|
||||||
Update.write(data, len);
|
Update.write(data, len);
|
||||||
updateActualSize += len;
|
updateActualSize += len;
|
||||||
|
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,8 +124,10 @@ class CRC32Characteristic : public CallbackCharacteristic
|
|||||||
|
|
||||||
void bluetoothRebootCheck()
|
void bluetoothRebootCheck()
|
||||||
{
|
{
|
||||||
if (rebootAtMsec && millis() > rebootAtMsec)
|
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||||
|
DEBUG_MSG("Rebooting for update\n");
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ BLEService *createBatteryService(BLEServer *server)
|
|||||||
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
||||||
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||||
|
|
||||||
// I don't think we need to advertise this
|
// I don't think we need to advertise this? and some phones only see the first thing advertised anyways...
|
||||||
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||||
pBattery->start();
|
pBattery->start();
|
||||||
|
|
||||||
@@ -135,8 +135,8 @@ BLEService *createBatteryService(BLEServer *server)
|
|||||||
*/
|
*/
|
||||||
void updateBatteryLevel(uint8_t level)
|
void updateBatteryLevel(uint8_t level)
|
||||||
{
|
{
|
||||||
// Pretend to update battery levels - fixme do elsewhere
|
|
||||||
if (batteryLevelC) {
|
if (batteryLevelC) {
|
||||||
|
DEBUG_MSG("set BLE battery level %u\n", level);
|
||||||
batteryLevelC->setValue(&level, 1);
|
batteryLevelC->setValue(&level, 1);
|
||||||
batteryLevelC->notify();
|
batteryLevelC->notify();
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ class MySecurity : public BLESecurityCallbacks
|
|||||||
|
|
||||||
BLEServer *pServer;
|
BLEServer *pServer;
|
||||||
|
|
||||||
BLEService *pDevInfo, *pUpdate;
|
BLEService *pDevInfo, *pUpdate, *pBattery;
|
||||||
|
|
||||||
void deinitBLE()
|
void deinitBLE()
|
||||||
{
|
{
|
||||||
@@ -230,6 +230,9 @@ void deinitBLE()
|
|||||||
pUpdate->executeDelete();
|
pUpdate->executeDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pBattery->stop();
|
||||||
|
pBattery->executeDelete();
|
||||||
|
|
||||||
pDevInfo->stop();
|
pDevInfo->stop();
|
||||||
pDevInfo->executeDelete();
|
pDevInfo->executeDelete();
|
||||||
|
|
||||||
@@ -242,6 +245,7 @@ void deinitBLE()
|
|||||||
if (pUpdate != NULL)
|
if (pUpdate != NULL)
|
||||||
delete pUpdate;
|
delete pUpdate;
|
||||||
delete pDevInfo;
|
delete pDevInfo;
|
||||||
|
delete pBattery;
|
||||||
delete pServer;
|
delete pServer;
|
||||||
|
|
||||||
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
|
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
|
||||||
@@ -279,10 +283,9 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo
|
|||||||
|
|
||||||
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
|
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
|
||||||
|
|
||||||
// We now let users create the battery service only if they really want (not all devices have a battery)
|
pBattery = createBatteryService(pServer);
|
||||||
// BLEService *pBattery = createBatteryService(pServer);
|
|
||||||
|
|
||||||
// #define BLE_SOFTWARE_UPDATE
|
#define BLE_SOFTWARE_UPDATE
|
||||||
#ifdef BLE_SOFTWARE_UPDATE
|
#ifdef BLE_SOFTWARE_UPDATE
|
||||||
pUpdate = createUpdateService(pServer, hwVendor, swVersion,
|
pUpdate = createUpdateService(pServer, hwVendor, swVersion,
|
||||||
hwVersion); // We need to advertise this so our android ble scan operation can see it
|
hwVersion); // We need to advertise this so our android ble scan operation can see it
|
||||||
|
|||||||
@@ -35,5 +35,8 @@ BLECharacteristic *addBLECharacteristic(BLECharacteristic *c);
|
|||||||
/// Add a characteristic that we will delete when we restart
|
/// Add a characteristic that we will delete when we restart
|
||||||
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c);
|
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c);
|
||||||
|
|
||||||
|
/// Given a level between 0-100, update the BLE attribute
|
||||||
|
void updateBatteryLevel(uint8_t level);
|
||||||
|
|
||||||
/// Any bluetooth objects you allocate _must_ come from this pool if you want to be able to call deinitBLE()
|
/// Any bluetooth objects you allocate _must_ come from this pool if you want to be able to call deinitBLE()
|
||||||
extern SimpleAllocator btPool;
|
extern SimpleAllocator btPool;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "power.h"
|
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
bool bluetoothOn;
|
bool bluetoothOn;
|
||||||
|
|
||||||
@@ -59,102 +60,6 @@ void getMacAddr(uint8_t *dmac)
|
|||||||
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef TBEAM_V10
|
|
||||||
|
|
||||||
// FIXME. nasty hack cleanup how we load axp192
|
|
||||||
#undef AXP192_SLAVE_ADDRESS
|
|
||||||
#include "axp20x.h"
|
|
||||||
AXP20X_Class axp;
|
|
||||||
bool pmu_irq = false;
|
|
||||||
|
|
||||||
/// Reads power status to powerStatus singleton.
|
|
||||||
//
|
|
||||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
|
||||||
void readPowerStatus()
|
|
||||||
{
|
|
||||||
powerStatus.haveBattery = axp.isBatteryConnect();
|
|
||||||
if (powerStatus.haveBattery) {
|
|
||||||
powerStatus.batteryVoltageMv = axp.getBattVoltage();
|
|
||||||
}
|
|
||||||
powerStatus.usb = axp.isVBUSPlug();
|
|
||||||
powerStatus.charging = axp.isChargeing();
|
|
||||||
}
|
|
||||||
#endif // TBEAM_V10
|
|
||||||
|
|
||||||
#ifdef AXP192_SLAVE_ADDRESS
|
|
||||||
/**
|
|
||||||
* Init the power manager chip
|
|
||||||
*
|
|
||||||
* axp192 power
|
|
||||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
|
||||||
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
|
||||||
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
|
||||||
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
|
||||||
*/
|
|
||||||
void axp192Init()
|
|
||||||
{
|
|
||||||
if (axp192_found) {
|
|
||||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
|
||||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
|
||||||
|
|
||||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("----------------------------------------\n");
|
|
||||||
|
|
||||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
|
||||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
|
||||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
|
||||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
|
||||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
|
||||||
axp.setDCDC1Voltage(3300); // for the OLED power
|
|
||||||
|
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
||||||
|
|
||||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
// Not connected
|
|
||||||
//val = 0xfc;
|
|
||||||
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
|
||||||
|
|
||||||
//not used
|
|
||||||
//val = 0x46;
|
|
||||||
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
|
||||||
#endif
|
|
||||||
axp.debugCharging();
|
|
||||||
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
pinMode(PMU_IRQ, INPUT);
|
|
||||||
attachInterrupt(
|
|
||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
|
||||||
|
|
||||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
|
||||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
|
||||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
|
||||||
1);
|
|
||||||
|
|
||||||
axp.clearIRQ();
|
|
||||||
#endif
|
|
||||||
readPowerStatus();
|
|
||||||
} else {
|
|
||||||
DEBUG_MSG("AXP192 Begin FAIL\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG_MSG("AXP192 not found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
static void printBLEinfo() {
|
static void printBLEinfo() {
|
||||||
int dev_num = esp_ble_get_bond_device_num();
|
int dev_num = esp_ble_get_bond_device_num();
|
||||||
@@ -180,9 +85,15 @@ void esp32Setup()
|
|||||||
|
|
||||||
// enableModemSleep();
|
// enableModemSleep();
|
||||||
|
|
||||||
#ifdef AXP192_SLAVE_ADDRESS
|
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
||||||
axp192Init();
|
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
|
||||||
#endif
|
#define APP_WATCHDOG_SECS 45
|
||||||
|
|
||||||
|
auto res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||||
|
assert(res == ESP_OK);
|
||||||
|
|
||||||
|
res = esp_task_wdt_add(NULL);
|
||||||
|
assert(res == ESP_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -205,60 +116,12 @@ uint32_t axpDebugRead()
|
|||||||
Periodic axpDebugOutput(axpDebugRead);
|
Periodic axpDebugOutput(axpDebugRead);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* Per @spattinson
|
|
||||||
* MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other
|
|
||||||
* charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph
|
|
||||||
* in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html)
|
|
||||||
* looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks
|
|
||||||
* to be a shade above 3.2V
|
|
||||||
*/
|
|
||||||
#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
|
|
||||||
|
|
||||||
/// loop code specific to ESP32 targets
|
/// loop code specific to ESP32 targets
|
||||||
void esp32Loop()
|
void esp32Loop()
|
||||||
{
|
{
|
||||||
|
esp_task_wdt_reset(); // service our app level watchdog
|
||||||
loopBLE();
|
loopBLE();
|
||||||
|
|
||||||
// for debug printing
|
// for debug printing
|
||||||
// radio.radioIf.canSleep();
|
// radio.radioIf.canSleep();
|
||||||
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
if (pmu_irq) {
|
|
||||||
pmu_irq = false;
|
|
||||||
axp.readIRQ();
|
|
||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
|
||||||
|
|
||||||
if (axp.isChargingIRQ()) {
|
|
||||||
DEBUG_MSG("Battery start charging\n");
|
|
||||||
}
|
|
||||||
if (axp.isChargingDoneIRQ()) {
|
|
||||||
DEBUG_MSG("Battery fully charged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("USB unplugged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("USB plugged In\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("Battery inserted\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("Battery removed\n");
|
|
||||||
}
|
|
||||||
if (axp.isPEKShortPressIRQ()) {
|
|
||||||
DEBUG_MSG("PEK short button press\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
readPowerStatus();
|
|
||||||
axp.clearIRQ();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerStatus.haveBattery && !powerStatus.usb &&
|
|
||||||
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep
|
|
||||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
|
||||||
|
|
||||||
#endif // T_BEAM_V10
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@
|
|||||||
void perhapsSetRTC(const struct timeval *tv);
|
void perhapsSetRTC(const struct timeval *tv);
|
||||||
void perhapsSetRTC(struct tm &t);
|
void perhapsSetRTC(struct tm &t);
|
||||||
|
|
||||||
|
// Generate a string representation of DOP
|
||||||
|
const char *getDOPString(uint32_t dop);
|
||||||
|
|
||||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
||||||
uint32_t getTime();
|
uint32_t getTime();
|
||||||
|
|
||||||
@@ -31,10 +35,14 @@ class GPS : public Observable<void *>
|
|||||||
public:
|
public:
|
||||||
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||||
int32_t altitude = 0;
|
int32_t altitude = 0;
|
||||||
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
||||||
|
|
||||||
bool isConnected = false; // Do we have a GPS we are talking to
|
bool isConnected = false; // Do we have a GPS we are talking to
|
||||||
|
|
||||||
virtual ~GPS() {}
|
virtual ~GPS() {}
|
||||||
|
|
||||||
|
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we succeeded
|
* Returns true if we succeeded
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -53,13 +53,21 @@ void NEMAGPS::loop()
|
|||||||
latitude = toDegInt(loc.lat);
|
latitude = toDegInt(loc.lat);
|
||||||
longitude = toDegInt(loc.lng);
|
longitude = toDegInt(loc.lng);
|
||||||
}
|
}
|
||||||
|
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||||
|
if(reader.hdop.isValid()) {
|
||||||
|
dop = reader.hdop.value();
|
||||||
|
}
|
||||||
|
|
||||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
|
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
||||||
|
|
||||||
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||||
if (hasValidLocation)
|
if (hasValidLocation)
|
||||||
notifyObservers(NULL);
|
notifyObservers(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,12 +99,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||||
*/
|
*/
|
||||||
struct tm t;
|
struct tm t;
|
||||||
t.tm_sec = ublox.getSecond();
|
t.tm_sec = ublox.getSecond(0);
|
||||||
t.tm_min = ublox.getMinute();
|
t.tm_min = ublox.getMinute(0);
|
||||||
t.tm_hour = ublox.getHour();
|
t.tm_hour = ublox.getHour(0);
|
||||||
t.tm_mday = ublox.getDay();
|
t.tm_mday = ublox.getDay(0);
|
||||||
t.tm_mon = ublox.getMonth() - 1;
|
t.tm_mon = ublox.getMonth(0) - 1;
|
||||||
t.tm_year = ublox.getYear() - 1900;
|
t.tm_year = ublox.getYear(0) - 1900;
|
||||||
t.tm_isdst = false;
|
t.tm_isdst = false;
|
||||||
perhapsSetRTC(t);
|
perhapsSetRTC(t);
|
||||||
}
|
}
|
||||||
@@ -112,12 +112,14 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(0)) // rd fixes only
|
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(0)) // rd fixes only
|
||||||
{
|
{
|
||||||
// we only notify if position has changed
|
// we only notify if position has changed
|
||||||
latitude = ublox.getLatitude();
|
latitude = ublox.getLatitude(0);
|
||||||
longitude = ublox.getLongitude();
|
longitude = ublox.getLongitude(0);
|
||||||
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
|
altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters
|
||||||
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
|
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||||
|
|
||||||
hasValidLocation = (latitude != 0) && (longitude != 0); // bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||||
|
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||||
|
hasValidLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
|
||||||
if (hasValidLocation) {
|
if (hasValidLocation) {
|
||||||
wantNewLocation = false;
|
wantNewLocation = false;
|
||||||
notifyObservers(NULL);
|
notifyObservers(NULL);
|
||||||
@@ -126,6 +128,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||||
wantNewLocation = true;
|
wantNewLocation = true;
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
||||||
// the serial
|
// the serial
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0
|
|||||||
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
|
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
|
||||||
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
|
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
|
||||||
|
|
||||||
const
|
const uint8_t imgUSB[] PROGMEM = { 0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C };
|
||||||
|
const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22 };
|
||||||
|
const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C };
|
||||||
|
const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF };
|
||||||
|
const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF };
|
||||||
|
|
||||||
#include "icon.xbm"
|
#include "icon.xbm"
|
||||||
|
|
||||||
// We now programmatically draw our compass
|
// We now programmatically draw our compass
|
||||||
|
|||||||
105
src/main.cpp
105
src/main.cpp
@@ -37,11 +37,13 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
|
#include <OneButton.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
// #include <driver/rtc_io.h>
|
// #include <driver/rtc_io.h>
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
|
#include "WiFi.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "RF95Interface.h"
|
#include "RF95Interface.h"
|
||||||
@@ -54,8 +56,14 @@
|
|||||||
// We always create a screen object, but we only init it if we find the hardware
|
// We always create a screen object, but we only init it if we find the hardware
|
||||||
meshtastic::Screen screen(SSD1306_ADDRESS);
|
meshtastic::Screen screen(SSD1306_ADDRESS);
|
||||||
|
|
||||||
// Global power status singleton
|
// Global power status
|
||||||
meshtastic::PowerStatus powerStatus;
|
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
||||||
|
|
||||||
|
// Global GPS status
|
||||||
|
meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
|
||||||
|
|
||||||
|
// Global Node status
|
||||||
|
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
||||||
|
|
||||||
bool ssd1306_found;
|
bool ssd1306_found;
|
||||||
bool axp192_found;
|
bool axp192_found;
|
||||||
@@ -119,12 +127,49 @@ static uint32_t ledBlinker()
|
|||||||
setLed(ledOn);
|
setLed(ledOn);
|
||||||
|
|
||||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000);
|
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Periodic ledPeriodic(ledBlinker);
|
Periodic ledPeriodic(ledBlinker);
|
||||||
|
|
||||||
|
// Prepare for button presses
|
||||||
|
#ifdef BUTTON_PIN
|
||||||
|
OneButton userButton;
|
||||||
|
#endif
|
||||||
|
#ifdef BUTTON_PIN_ALT
|
||||||
|
OneButton userButtonAlt;
|
||||||
|
#endif
|
||||||
|
void userButtonPressed()
|
||||||
|
{
|
||||||
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
|
}
|
||||||
|
void userButtonPressedLong()
|
||||||
|
{
|
||||||
|
screen.adjustBrightness();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_ESP32
|
||||||
|
void initWifi()
|
||||||
|
{
|
||||||
|
strcpy(radioConfig.preferences.wifi_ssid, "geeksville");
|
||||||
|
strcpy(radioConfig.preferences.wifi_password, "xxx");
|
||||||
|
if (radioConfig.has_preferences) {
|
||||||
|
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||||
|
|
||||||
|
if (*wifiName) {
|
||||||
|
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||||
|
if (radioConfig.preferences.wifi_ap_mode) {
|
||||||
|
// DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw));
|
||||||
|
} else {
|
||||||
|
// WiFi.mode(WIFI_MODE_STA);
|
||||||
|
DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName);
|
||||||
|
// WiFi.begin(wifiName, wifiPsw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
DEBUG_MSG("Not using WIFI\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
@@ -161,8 +206,14 @@ void setup()
|
|||||||
|
|
||||||
// Buttons & LED
|
// Buttons & LED
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
userButton = OneButton(BUTTON_PIN, true, true);
|
||||||
digitalWrite(BUTTON_PIN, 1);
|
userButton.attachClick(userButtonPressed);
|
||||||
|
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||||
|
#endif
|
||||||
|
#ifdef BUTTON_PIN_ALT
|
||||||
|
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||||
|
userButtonAlt.attachClick(userButtonPressed);
|
||||||
|
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||||
#endif
|
#endif
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
@@ -182,6 +233,14 @@ void setup()
|
|||||||
esp32Setup();
|
esp32Setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
// Currently only the tbeam has a PMU
|
||||||
|
power = new Power();
|
||||||
|
power->setup();
|
||||||
|
power->setStatusHandler(powerStatus);
|
||||||
|
powerStatus->observe(&power->newStatus);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef NRF52_SERIES
|
#ifdef NRF52_SERIES
|
||||||
nrf52Setup();
|
nrf52Setup();
|
||||||
#endif
|
#endif
|
||||||
@@ -210,8 +269,14 @@ void setup()
|
|||||||
gps = new NEMAGPS();
|
gps = new NEMAGPS();
|
||||||
gps->setup();
|
gps->setup();
|
||||||
#endif
|
#endif
|
||||||
|
gpsStatus->observe(&gps->newStatus);
|
||||||
|
nodeStatus->observe(&nodeDB.newStatus);
|
||||||
|
|
||||||
service.init();
|
service.init();
|
||||||
|
#ifndef NO_ESP32
|
||||||
|
// Must be after we init the service, because the wifi settings are loaded by NodeDB (oops)
|
||||||
|
initWifi();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef SX1262_ANT_SW
|
#ifdef SX1262_ANT_SW
|
||||||
// make analog PA vs not PA switch on SX1262 eval board work properly
|
// make analog PA vs not PA switch on SX1262 eval board work properly
|
||||||
@@ -293,26 +358,15 @@ void loop()
|
|||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp32Loop();
|
esp32Loop();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
power->loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of
|
userButton.tick();
|
||||||
// this boilerplate)
|
#endif
|
||||||
static bool wasPressed = false;
|
#ifdef BUTTON_PIN_ALT
|
||||||
|
userButtonAlt.tick();
|
||||||
if (!digitalRead(BUTTON_PIN)) {
|
|
||||||
if (!wasPressed) { // just started a new press
|
|
||||||
DEBUG_MSG("pressing\n");
|
|
||||||
|
|
||||||
// doLightSleep();
|
|
||||||
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
|
||||||
wasPressed = true;
|
|
||||||
|
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
|
||||||
}
|
|
||||||
} else if (wasPressed) {
|
|
||||||
// we just did a release
|
|
||||||
wasPressed = false;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Show boot screen for first 3 seconds, then switch to normal operation.
|
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||||
@@ -331,11 +385,8 @@ void loop()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update the screen last, after we've figured out what to show.
|
// Update the screen last, after we've figured out what to show.
|
||||||
screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
|
|
||||||
screen.debug()->setChannelNameStatus(channelSettings.name);
|
screen.debug()->setChannelNameStatus(channelSettings.name);
|
||||||
screen.debug()->setPowerStatus(powerStatus);
|
// screen.debug()->setPowerStatus(powerStatus);
|
||||||
// TODO(#4): use something based on hdop to show GPS "signal" strength.
|
|
||||||
screen.debug()->setGPSStatus(gps->isConnected ? (gps->hasLock() ? "GPS ok" : "No Sats") : "No GPS");
|
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
||||||
// i.e. don't just keep spinning in loop as fast as we can.
|
// i.e. don't just keep spinning in loop as fast as we can.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
extern bool ssd1306_found;
|
||||||
@@ -9,6 +12,11 @@ extern bool isUSBPowered;
|
|||||||
|
|
||||||
// Global Screen singleton.
|
// Global Screen singleton.
|
||||||
extern meshtastic::Screen screen;
|
extern meshtastic::Screen screen;
|
||||||
|
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||||
|
|
||||||
|
//extern meshtastic::PowerStatus *powerStatus;
|
||||||
|
//extern meshtastic::GPSStatus *gpsStatus;
|
||||||
|
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||||
|
|
||||||
// Return a human readable string of the form "Meshtastic_ab13"
|
// Return a human readable string of the form "Meshtastic_ab13"
|
||||||
const char *getDeviceName();
|
const char *getDeviceName();
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "power.h"
|
||||||
|
#include "BluetoothUtil.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
|
||||||
|
|
||||||
/*
|
/*
|
||||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||||
@@ -280,6 +282,8 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
|||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int MeshService::onGPSChanged(void *unused)
|
int MeshService::onGPSChanged(void *unused)
|
||||||
{
|
{
|
||||||
// DEBUG_MSG("got gps notify\n");
|
// DEBUG_MSG("got gps notify\n");
|
||||||
@@ -298,6 +302,10 @@ int MeshService::onGPSChanged(void *unused)
|
|||||||
pos.time = getValidTime();
|
pos.time = getValidTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Include our current battery voltage in our position announcement
|
||||||
|
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||||
|
updateBatteryLevel(pos.battery_level);
|
||||||
|
|
||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
static uint32_t lastGpsSend;
|
static uint32_t lastGpsSend;
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
NodeDB nodeDB;
|
NodeDB nodeDB;
|
||||||
|
|
||||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||||
DeviceState devicestate;
|
EXT_RAM_ATTR DeviceState devicestate;
|
||||||
MyNodeInfo &myNodeInfo = devicestate.my_node;
|
MyNodeInfo &myNodeInfo = devicestate.my_node;
|
||||||
RadioConfig &radioConfig = devicestate.radio;
|
RadioConfig &radioConfig = devicestate.radio;
|
||||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||||
@@ -103,13 +103,16 @@ void NodeDB::resetRadioConfig()
|
|||||||
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
|
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
|
||||||
|
|
||||||
// temp hack for quicker testing
|
// temp hack for quicker testing
|
||||||
|
// devicestate.no_save = true;
|
||||||
|
if (devicestate.no_save) {
|
||||||
|
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
|
||||||
|
|
||||||
/*
|
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
|
||||||
radioConfig.preferences.screen_on_secs = 30;
|
radioConfig.preferences.screen_on_secs = 30;
|
||||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||||
radioConfig.preferences.ls_secs = 60;
|
radioConfig.preferences.ls_secs = 60;
|
||||||
*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::installDefaultDeviceState()
|
void NodeDB::installDefaultDeviceState()
|
||||||
@@ -257,32 +260,36 @@ void NodeDB::loadFromDisk()
|
|||||||
void NodeDB::saveToDisk()
|
void NodeDB::saveToDisk()
|
||||||
{
|
{
|
||||||
#ifdef FS
|
#ifdef FS
|
||||||
auto f = FS.open(preftmp, FILE_O_WRITE);
|
if (!devicestate.no_save) {
|
||||||
if (f) {
|
auto f = FS.open(preftmp, FILE_O_WRITE);
|
||||||
DEBUG_MSG("Writing preferences\n");
|
if (f) {
|
||||||
|
DEBUG_MSG("Writing preferences\n");
|
||||||
|
|
||||||
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
||||||
|
|
||||||
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||||
|
|
||||||
devicestate.version = DEVICESTATE_CUR_VER;
|
devicestate.version = DEVICESTATE_CUR_VER;
|
||||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
||||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||||
// FIXME - report failure to phone
|
// FIXME - report failure to phone
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
} else {
|
||||||
|
// Success - replace the old file
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
// brief window of risk here ;-)
|
||||||
|
if (!FS.remove(preffile))
|
||||||
|
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
||||||
|
if (!FS.rename(preftmp, preffile))
|
||||||
|
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Success - replace the old file
|
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
||||||
f.close();
|
|
||||||
|
|
||||||
// brief window of risk here ;-)
|
|
||||||
if (!FS.remove(preffile))
|
|
||||||
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
|
||||||
if (!FS.rename(preftmp, preffile))
|
|
||||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
DEBUG_MSG("ERROR filesystem not implemented\n");
|
DEBUG_MSG("ERROR filesystem not implemented\n");
|
||||||
@@ -335,8 +342,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
int oldNumNodes = *numNodes;
|
int oldNumNodes = *numNodes;
|
||||||
NodeInfo *info = getOrCreateNode(mp.from);
|
NodeInfo *info = getOrCreateNode(mp.from);
|
||||||
|
|
||||||
if (oldNumNodes != *numNodes)
|
notifyObservers();
|
||||||
updateGUI = true; // we just created a nodeinfo
|
|
||||||
|
|
||||||
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
||||||
info->has_position = true; // at least the time is valid
|
info->has_position = true; // at least the time is valid
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include "Observer.h"
|
||||||
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
|
||||||
extern DeviceState devicestate;
|
extern DeviceState devicestate;
|
||||||
extern MyNodeInfo &myNodeInfo;
|
extern MyNodeInfo &myNodeInfo;
|
||||||
@@ -32,6 +34,7 @@ class NodeDB
|
|||||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||||
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
||||||
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
|
|
||||||
/// don't do mesh based algoritm for node id assignment (initially)
|
/// don't do mesh based algoritm for node id assignment (initially)
|
||||||
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
||||||
@@ -91,6 +94,13 @@ class NodeDB
|
|||||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
NodeInfo *getOrCreateNode(NodeNum n);
|
NodeInfo *getOrCreateNode(NodeNum n);
|
||||||
|
|
||||||
|
/// Notify observers of changes to the DB
|
||||||
|
void notifyObservers() {
|
||||||
|
// Notify observers of the current node state
|
||||||
|
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes());
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
}
|
||||||
|
|
||||||
/// read our db from flash
|
/// read our db from flash
|
||||||
void loadFromDisk();
|
void loadFromDisk();
|
||||||
|
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ typedef struct _RadioConfig_UserPreferences {
|
|||||||
uint32_t sds_secs;
|
uint32_t sds_secs;
|
||||||
uint32_t ls_secs;
|
uint32_t ls_secs;
|
||||||
uint32_t min_wake_secs;
|
uint32_t min_wake_secs;
|
||||||
bool keep_all_packets;
|
char wifi_ssid[33];
|
||||||
bool promiscuous_mode;
|
char wifi_password[64];
|
||||||
|
bool wifi_ap_mode;
|
||||||
pb_size_t ignore_incoming_count;
|
pb_size_t ignore_incoming_count;
|
||||||
uint32_t ignore_incoming[3];
|
uint32_t ignore_incoming[3];
|
||||||
} RadioConfig_UserPreferences;
|
} RadioConfig_UserPreferences;
|
||||||
@@ -187,6 +188,7 @@ typedef struct _DeviceState {
|
|||||||
bool has_rx_text_message;
|
bool has_rx_text_message;
|
||||||
MeshPacket rx_text_message;
|
MeshPacket rx_text_message;
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
|
bool no_save;
|
||||||
} DeviceState;
|
} DeviceState;
|
||||||
|
|
||||||
typedef struct _FromRadio {
|
typedef struct _FromRadio {
|
||||||
@@ -241,10 +243,10 @@ typedef struct _ToRadio {
|
|||||||
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
||||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
||||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
||||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}}
|
||||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0}
|
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0}
|
||||||
#define DebugString_init_default {""}
|
#define DebugString_init_default {""}
|
||||||
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
||||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||||
@@ -257,10 +259,10 @@ typedef struct _ToRadio {
|
|||||||
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
||||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""}
|
||||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
||||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}}
|
||||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
|
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0}
|
||||||
#define DebugString_init_zero {""}
|
#define DebugString_init_zero {""}
|
||||||
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
||||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||||
@@ -308,8 +310,9 @@ typedef struct _ToRadio {
|
|||||||
#define RadioConfig_UserPreferences_sds_secs_tag 9
|
#define RadioConfig_UserPreferences_sds_secs_tag 9
|
||||||
#define RadioConfig_UserPreferences_ls_secs_tag 10
|
#define RadioConfig_UserPreferences_ls_secs_tag 10
|
||||||
#define RadioConfig_UserPreferences_min_wake_secs_tag 11
|
#define RadioConfig_UserPreferences_min_wake_secs_tag 11
|
||||||
#define RadioConfig_UserPreferences_keep_all_packets_tag 100
|
#define RadioConfig_UserPreferences_wifi_ssid_tag 12
|
||||||
#define RadioConfig_UserPreferences_promiscuous_mode_tag 101
|
#define RadioConfig_UserPreferences_wifi_password_tag 13
|
||||||
|
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
|
||||||
#define RadioConfig_UserPreferences_ignore_incoming_tag 102
|
#define RadioConfig_UserPreferences_ignore_incoming_tag 102
|
||||||
#define RouteDiscovery_route_tag 2
|
#define RouteDiscovery_route_tag 2
|
||||||
#define User_id_tag 1
|
#define User_id_tag 1
|
||||||
@@ -351,6 +354,7 @@ typedef struct _ToRadio {
|
|||||||
#define DeviceState_receive_queue_tag 5
|
#define DeviceState_receive_queue_tag 5
|
||||||
#define DeviceState_version_tag 8
|
#define DeviceState_version_tag 8
|
||||||
#define DeviceState_rx_text_message_tag 7
|
#define DeviceState_rx_text_message_tag 7
|
||||||
|
#define DeviceState_no_save_tag 9
|
||||||
#define FromRadio_packet_tag 2
|
#define FromRadio_packet_tag 2
|
||||||
#define FromRadio_my_info_tag 3
|
#define FromRadio_my_info_tag 3
|
||||||
#define FromRadio_node_info_tag 4
|
#define FromRadio_node_info_tag 4
|
||||||
@@ -456,8 +460,9 @@ X(a, STATIC, SINGULAR, UINT32, mesh_sds_timeout_secs, 8) \
|
|||||||
X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \
|
X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \
|
X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 11) \
|
X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 11) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, keep_all_packets, 100) \
|
X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) \
|
X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
|
||||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 102)
|
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 102)
|
||||||
#define RadioConfig_UserPreferences_CALLBACK NULL
|
#define RadioConfig_UserPreferences_CALLBACK NULL
|
||||||
#define RadioConfig_UserPreferences_DEFAULT NULL
|
#define RadioConfig_UserPreferences_DEFAULT NULL
|
||||||
@@ -498,7 +503,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
|
|||||||
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
||||||
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
|
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, version, 8)
|
X(a, STATIC, SINGULAR, UINT32, version, 8) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, no_save, 9)
|
||||||
#define DeviceState_CALLBACK NULL
|
#define DeviceState_CALLBACK NULL
|
||||||
#define DeviceState_DEFAULT NULL
|
#define DeviceState_DEFAULT NULL
|
||||||
#define DeviceState_radio_MSGTYPE RadioConfig
|
#define DeviceState_radio_MSGTYPE RadioConfig
|
||||||
@@ -592,11 +598,11 @@ extern const pb_msgdesc_t ManufacturingData_msg;
|
|||||||
#define SubPacket_size 274
|
#define SubPacket_size 274
|
||||||
#define MeshPacket_size 313
|
#define MeshPacket_size 313
|
||||||
#define ChannelSettings_size 60
|
#define ChannelSettings_size 60
|
||||||
#define RadioConfig_size 157
|
#define RadioConfig_size 253
|
||||||
#define RadioConfig_UserPreferences_size 93
|
#define RadioConfig_UserPreferences_size 188
|
||||||
#define NodeInfo_size 132
|
#define NodeInfo_size 132
|
||||||
#define MyNodeInfo_size 110
|
#define MyNodeInfo_size 110
|
||||||
#define DeviceState_size 5305
|
#define DeviceState_size 5403
|
||||||
#define DebugString_size 258
|
#define DebugString_size 258
|
||||||
#define FromRadio_size 322
|
#define FromRadio_size 322
|
||||||
#define ToRadio_size 316
|
#define ToRadio_size 316
|
||||||
|
|||||||
48
src/power.h
48
src/power.h
@@ -1,20 +1,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "PeriodicTask.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
|
||||||
namespace meshtastic
|
/**
|
||||||
|
* Per @spattinson
|
||||||
|
* MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other
|
||||||
|
* charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph
|
||||||
|
* in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html)
|
||||||
|
* looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks
|
||||||
|
* to be a shade above 3.2V
|
||||||
|
*/
|
||||||
|
#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
|
||||||
|
|
||||||
|
#define BAT_MILLIVOLTS_FULL 4100
|
||||||
|
#define BAT_MILLIVOLTS_EMPTY 3500
|
||||||
|
|
||||||
|
class Power : public PeriodicTask
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Describes the state of the power system.
|
public:
|
||||||
struct PowerStatus {
|
|
||||||
/// Whether we have a battery connected
|
Observable<const meshtastic::PowerStatus *> newStatus;
|
||||||
bool haveBattery;
|
|
||||||
/// Battery voltage in mV, valid if haveBattery is true
|
void readPowerStatus();
|
||||||
int batteryVoltageMv;
|
void loop();
|
||||||
/// Whether USB is connected
|
virtual bool setup();
|
||||||
bool usb;
|
virtual void doTask();
|
||||||
/// Whether we are charging the battery
|
void setStatusHandler(meshtastic::PowerStatus *handler)
|
||||||
bool charging;
|
{
|
||||||
|
statusHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
meshtastic::PowerStatus *statusHandler;
|
||||||
|
virtual void axp192Init();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace meshtastic
|
extern Power *power;
|
||||||
|
|
||||||
extern meshtastic::PowerStatus powerStatus;
|
|
||||||
311
src/screen.cpp
311
src/screen.cpp
@@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
||||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
||||||
@@ -41,7 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#endif
|
#endif
|
||||||
#define SCREEN_HEIGHT 64
|
#define SCREEN_HEIGHT 64
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
#define TRANSITION_FRAMERATE 30 // fps
|
||||||
#define IDLE_FRAMERATE 10 // in fps
|
#define IDLE_FRAMERATE 1 // in fps
|
||||||
#define COMPASS_DIAM 44
|
#define COMPASS_DIAM 44
|
||||||
|
|
||||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||||
@@ -54,6 +55,14 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
|||||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||||
static char btPIN[16] = "888888";
|
static char btPIN[16] = "888888";
|
||||||
|
|
||||||
|
uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
|
||||||
|
|
||||||
|
// if defined a pixel will blink to show redraws
|
||||||
|
// #define SHOW_REDRAWS
|
||||||
|
#ifdef SHOW_REDRAWS
|
||||||
|
static bool heartbeat = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// draw an xbm image.
|
// draw an xbm image.
|
||||||
@@ -64,20 +73,34 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||||
|
display->setFont(ArialMT_Plain_10);
|
||||||
|
const char *region = xstr(HW_VERSION);
|
||||||
|
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
||||||
|
region += 4;
|
||||||
|
char buf[16];
|
||||||
|
snprintf(buf, sizeof(buf), "%s",
|
||||||
|
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||||
|
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->drawString(64 + x, 2 + y, "Bluetooth");
|
display->drawString(64 + x, y, "Bluetooth");
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT + y, "Enter this code");
|
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->setFont(ArialMT_Plain_24);
|
display->setFont(ArialMT_Plain_24);
|
||||||
display->drawString(64 + x, 22 + y, btPIN);
|
display->drawString(64 + x, 26 + y, btPIN);
|
||||||
|
|
||||||
|
display->setFont(ArialMT_Plain_10);
|
||||||
|
char buf[30];
|
||||||
|
const char *name = "Name: ";
|
||||||
|
strcpy(buf, name);
|
||||||
|
strcat(buf, getDeviceName());
|
||||||
|
display->drawString(64 + x, 48 + y, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the last text message we received
|
/// Draw the last text message we received
|
||||||
@@ -126,34 +149,105 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
#if 0
|
||||||
/// @return the max y we ended up printing to
|
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
||||||
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
/// @return the max y we ended up printing to
|
||||||
{
|
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
// The coordinates define the left starting point of the text
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
// The coordinates define the left starting point of the text
|
||||||
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
const char **f = fields;
|
const char **f = fields;
|
||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
const int COLUMNS = 2; // hardwired for two columns per row....
|
const int COLUMNS = 2; // hardwired for two columns per row....
|
||||||
int col = 0; // track which column we are on
|
int col = 0; // track which column we are on
|
||||||
while (*f) {
|
while (*f) {
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
xo += SCREEN_WIDTH / COLUMNS;
|
xo += SCREEN_WIDTH / COLUMNS;
|
||||||
// Wrap to next row, if needed.
|
// Wrap to next row, if needed.
|
||||||
if (++col >= COLUMNS) {
|
if (++col >= COLUMNS) {
|
||||||
xo = x;
|
xo = x;
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT;
|
||||||
col = 0;
|
col = 0;
|
||||||
|
}
|
||||||
|
f++;
|
||||||
|
}
|
||||||
|
if (col != 0) {
|
||||||
|
// Include last incomplete line in our total.
|
||||||
|
yo += FONT_HEIGHT;
|
||||||
}
|
}
|
||||||
f++;
|
|
||||||
}
|
|
||||||
if (col != 0) {
|
|
||||||
// Include last incomplete line in our total.
|
|
||||||
yo += FONT_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return yo;
|
return yo;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
|
||||||
|
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
|
||||||
|
{
|
||||||
|
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
|
||||||
|
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
|
||||||
|
// Clear the bar area on the battery image
|
||||||
|
for (int i = 1; i < 14; i++) {
|
||||||
|
imgBuffer[i] = 0x81;
|
||||||
|
}
|
||||||
|
// If charging, draw a charging indicator
|
||||||
|
if (powerStatus->getIsCharging()) {
|
||||||
|
memcpy(imgBuffer + 3, lightning, 8);
|
||||||
|
// If not charging, Draw power bars
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (powerStatus->getBatteryChargePercent() >= 25 * i)
|
||||||
|
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display->drawFastImage(x, y, 16, 8, imgBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw nodes status
|
||||||
|
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *nodeStatus)
|
||||||
|
{
|
||||||
|
char usersString[20];
|
||||||
|
sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
|
||||||
|
display->drawFastImage(x, y, 8, 8, imgUser);
|
||||||
|
display->drawString(x + 10, y - 2, usersString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw GPS status summary
|
||||||
|
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||||
|
{
|
||||||
|
if (!gps->getIsConnected()) {
|
||||||
|
display->drawString(x, y - 2, "No GPS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
|
||||||
|
if (!gps->getHasLock()) {
|
||||||
|
display->drawString(x + 8, y - 2, "No sats");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 100) {
|
||||||
|
display->drawString(x + 8, y - 2, "Ideal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 200) {
|
||||||
|
display->drawString(x + 8, y - 2, "Exc.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 500) {
|
||||||
|
display->drawString(x + 8, y - 2, "Good");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 1000) {
|
||||||
|
display->drawString(x + 8, y - 2, "Mod.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 2000) {
|
||||||
|
display->drawString(x + 8, y - 2, "Fair");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() > 0) {
|
||||||
|
display->drawString(x + 8, y - 2, "Poor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ported from my old java code, returns distance in meters along the globe
|
/// Ported from my old java code, returns distance in meters along the globe
|
||||||
@@ -295,6 +389,30 @@ static bool hasPosition(NodeInfo *n)
|
|||||||
static size_t nodeIndex;
|
static size_t nodeIndex;
|
||||||
static int8_t prevFrame = -1;
|
static int8_t prevFrame = -1;
|
||||||
|
|
||||||
|
// Draw the compass and arrow pointing to location
|
||||||
|
static void drawCompass(OLEDDisplay *display, int16_t compassX, int16_t compassY, float headingRadian)
|
||||||
|
{
|
||||||
|
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
||||||
|
// (const uint8_t *)compass_bits);
|
||||||
|
|
||||||
|
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||||
|
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||||
|
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
||||||
|
|
||||||
|
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
points[i]->rotate(headingRadian);
|
||||||
|
points[i]->scale(COMPASS_DIAM * 0.6);
|
||||||
|
points[i]->translate(compassX, compassY);
|
||||||
|
}
|
||||||
|
drawLine(display, tip, tail);
|
||||||
|
drawLine(display, leftArrow, tip);
|
||||||
|
drawLine(display, rightArrow, tip);
|
||||||
|
|
||||||
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert an integer GPS coords to a floating point
|
/// Convert an integer GPS coords to a floating point
|
||||||
#define DegD(i) (i * 1e-7)
|
#define DegD(i) (i * 1e-7)
|
||||||
|
|
||||||
@@ -328,7 +446,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
|
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
|
||||||
|
|
||||||
static char signalStr[20];
|
static char signalStr[20];
|
||||||
snprintf(signalStr, sizeof(signalStr), "Signal: %.0f", node->snr);
|
snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
|
||||||
|
|
||||||
uint32_t agoSecs = sinceLastSeen(node);
|
uint32_t agoSecs = sinceLastSeen(node);
|
||||||
static char lastStr[20];
|
static char lastStr[20];
|
||||||
@@ -339,15 +457,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
else
|
else
|
||||||
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static float simRadian;
|
|
||||||
simRadian += 0.1; // For testing, have the compass spin unless both
|
|
||||||
// locations are valid
|
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
*distStr = 0; // might not have location data
|
strcpy(distStr, "? km"); // might not have location data
|
||||||
float headingRadian = simRadian;
|
float headingRadian;
|
||||||
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
||||||
if (ourNode && hasPosition(ourNode) && hasPosition(node)) {
|
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||||
|
|
||||||
|
// coordinates for the center of the compass/circle
|
||||||
|
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||||
|
|
||||||
|
if (ourNode && hasPosition(ourNode) && hasPosition(node)) { // display direction toward node
|
||||||
Position &op = ourNode->position, &p = node->position;
|
Position &op = ourNode->position, &p = node->position;
|
||||||
float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||||
if (d < 2000)
|
if (d < 2000)
|
||||||
@@ -360,35 +479,17 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||||
float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i));
|
float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i));
|
||||||
headingRadian = bearingToOther - myHeading;
|
headingRadian = bearingToOther - myHeading;
|
||||||
} else {
|
drawCompass(display, compassX, compassY, headingRadian);
|
||||||
|
} else { // direction to node is unknown so display question mark
|
||||||
// Debug info for gps lock errors
|
// Debug info for gps lock errors
|
||||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||||
|
|
||||||
|
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
||||||
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
// Must be after distStr is populated
|
||||||
drawColumns(display, x, y, fields);
|
drawColumns(display, x, y, fields);
|
||||||
|
|
||||||
// coordinates for the center of the compass
|
|
||||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
|
||||||
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
|
||||||
// (const uint8_t *)compass_bits);
|
|
||||||
|
|
||||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
|
||||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
|
||||||
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
|
||||||
|
|
||||||
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
points[i]->rotate(headingRadian);
|
|
||||||
points[i]->scale(COMPASS_DIAM * 0.6);
|
|
||||||
points[i]->translate(compassX, compassY);
|
|
||||||
}
|
|
||||||
drawLine(display, tip, tail);
|
|
||||||
drawLine(display, leftArrow, tip);
|
|
||||||
drawLine(display, rightArrow, tip);
|
|
||||||
|
|
||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -456,6 +557,9 @@ void Screen::setup()
|
|||||||
// Store a pointer to Screen so we can get to it from static functions.
|
// Store a pointer to Screen so we can get to it from static functions.
|
||||||
ui.getUiState()->userData = this;
|
ui.getUiState()->userData = this;
|
||||||
|
|
||||||
|
// Set the utf8 conversion function
|
||||||
|
dispdev.setFontTableLookupFunction(customFontTableLookup);
|
||||||
|
|
||||||
// Add frames.
|
// Add frames.
|
||||||
static FrameCallback bootFrames[] = {drawBootScreen};
|
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||||
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||||
@@ -480,6 +584,11 @@ void Screen::setup()
|
|||||||
// twice initially.
|
// twice initially.
|
||||||
ui.update();
|
ui.update();
|
||||||
ui.update();
|
ui.update();
|
||||||
|
|
||||||
|
// Subscribe to status updates
|
||||||
|
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||||
|
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||||
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::doTask()
|
||||||
@@ -541,14 +650,7 @@ void Screen::doTask()
|
|||||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
// standard screen switching is stopped.
|
// standard screen switching is stopped.
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// TODO(girts): decouple nodeDB from screen.
|
// standard screen loop handling here
|
||||||
// standard screen loop handling ehre
|
|
||||||
// If the # nodes changes, we need to regen our list of screens
|
|
||||||
if (nodeDB.updateGUI || nodeDB.updateTextMessage) {
|
|
||||||
setFrames();
|
|
||||||
nodeDB.updateGUI = false;
|
|
||||||
nodeDB.updateTextMessage = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.update();
|
ui.update();
|
||||||
@@ -573,8 +675,8 @@ void Screen::setFrames()
|
|||||||
DEBUG_MSG("showing standard frames\n");
|
DEBUG_MSG("showing standard frames\n");
|
||||||
showingNormalScreen = true;
|
showingNormalScreen = true;
|
||||||
|
|
||||||
size_t numnodes = nodeDB.getNumNodes();
|
|
||||||
// We don't show the node info our our node (if we have it yet - we should)
|
// We don't show the node info our our node (if we have it yet - we should)
|
||||||
|
size_t numnodes = nodeStatus->getNumTotal();
|
||||||
if (numnodes > 0)
|
if (numnodes > 0)
|
||||||
numnodes--;
|
numnodes--;
|
||||||
|
|
||||||
@@ -648,35 +750,58 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
char usersStr[20];
|
|
||||||
char channelStr[20];
|
char channelStr[20];
|
||||||
char batStr[20];
|
|
||||||
char gpsStr[20];
|
|
||||||
{
|
{
|
||||||
LockGuard guard(&lock);
|
LockGuard guard(&lock);
|
||||||
snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodesOnline, nodesTotal);
|
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
|
||||||
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
|
|
||||||
if (powerStatus.haveBattery) {
|
|
||||||
// TODO: draw a battery icon instead of letter "B".
|
|
||||||
int batV = powerStatus.batteryVoltageMv / 1000;
|
|
||||||
int batCv = (powerStatus.batteryVoltageMv % 1000) / 10;
|
|
||||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ',
|
|
||||||
powerStatus.usb ? 'U' : ' ');
|
|
||||||
} else {
|
|
||||||
snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gpsStatus.empty()) {
|
// Display power status
|
||||||
snprintf(gpsStr, sizeof(gpsStr), "%s", gpsStatus.c_str());
|
if (powerStatus->getHasBattery())
|
||||||
} else {
|
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||||||
gpsStr[0] = '\0'; // Just show empty string.
|
else
|
||||||
}
|
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||||
|
// Display nodes status
|
||||||
|
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||||
|
// Display GPS status
|
||||||
|
drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gpsStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fields[] = {batStr, gpsStr, usersStr, channelStr, nullptr};
|
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
||||||
uint32_t yo = drawRows(display, x, y, fields);
|
|
||||||
|
|
||||||
display->drawLogBuffer(x, yo);
|
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
||||||
|
|
||||||
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
|
#ifdef SHOW_REDRAWS
|
||||||
|
if (heartbeat)
|
||||||
|
display->setPixel(0, 0);
|
||||||
|
heartbeat = !heartbeat;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
|
||||||
|
void Screen::adjustBrightness()
|
||||||
|
{
|
||||||
|
if (brightness == 254) {
|
||||||
|
brightness = 0;
|
||||||
|
} else {
|
||||||
|
brightness++;
|
||||||
|
}
|
||||||
|
int width = brightness / (254.00 / SCREEN_WIDTH);
|
||||||
|
dispdev.drawRect(0, 30, SCREEN_WIDTH, 4);
|
||||||
|
dispdev.fillRect(0, 31, width, 2);
|
||||||
|
dispdev.display();
|
||||||
|
dispdev.setBrightness(brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Screen::handleStatusUpdate(const Status *arg)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||||
|
switch (arg->getStatusType()) {
|
||||||
|
case STATUS_TYPE_NODE:
|
||||||
|
setFrames();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setPeriod(1); // Update the screen right away
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
} // namespace meshtastic
|
} // namespace meshtastic
|
||||||
|
|||||||
80
src/screen.h
80
src/screen.h
@@ -13,7 +13,10 @@
|
|||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "TypedQueue.h"
|
#include "TypedQueue.h"
|
||||||
#include "lock.h"
|
#include "lock.h"
|
||||||
#include "power.h"
|
#include "PowerStatus.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
#include "Observer.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace meshtastic
|
namespace meshtastic
|
||||||
@@ -29,14 +32,6 @@ class DebugInfo
|
|||||||
DebugInfo(const DebugInfo &) = delete;
|
DebugInfo(const DebugInfo &) = delete;
|
||||||
DebugInfo &operator=(const DebugInfo &) = delete;
|
DebugInfo &operator=(const DebugInfo &) = delete;
|
||||||
|
|
||||||
/// Sets user statistics.
|
|
||||||
void setNodeNumbersStatus(int online, int total)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
nodesOnline = online;
|
|
||||||
nodesTotal = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the name of the channel.
|
/// Sets the name of the channel.
|
||||||
void setChannelNameStatus(const char *name)
|
void setChannelNameStatus(const char *name)
|
||||||
{
|
{
|
||||||
@@ -44,25 +39,6 @@ class DebugInfo
|
|||||||
channelName = name;
|
channelName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets battery/charging/etc status.
|
|
||||||
//
|
|
||||||
void setPowerStatus(const PowerStatus &status)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
powerStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets GPS status.
|
|
||||||
//
|
|
||||||
// If this function never gets called, we assume GPS does not exist on this
|
|
||||||
// device.
|
|
||||||
// TODO(girts): figure out what the format should be.
|
|
||||||
void setGPSStatus(const char *status)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
gpsStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend Screen;
|
friend Screen;
|
||||||
|
|
||||||
@@ -71,15 +47,8 @@ class DebugInfo
|
|||||||
/// Renders the debug screen.
|
/// Renders the debug screen.
|
||||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
int nodesOnline = 0;
|
|
||||||
int nodesTotal = 0;
|
|
||||||
|
|
||||||
PowerStatus powerStatus;
|
|
||||||
|
|
||||||
std::string channelName;
|
std::string channelName;
|
||||||
|
|
||||||
std::string gpsStatus;
|
|
||||||
|
|
||||||
/// Protects all of internal state.
|
/// Protects all of internal state.
|
||||||
Lock lock;
|
Lock lock;
|
||||||
};
|
};
|
||||||
@@ -93,6 +62,10 @@ class DebugInfo
|
|||||||
// simultaneously).
|
// simultaneously).
|
||||||
class Screen : public PeriodicTask
|
class Screen : public PeriodicTask
|
||||||
{
|
{
|
||||||
|
CallbackObserver<Screen, const Status *> powerStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
CallbackObserver<Screen, const Status *> gpsStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
CallbackObserver<Screen, const Status *> nodeStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||||
|
|
||||||
@@ -117,6 +90,10 @@ class Screen : public PeriodicTask
|
|||||||
/// Handles a button press.
|
/// Handles a button press.
|
||||||
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
|
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
|
||||||
|
|
||||||
|
// Implementation to Adjust Brightness
|
||||||
|
void adjustBrightness();
|
||||||
|
int brightness = 150;
|
||||||
|
|
||||||
/// Starts showing the Bluetooth PIN screen.
|
/// Starts showing the Bluetooth PIN screen.
|
||||||
//
|
//
|
||||||
// Switches over to a static frame showing the Bluetooth pairing screen
|
// Switches over to a static frame showing the Bluetooth pairing screen
|
||||||
@@ -149,11 +126,44 @@ class Screen : public PeriodicTask
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overrides the default utf8 character conversion, to replace empty space with question marks
|
||||||
|
static char customFontTableLookup(const uint8_t ch) {
|
||||||
|
// UTF-8 to font table index converter
|
||||||
|
// Code form http://playground.arduino.cc/Main/Utf8ascii
|
||||||
|
static uint8_t LASTCHAR;
|
||||||
|
static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
|
||||||
|
|
||||||
|
if (ch < 128) { // Standard ASCII-set 0..0x7F handling
|
||||||
|
LASTCHAR = 0;
|
||||||
|
SKIPREST = false;
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t last = LASTCHAR; // get last char
|
||||||
|
LASTCHAR = ch;
|
||||||
|
|
||||||
|
switch (last) { // conversion depnding on first UTF8-character
|
||||||
|
case 0xC2: { SKIPREST = false; return (uint8_t) ch; }
|
||||||
|
case 0xC3: { SKIPREST = false; return (uint8_t) (ch | 0xC0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want to strip out prefix chars for two-byte char formats
|
||||||
|
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82) return (uint8_t) 0;
|
||||||
|
|
||||||
|
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the rest of it
|
||||||
|
if (SKIPREST) return (uint8_t) 0;
|
||||||
|
SKIPREST = true;
|
||||||
|
|
||||||
|
return (uint8_t) 191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't stick to standard EASCII codes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a handle to the DebugInfo screen.
|
/// Returns a handle to the DebugInfo screen.
|
||||||
//
|
//
|
||||||
// Use this handle to set things like battery status, user count, GPS status, etc.
|
// Use this handle to set things like battery status, user count, GPS status, etc.
|
||||||
DebugInfo *debug() { return &debugInfo; }
|
DebugInfo *debug() { return &debugInfo; }
|
||||||
|
|
||||||
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Updates the UI.
|
/// Updates the UI.
|
||||||
//
|
//
|
||||||
|
|||||||
7
src/utils.h
Normal file
7
src/utils.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// C++ v17+ clamp function, limits a given value to a range defined by lo and hi
|
||||||
|
template<class T>
|
||||||
|
constexpr const T& clamp( const T& v, const T& lo, const T& hi ) {
|
||||||
|
return (v < lo) ? lo : (hi < v) ? hi : v;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user