Compare commits

..

93 Commits

Author SHA1 Message Date
Kevin Hester
d248c6be4b Merge pull request #290 from geeksville/master
get ready for release 0.8.1
2020-07-18 13:24:11 -07:00
geeksville
39b0a89821 0.8.1 2020-07-18 13:19:35 -07:00
geeksville
d9f43d3e2f update protobufs related to https://github.com/meshtastic/Meshtastic-device/issues/269 2020-07-18 09:12:51 -07:00
geeksville
08c77caaa9 fix #266 ble forced to re-pair details below
The NVS copies of hte BLE pairing info for clients were getting corrupted
occasionally.  So I went googling and found some plausible bug reports
but nothing that was an exact match.  Then I looked at the arduino-esp32
binaries for the ESP-IDF framework.  They were fairly old (Jan 20).

Looking through the commits on ESP-IDF release3.3 it seems like there have
been a few fixes for mutual exclusion errors wrt bluetooth.  So I punted
and tried updating ESP-IDF to latest and everything seems fairly solid
now.  Currently running a long test run with three nodes.
2020-07-18 08:54:52 -07:00
geeksville
cfedc97cd0 Show NVS flash utilization at boot (for debugging ble things?) 2020-07-18 08:49:42 -07:00
geeksville
cfad226b2b use new ttgo-lora build names 2020-07-17 14:11:27 -07:00
Kevin Hester
4ee35a0612 Merge pull request #280 from aHVzY2g/master
fix #272 add support for tlora-v2-1-1.6
2020-07-16 11:12:40 -07:00
Marlon Spangenberg
44749470a4 fix #272 add support for tlora-v2-1-1.6 2020-07-14 16:16:49 +02:00
Kevin Hester
8fe714d8b1 Merge pull request #278 from geeksville/master
Fix URL
2020-07-13 17:58:42 -07:00
Kevin Hester
22137ff1bd Merge branch 'master' into master 2020-07-13 17:56:08 -07:00
geeksville
da3b6d1958 Fix URL 2020-07-13 17:55:30 -07:00
Kevin Hester
637960edde Merge pull request #277 from geeksville/master
fix doc typo
2020-07-13 17:51:46 -07:00
geeksville
d9209ffaea fix doc typo 2020-07-13 17:47:22 -07:00
Kevin Hester
9fb94796c8 Merge pull request #276 from rezl/master
Added Beginner's Guide to README.md
2020-07-13 17:46:02 -07:00
Rezl
f060f7faad Merge pull request #1 from rezl/rezl-beginners-guide
Added Beginner's Guide
2020-07-13 19:35:09 -05:00
Rezl
55673fcd66 Added Beginner's Guide 2020-07-13 19:34:14 -05:00
Kevin Hester
51267379ab Merge pull request #273 from geeksville/dev
0.7.11
2020-07-12 15:28:16 -07:00
geeksville
e2cf2ba4f2 recommend tbeam 1.0 over 0.7 2020-07-12 14:55:50 -07:00
geeksville
4550cce639 0.7.11 2020-07-12 14:42:21 -07:00
Kevin Hester
7c0d13f00a Merge pull request #268 from geeksville/dev
Dev
2020-07-11 17:16:48 -07:00
geeksville
f78f3232e2 update todo 2020-07-11 17:08:36 -07:00
geeksville
7802d00031 add nrf52832 support 2020-07-10 10:03:08 -07:00
geeksville
40a15248e8 @slavino fixed tbeam in #243, so add it back to the builds 2020-07-09 22:43:04 -07:00
geeksville
3a62453b8b todo updates for 1.0 2020-07-07 17:40:59 -07:00
Kevin Hester
c3f7829255 Merge pull request #262 from grcasanova/graphics
Graphics
2020-07-07 15:08:24 -07:00
grcasanova
37d9fb2dad just a cleanup of the graphics 2020-07-07 10:46:49 +02:00
Kevin Hester
4388e72dec Merge pull request #259 from grcasanova/concurrency
Threading refactored
2020-07-06 14:15:18 -07:00
grcasanova
9803141fe7 merged with master 2020-07-06 21:53:10 +02:00
Kevin Hester
1f0e9cc1c3 Merge pull request #258 from Professr/issue#257
Added sinceLastSeen check to pings generated by node UI
2020-07-06 09:20:41 -07:00
grcasanova
92b30ebec6 fixes now compiles 2020-07-06 10:45:55 +02:00
Professr
ccadb6a43d Added sinceLastSeen check to pings generated by node UI 2020-07-05 19:56:57 -07:00
Ellie Hussey
6f7f540c79 Added the option for forced NodeStatus updates on user change or text message, tweaked compass (#256) 2020-07-05 17:03:12 -07:00
grcasanova
d5b8038457 fixes 2020-07-06 00:54:30 +02:00
grcasanova
0a6059ba13 refactored threading-related classes, code broken 2020-07-05 23:11:40 +02:00
Kevin Hester
aba5b01fa0 Merge pull request #255 from geeksville/dev
fix #254 - a RadioLib (and arduino-esp32) needed to have IRAM attr on for disable interrupt
2020-07-05 12:13:08 -07:00
geeksville
09f4943869 Merge remote-tracking branch 'root/master' into dev 2020-07-05 12:10:25 -07:00
Kevin Hester
29c8543f87 Merge pull request #248 from Professr/issue#199
Issue#199 update - add satellite info, change DOP display, add compass rose
2020-07-05 12:10:03 -07:00
geeksville
7bd4940ed8 fix #254 2020-07-05 12:04:15 -07:00
Kevin Hester
d5116935b5 Merge branch 'master' into issue#199 2020-07-04 12:13:21 -07:00
Kevin Hester
0d320fe29b Merge pull request #251 from mrvdb/sh1106-support
Screen width correction for sh1106 display controller
2020-07-04 12:13:04 -07:00
Marcel van der Boom
4159461a62 Merge remote-tracking branch 'upstream/master' into sh1106-support 2020-07-04 10:45:13 +02:00
Ellie Hussey
f4bd39e3fa Merge pull request #246 from slavino/patch-4
Update platformio.ini
2020-07-03 03:27:41 -07:00
Ellie Hussey
fbc36a2cfd Merge branch 'master' into patch-4 2020-07-03 03:25:09 -07:00
Ellie Hussey
e93ba73adb Merge pull request #245 from slavino/patch-3
Update configuration.h
2020-07-03 03:24:56 -07:00
Ellie Hussey
03301f093d Merge branch 'master' into patch-3 2020-07-03 03:22:45 -07:00
Ellie Hussey
55a5fa6fb5 Merge pull request #247 from slavino/patch-5
Update README.md
2020-07-03 03:22:04 -07:00
Professr
4d04d10135 Merge screen.cpp 2020-07-03 02:58:55 -07:00
Professr
cda423acab Changed GPS DOP display to bars, added satellites display and compass rose 2020-07-03 02:53:56 -07:00
Slavomir Hustaty
0f92678c3b Update README.md
TBeam 0.7 + W.W. LoRa freqs list link
2020-07-03 10:35:42 +02:00
Slavomir Hustaty
8d122f36e3 Update platformio.ini
https://github.com/meshtastic/Meshtastic-device/issues/243#issuecomment-653361142
2020-07-03 07:44:14 +02:00
Slavomir Hustaty
439cdfbb32 Update configuration.h
https://github.com/meshtastic/Meshtastic-device/issues/243#issuecomment-653361142
2020-07-03 07:41:22 +02:00
Kevin Hester
0a6ab31e10 Merge pull request #244 from slavino/patch-2
Update configuration.h to fix TBEAM v07 GPS to work
2020-07-02 10:03:07 -07:00
Marcel van der Boom
0b6486256d Merge remote-tracking branch 'upstream/master' into sh1106-support 2020-07-02 17:36:31 +02:00
Slavomir Hustaty
da12b93f82 Update configuration.h
https://user-images.githubusercontent.com/1584034/86362734-08525e00-bc76-11ea-8a34-8579d1fa2965.jpg

related to issue https://github.com/meshtastic/Meshtastic-device/issues/243
2020-07-02 16:54:24 +02:00
Kevin Hester
6dec6af5dc Merge pull request #240 from tobymurray/master
Add Canada to list of countries that use 915 MHz
2020-07-01 11:59:04 -07:00
Kevin Hester
11444621ae Merge branch 'master' into master 2020-07-01 10:26:31 -07:00
Kevin Hester
98f1b3296c Merge pull request #241 from geeksville/dev
Dev
2020-07-01 10:26:13 -07:00
geeksville
26c43e7091 minor docs 2020-07-01 10:22:17 -07:00
Toby Murray
9b9447858a Add Canada to list of countries that use 915 MHz
Meshtastic prompted me to get a couple boards to try, and I had to figure out what frequency. Canada uses the same US902-928 as the US, add it to the list for simplicity.

Not sure where to find an "official" reference, but there's a reference here: https://www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html
2020-07-01 13:12:00 -04:00
geeksville
3151cfb064 0.7.10 2020-07-01 10:09:32 -07:00
geeksville
c327fee986 Fix formatting 2020-07-01 10:09:06 -07:00
geeksville
a4f53270e8 fix heltec build (and fix formatting) 2020-07-01 10:08:38 -07:00
geeksville
a7456a1126 all targets are arduino - for now 2020-07-01 10:08:09 -07:00
geeksville
8381512ce4 todo updates 2020-07-01 09:32:01 -07:00
Marcel van der Boom
57d968cdcd Merge remote-tracking branch 'upstream/master' into sh1106-support 2020-06-29 09:26:25 +02:00
Kevin Hester
20a669029b Merge pull request #237 from Professr/issue#182
Switch main display to event-driven model, abstract the three main status info categories, work on power.h
2020-06-28 19:10:42 -07:00
Professr
f2e6c6de58 Fixed filename case sensitivity 2020-06-28 19:03:39 -07:00
Professr
8fa44c3590 Disabled the display heartbeat pixel for pull request 2020-06-28 18:55:51 -07:00
Professr
f5b7c33d4e Refactored status handlers and merged 2020-06-28 18:17:52 -07:00
Kevin Hester
be8e663d39 Merge pull request #236 from geeksville/dev
turn on thread watchdog
2020-06-28 11:24:03 -07:00
geeksville
0d4a9748e3 Merge remote-tracking branch 'root/master' into dev 2020-06-28 11:13:05 -07:00
geeksville
bd477f0fb2 turn on thread watchdog 2020-06-28 11:12:12 -07:00
Professr
5317895a5e Merged 2020-06-27 21:26:57 -07:00
Professr
542b8b26ce Abstracted statuses, made display event-driven 2020-06-27 21:19:49 -07:00
Marcel van der Boom
aaca854620 Merge remote-tracking branch 'upstream/master' into sh1106-support 2020-06-27 10:18:55 +02:00
Kevin Hester
64da384fc1 Merge pull request #234 from geeksville/dev
Dev
2020-06-26 16:16:54 -07:00
geeksville
a595fc4642 Fix #233 - init distance string before drawing it
(and violating my own "no formatting checkins with other changes" rule
to restore proper indentation for this file)
2020-06-26 15:04:22 -07:00
geeksville
ac135be8cd move geeksville's personal todos somewhere else (mostly android) 2020-06-25 15:49:49 -07:00
Marcel van der Boom
ac2d3e2ae0 Correct type of setBrightness parameter 2020-06-25 21:16:35 +02:00
Marcel van der Boom
33946af39f SCREEN_WIDTH is visible area already, not addressable area
- sh1106 starts showing from column 2 (the library handles the offsets) so we don't actually need
the different screen width here.
2020-06-25 21:15:12 +02:00
Kevin Hester
5ea59a1c4d Merge pull request #230 from aHVzY2g/pr/228
fix pr #228 int float casting & made fillRect 2px smaller
2020-06-25 11:41:40 -07:00
Kevin Hester
8bafd87b76 Merge branch 'master' into pr/228 2020-06-25 11:26:37 -07:00
Kevin Hester
6b40e9a5e0 Merge pull request #232 from geeksville/dev
Add optional external GPS for TTGO Lora V2
2020-06-25 11:25:46 -07:00
Kevin Hester
a2f6fd9298 Merge branch 'master' into dev 2020-06-25 10:07:06 -07:00
Marlon Spangenberg
bc604fc9ba Merge branch 'master' into pr/228 2020-06-25 18:56:20 +02:00
geeksville
9baaa13897 Add optional external GPS for TTGO Lora V2
per this request: https://meshtastic.discourse.group/t/gps-pin-assignments-for-custom-boards/501/9?u=geeksville
2020-06-25 09:45:21 -07:00
Kevin Hester
65e53be8b0 Merge pull request #231 from geeksville/dev
Add an initial pull-request template
2020-06-25 09:37:40 -07:00
geeksville
7f5283e95d Add an initial pull-request template 2020-06-25 09:33:29 -07:00
Marlon Spangenberg
ae4ab48ddc fixed int float casting & made fillRect 2px smaller 2020-06-25 18:26:32 +02:00
Kevin Hester
afccf1da02 Merge pull request #229 from meshtastic/geeksville-patch-1
Update issue templates
2020-06-25 09:21:38 -07:00
Kevin Hester
fc07c7c01f Update issue templates 2020-06-25 09:18:54 -07:00
Kevin Hester
aeb906414f Merge pull request #228 from mrvdb/sh1106-support
Take configured SCREEN_WIDTH into account for brightness bar
2020-06-25 08:44:34 -07:00
Marcel van der Boom
a6c6b45576 Take different screen widths into account for brightness bar
width of sh1106 display is 132 for example
2020-06-25 16:20:49 +02:00
83 changed files with 1694 additions and 898 deletions

View 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
View 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.

11
.gitignore vendored
View File

@@ -7,4 +7,13 @@ main/credentials.h
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/extensions.json
*.code-workspace
*.code-workspace
.DS_Store
Thumbs.db
.autotools
.built
.context
.cproject
.idea/*
.vagrant

View File

@@ -61,5 +61,6 @@
"ocrypto",
"protobufs",
"wifi"
]
],
"C_Cpp.dimInactiveRegions": true
}

17
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "PlatformIO",
"task": "Build",
"problemMatcher": [
"$platformio"
],
"group": {
"kind": "build",
"isDefault": true
},
"label": "PlatformIO: Build"
}
]
}

View File

@@ -22,10 +22,10 @@ This software is 100% open source and developed by a group of hobbyist experimen
We currently support three models of radios.
- TTGO T-Beam
- [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)
- TTGO T-Beam (usually the recommended choice)
- [T-Beam V1.1 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-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
- [T-Beam V0.7 w/ NEO-6M](https://www.aliexpress.com/item/4000574335430.html) (will work but **you must use the tbeam0.7 firmware ** - but the T-Beam V1.0 or later are better!)
- 3D printable cases
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
@@ -40,9 +40,10 @@ We currently support three models of radios.
**Make sure to get the frequency for your country**
- US/JP/AU/NZ - 915MHz
- US/JP/AU/NZ/CA - 915MHz
- CN - 470MHz
- EU - 868MHz, 433MHz
- full list of LoRa frequencies per region is available [here](https://www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html)
Getting a version that includes a screen is optional, but highly recommended.

View File

@@ -8,7 +8,7 @@ COUNTRIES="US EU433 EU865 CN JP"
#COUNTRIES=US
#COUNTRIES=CN
BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec"
BOARDS="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
#BOARDS=tbeam
OUTDIR=release/latest

3
bin/nrf52840-gdbserver.sh Executable file
View File

@@ -0,0 +1,3 @@
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52832_XXAA

View File

@@ -1,3 +1,3 @@
export VERSION=0.7.9
export VERSION=0.8.1

View File

@@ -35,6 +35,10 @@ This project is currently in beta testing but it is fairly stable and feature co
This software is 100% open source and developed by a group of hobbyist experimenters. No warranty is provided, if you'd like to improve it - we'd love your help. Please post in the [forum](https://meshtastic.discourse.group/).
### Beginner's Guide
For an detailed walk-through aimed at beginners, we recommend [meshtastic.letstalkthis.com](https://meshtastic.letstalkthis.com/).
# Updates
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.

View File

@@ -2,19 +2,6 @@
You probably don't care about this section - skip to the next one.
- do device release (after testing BLE fixes) https://github.com/meshtastic/Meshtastic-device/pull/218#event-3475395948
- some channel names don't work - check for illegal symbols per forum report
- auto invalidate old BLE handles
- disable software update button after update finishes
- mention translations in main readme
- check BLE handle stability across sleep - stress test sleep/wake - btu_init_core calls gatt_init - which assigns handles global
- @feh123 Sony Xperia Z1 C6903 running Android 5.1.1
- first message sent is still doubled for some people
- Android frontend should refetch the android messages from backend service on Resume
- let users set arbitrary params in android
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
- NRF52 BLE support
# Medium priority
Items to complete before 1.0.
@@ -58,7 +45,6 @@ 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.
- read the PMU battery fault indicators and blink/led/warn user on screen
- discard very old nodedb records (> 1wk)
- add a watchdog timer
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
- 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)

View File

@@ -32,8 +32,8 @@ 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
- 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 Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
- 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
- low priority: think more careful about reliable retransmit intervals
- make ReliableRouter.pending threadsafe

View File

@@ -1,5 +1,36 @@
# NRF52 TODO
## RAK815
### Bootloader
Installing the adafruit bootloader is optional - I think the stock bootloader will work okay for most.
```
kevinh@kevin-server:~/development/meshtastic/Adafruit_nRF52_Bootloader$ make BOARD=rak815 flash
LD rak815_bootloader-0.3.2-111-g9478eb7-dirty.out
text data bss dec hex filename
20888 1124 15006 37018 909a _build/build-rak815/rak815_bootloader-0.3.2-111-g9478eb7-dirty.out
Create rak815_bootloader-0.3.2-111-g9478eb7-dirty.hex
Create rak815_bootloader-0.3.2-111-g9478eb7-dirty-nosd.hex
Flashing: rak815_bootloader-0.3.2-111-g9478eb7-dirty-nosd.hex
nrfjprog --program _build/build-rak815/rak815_bootloader-0.3.2-111-g9478eb7-dirty-nosd.hex --sectoranduicrerase -f nrf52 --reset
Parsing hex file.
Erasing page at address 0x0.
Erasing page at address 0x74000.
Erasing page at address 0x75000.
Erasing page at address 0x76000.
Erasing page at address 0x77000.
Erasing page at address 0x78000.
Erasing page at address 0x79000.
Erasing UICR flash area.
Applying system reset.
Checking that the area to write is not protected.
Programming device.
Applying system reset.
Run.
```
## Misc work items
RAM investigation.

View File

@@ -23,6 +23,8 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR
[env]
framework = arduino
; customize the partition table
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
board_build.partitions = partition-table.csv
@@ -72,13 +74,12 @@ lib_deps =
Wire ; explicitly needed here because the AXP202 library forgets to add it
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
https://github.com/meshtastic/RadioLib.git
https://github.com/meshtastic/RadioLib.git#6aa38a85856012c99c4e9b4e7cee35e37671a4bc
https://github.com/meshtastic/TinyGPSPlus.git
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
platform = espressif32
framework = arduino
src_filter =
${env.src_filter} -<nrf52/>
upload_speed = 921600
@@ -89,7 +90,7 @@ build_flags =
# board_build.ldscript = linker/esp32.extram.bss.ld
lib_ignore = segger_rtt
platform_packages =
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#f26c4f96fefd13ed0ed042e27954f8aba6328f6b
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#7a78d82f1b5cf64715a14d2f096b8dd775948ac1
; The 1.0 release of the TBEAM board
[env:tbeam]
@@ -98,42 +99,46 @@ board = ttgo-t-beam
lib_deps =
${env.lib_deps}
https://github.com/meshtastic/AXP202X_Library.git
build_flags =
${esp32_base.build_flags} -D TBEAM_V10
; The original TBEAM board without the AXP power chip and a few other changes
; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug.
;[env:tbeam0.7]
;extends = esp32_base
;board = ttgo-t-beam
;build_flags =
; ${esp32_base.build_flags} -D TBEAM_V07
[env:tbeam0.7]
extends = esp32_base
board = ttgo-t-beam
build_flags =
${esp32_base.build_flags} -D TBEAM_V07
[env:heltec]
;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base
board = heltec_wifi_lora_32_V2
[env:ttgo-lora32-v1]
[env:tlora-v1]
extends = esp32_base
board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TTGO_LORA_V1
${esp32_base.build_flags} -D TLORA_V1
; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works
[env:ttgo-lora32-v2]
[env:tlora-v2]
extends = esp32_base
board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TTGO_LORA_V2
${esp32_base.build_flags} -D TLORA_V2
[env:tlora-v2-1-1.6]
extends = esp32_base
board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_16
; The Heltec Cubecell plus
; IMPORTANT NOTE: This target doesn't yet work and probably won't ever work. I'm keeping it around for now.
; For more details see my post in the forum.
[env:cubecellplus]
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
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
@@ -143,7 +148,6 @@ src_filter =
; Common settings for NRF52 based targets
[nrf52_base]
platform = nordicnrf52
framework = arduino
debug_tool = jlink
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)

2
proto

Submodule proto updated: ab281311c4...0523977d1f

126
src/GPSStatus.h Normal file
View File

@@ -0,0 +1,126 @@
#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)
uint32_t heading = 0;
uint32_t numSatellites = 0;
public:
GPSStatus() {
statusType = STATUS_TYPE_GPS;
}
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop, uint32_t heading, uint32_t numSatellites ) : Status()
{
this->hasLock = hasLock;
this->isConnected = isConnected;
this->latitude = latitude;
this->longitude = longitude;
this->altitude = altitude;
this->dop = dop;
this->heading = heading;
this->numSatellites = numSatellites;
}
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;
}
uint32_t getHeading() const
{
return heading;
}
uint32_t getNumSatellites() const
{
return numSatellites;
}
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 ||
newStatus->heading != heading ||
newStatus->numSatellites != numSatellites
);
}
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;
heading = newStatus->heading;
numSatellites = newStatus->numSatellites;
}
if(isDirty) {
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5, numSatellites);
onNewStatus.notifyObservers(this);
}
return 0;
}
};
}
extern meshtastic::GPSStatus *gpsStatus;

83
src/NodeStatus.h Normal file
View File

@@ -0,0 +1,83 @@
#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;
uint8_t lastNumTotal = 0;
public:
bool forceUpdate = false;
NodeStatus() {
statusType = STATUS_TYPE_NODE;
}
NodeStatus( uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false ) : Status()
{
this->forceUpdate = forceUpdate;
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;
}
uint8_t getLastNumTotal() const
{
return lastNumTotal;
}
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
lastNumTotal = numTotal;
bool isDirty;
{
isDirty = matches(newStatus);
initialized = true;
numOnline = newStatus->getNumOnline();
numTotal = newStatus->getNumTotal();
}
if(isDirty || newStatus->forceUpdate) {
DEBUG_MSG("Node status update: %d online, %d total\n", numOnline, numTotal);
onNewStatus.notifyObservers(this);
}
return 0;
}
};
}
extern meshtastic::NodeStatus *nodeStatus;

View File

@@ -1,43 +0,0 @@
#include "PeriodicTask.h"
#include "Periodic.h"
PeriodicScheduler periodicScheduler;
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
void PeriodicTask::setup()
{
periodicScheduler.schedule(this);
}
/// call this from loop
void PeriodicScheduler::loop()
{
meshtastic::LockGuard lg(&lock);
uint32_t now = millis();
for (auto t : tasks) {
if (t->period && (now - t->lastMsec) >= t->period) {
t->doTask();
t->lastMsec = now;
}
}
}
void PeriodicScheduler::schedule(PeriodicTask *t)
{
meshtastic::LockGuard lg(&lock);
tasks.insert(t);
}
void PeriodicScheduler::unschedule(PeriodicTask *t)
{
meshtastic::LockGuard lg(&lock);
tasks.erase(t);
}
void Periodic::doTask()
{
uint32_t p = callback();
setPeriod(p);
}

View File

@@ -1,85 +0,0 @@
#pragma once
#include "lock.h"
#include <Arduino.h>
#include <cstdint>
#include <unordered_set>
class PeriodicTask;
/**
* Runs all PeriodicTasks in the system.
*
* Currently called from main loop() but eventually should be its own thread blocked on a freertos timer.
*/
class PeriodicScheduler
{
friend class PeriodicTask;
/**
* This really should be some form of heap, and when the period gets changed on a task it should get
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
* _every_ tasks. If it was a heap we'd only have to check the first task.
*/
std::unordered_set<PeriodicTask *> tasks;
// Protects the above variables.
meshtastic::Lock lock;
public:
/// Run any next tasks which are due for execution
void loop();
private:
void schedule(PeriodicTask *t);
void unschedule(PeriodicTask *t);
};
extern PeriodicScheduler periodicScheduler;
/**
* A base class for tasks that want their doTask() method invoked periodically
*
* FIXME: currently just syntatic sugar for polling in loop (you must call .loop), but eventually
* generalize with the freertos scheduler so we can save lots of power by having everything either in
* something like this or triggered off of an irq.
*/
class PeriodicTask
{
friend class PeriodicScheduler;
uint32_t lastMsec = 0;
uint32_t period = 1; // call soon after creation
public:
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
/**
* Constructor (will schedule with the global PeriodicScheduler)
*/
PeriodicTask(uint32_t initialPeriod = 1);
/** MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
*/
void setup();
/**
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
* While zero this task is disabled and will not run
*/
void setPeriod(uint32_t p)
{
lastMsec = millis(); // reset starting from now
period = p;
}
uint32_t getPeriod() const { return period; }
/**
* Syntatic sugar for suspending tasks
*/
void disable() { setPeriod(0); }
protected:
virtual void doTask() = 0;
};

179
src/Power.cpp Normal file
View File

@@ -0,0 +1,179 @@
#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();
concurrency::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
}

View File

@@ -5,9 +5,10 @@
#include "NodeDB.h"
#include "configuration.h"
#include "main.h"
#include "screen.h"
#include "graphics/Screen.h"
#include "sleep.h"
#include "target_specific.h"
#include "timing.h"
static void sdsEnter()
{
@@ -15,7 +16,7 @@ static void sdsEnter()
// Don't deepsleep if we have USB power or if the user as pressed a button recently
// !isUSBPowered <- doesn't work yet because the axp192 isn't letting the battery fully charge when we are awake - FIXME
if (millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
if (timing::millis() - lastPressMs > radioConfig.preferences.mesh_sds_timeout_secs)
{
doDeepSleep(radioConfig.preferences.sds_secs);
}
@@ -130,7 +131,7 @@ static void onEnter()
static uint32_t lastPingMs;
uint32_t now = millis();
uint32_t now = timing::millis();
if (now - lastPingMs > 30 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
if (displayedNodeNum)

103
src/PowerStatus.h Normal file
View 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
View 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
View File

View File

@@ -1,49 +0,0 @@
#include "WorkerThread.h"
#include "debug.h"
#include <assert.h>
#ifdef configUSE_PREEMPTION
void Thread::start(const char *name, size_t stackSize, uint32_t priority)
{
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
assert(r == pdPASS);
}
void Thread::callRun(void *_this)
{
((Thread *)_this)->doRun();
}
void WorkerThread::doRun()
{
while (!wantExit) {
block();
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 10 * 1000L) {
lastPrint = millis();
meshtastic::printThreadInfo("net");
}
#endif
loop();
}
}
/**
* Notify this thread so it can run
*/
void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
{
xTaskNotify(taskHandle, v, action);
}
void NotifiedWorkerThread::block()
{
xTaskNotifyWait(0, // don't clear notification on entry
clearOnRead, &notification, portMAX_DELAY); // Wait forever
}
#endif

View File

@@ -1,96 +0,0 @@
#include <Arduino.h>
#include "freertosinc.h"
#ifdef HAS_FREE_RTOS
class Thread
{
protected:
TaskHandle_t taskHandle = NULL;
/**
* set this to true to ask thread to cleanly exit asap
*/
volatile bool wantExit = false;
public:
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
virtual ~Thread() { vTaskDelete(taskHandle); }
uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
virtual void doRun() = 0;
private:
static void callRun(void *_this);
};
/**
* This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting onlyschool arduino loop() code.
*
* Use as a mixin base class for the classes you want to convert.
*
* https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
*/
class WorkerThread : public Thread
{
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block() = 0;
virtual void loop() = 0;
/**
* The method that will be called when start is called.
*/
virtual void doRun();
};
/**
* A worker thread that waits on a freertos notification
*/
class NotifiedWorkerThread : public WorkerThread
{
public:
/**
* Notify this thread so it can run
*/
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
{
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
}
protected:
/**
* The notification that was most recently used to wake the thread. Read from loop()
*/
uint32_t notification = 0;
/**
* What notification bits should be cleared just after we read and return them in notification?
*
* Defaults to clear all of them.
*/
uint32_t clearOnRead = UINT32_MAX;
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block();
};
#endif

15
src/commands.h Normal file
View File

@@ -0,0 +1,15 @@
/**
* @brief This class enables on the fly software and hardware setup.
* It will contain all command messages to change internal settings.
*/
enum class Cmd {
INVALID,
SET_ON,
SET_OFF,
ON_PRESS,
START_BLUETOOTH_PIN_SCREEN,
STOP_BLUETOOTH_PIN_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
};

23
src/concurrency/Lock.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "Lock.h"
#include <cassert>
namespace concurrency {
Lock::Lock()
{
handle = xSemaphoreCreateBinary();
assert(handle);
assert(xSemaphoreGive(handle));
}
void Lock::lock()
{
assert(xSemaphoreTake(handle, portMAX_DELAY));
}
void Lock::unlock()
{
assert(xSemaphoreGive(handle));
}
} // namespace concurrency

33
src/concurrency/Lock.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include "../freertosinc.h"
namespace concurrency {
/**
* @brief Simple wrapper around FreeRTOS API for implementing a mutex lock
*/
class Lock
{
public:
Lock();
Lock(const Lock &) = delete;
Lock &operator=(const Lock &) = delete;
/// Locks the lock.
//
// Must not be called from an ISR.
void lock();
// Unlocks the lock.
//
// Must not be called from an ISR.
void unlock();
private:
SemaphoreHandle_t handle;
};
} // namespace concurrency

View File

@@ -0,0 +1,15 @@
#include "LockGuard.h"
namespace concurrency {
LockGuard::LockGuard(Lock *lock) : lock(lock)
{
lock->lock();
}
LockGuard::~LockGuard()
{
lock->unlock();
}
} // namespace concurrency

View File

@@ -0,0 +1,23 @@
#pragma once
#include "Lock.h"
namespace concurrency {
/**
* @brief RAII lock guard
*/
class LockGuard
{
public:
LockGuard(Lock *lock);
~LockGuard();
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
private:
Lock *lock;
};
} // namespace concurrency

View File

@@ -0,0 +1,19 @@
#include "NotifiedWorkerThread.h"
namespace concurrency {
/**
* Notify this thread so it can run
*/
void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action)
{
xTaskNotify(taskHandle, v, action);
}
void NotifiedWorkerThread::block()
{
xTaskNotifyWait(0, // don't clear notification on entry
clearOnRead, &notification, portMAX_DELAY); // Wait forever
}
} // namespace concurrency

View File

@@ -0,0 +1,47 @@
#pragma once
#include "WorkerThread.h"
namespace concurrency {
/**
* @brief A worker thread that waits on a freertos notification
*/
class NotifiedWorkerThread : public WorkerThread
{
public:
/**
* Notify this thread so it can run
*/
void notify(uint32_t v = 0, eNotifyAction action = eNoAction);
/**
* Notify from an ISR
*
* This must be inline or IRAM_ATTR on ESP32
*/
inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction)
{
xTaskNotifyFromISR(taskHandle, v, action, highPriWoken);
}
protected:
/**
* The notification that was most recently used to wake the thread. Read from loop()
*/
uint32_t notification = 0;
/**
* What notification bits should be cleared just after we read and return them in notification?
*
* Defaults to clear all of them.
*/
uint32_t clearOnRead = UINT32_MAX;
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block();
};
} // namespace concurrency

View File

@@ -1,12 +1,12 @@
#pragma once
#include "PeriodicTask.h"
#include <Arduino.h>
namespace concurrency {
/**
* Periodically invoke a callback.
*
* This just provides C style callback conventions rather than a virtual function - FIXME, remove?
* @brief Periodically invoke a callback. This just provides C-style callback conventions
* rather than a virtual function - FIXME, remove?
*/
class Periodic : public PeriodicTask
{
@@ -17,5 +17,10 @@ class Periodic : public PeriodicTask
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
protected:
void doTask();
void doTask() {
uint32_t p = callback();
setPeriod(p);
}
};
} // namespace concurrency

View File

@@ -0,0 +1,35 @@
#include "PeriodicScheduler.h"
#include "PeriodicTask.h"
#include "LockGuard.h"
#include "../timing.h"
namespace concurrency {
/// call this from loop
void PeriodicScheduler::loop()
{
LockGuard lg(&lock);
uint32_t now = timing::millis();
for (auto t : tasks) {
if (t->period && (now - t->lastMsec) >= t->period) {
t->doTask();
t->lastMsec = now;
}
}
}
void PeriodicScheduler::schedule(PeriodicTask *t)
{
LockGuard lg(&lock);
tasks.insert(t);
}
void PeriodicScheduler::unschedule(PeriodicTask *t)
{
LockGuard lg(&lock);
tasks.erase(t);
}
} // namespace concurrency

View File

@@ -0,0 +1,40 @@
#pragma once
#include "Lock.h"
#include <cstdint>
#include <unordered_set>
namespace concurrency {
class PeriodicTask;
/**
* @brief Runs all PeriodicTasks in the system. Currently called from main loop()
* but eventually should be its own thread blocked on a freertos timer.
*/
class PeriodicScheduler
{
friend class PeriodicTask;
/**
* This really should be some form of heap, and when the period gets changed on a task it should get
* rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check
* _every_ tasks. If it was a heap we'd only have to check the first task.
*/
std::unordered_set<PeriodicTask *> tasks;
// Protects the above variables.
Lock lock;
public:
/// Run any next tasks which are due for execution
void loop();
private:
void schedule(PeriodicTask *t);
void unschedule(PeriodicTask *t);
};
extern PeriodicScheduler periodicScheduler;
} // namespace concurrency

View File

@@ -0,0 +1,16 @@
#include "PeriodicTask.h"
#include "Periodic.h"
#include "LockGuard.h"
namespace concurrency {
PeriodicScheduler periodicScheduler;
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
void PeriodicTask::setup()
{
periodicScheduler.schedule(this);
}
} // namespace concurrency

View File

@@ -0,0 +1,56 @@
#pragma once
#include "PeriodicScheduler.h"
#include "timing.h"
namespace concurrency {
/**
* @brief A base class for tasks that want their doTask() method invoked periodically
*
* @todo currently just syntatic sugar for polling in loop (you must call .loop), but eventually
* generalize with the freertos scheduler so we can save lots of power by having everything either in
* something like this or triggered off of an irq.
*/
class PeriodicTask
{
friend class PeriodicScheduler;
uint32_t lastMsec = 0;
uint32_t period = 1; // call soon after creation
public:
virtual ~PeriodicTask() { periodicScheduler.unschedule(this); }
/**
* Constructor (will schedule with the global PeriodicScheduler)
*/
PeriodicTask(uint32_t initialPeriod = 1);
/**
* MUST be be called once at startup (but after threading is running - i.e. not from a constructor)
*/
void setup();
/**
* Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
* While zero this task is disabled and will not run
*/
void setPeriod(uint32_t p)
{
lastMsec = timing::millis(); // reset starting from now
period = p;
}
uint32_t getPeriod() const { return period; }
/**
* Syntatic sugar for suspending tasks
*/
void disable() { setPeriod(0); }
protected:
virtual void doTask() = 0;
};
} // namespace concurrency

View File

@@ -0,0 +1,17 @@
#include "Thread.h"
#include "timing.h"
namespace concurrency {
void Thread::start(const char *name, size_t stackSize, uint32_t priority)
{
auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle);
assert(r == pdPASS);
}
void Thread::callRun(void *_this)
{
((Thread *)_this)->doRun();
}
} // namespace concurrency

56
src/concurrency/Thread.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include "freertosinc.h"
#include "esp_task_wdt.h"
namespace concurrency {
/**
* @brief Base threading
*/
class Thread
{
protected:
TaskHandle_t taskHandle = NULL;
/**
* set this to true to ask thread to cleanly exit asap
*/
volatile bool wantExit = false;
public:
void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY);
virtual ~Thread() { vTaskDelete(taskHandle); }
uint32_t getStackHighwaterMark() { return uxTaskGetStackHighWaterMark(taskHandle); }
protected:
/**
* The method that will be called when start is called.
*/
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:
static void callRun(void *_this);
};
} // namespace concurrency

View File

@@ -0,0 +1,32 @@
#include "WorkerThread.h"
#include "timing.h"
namespace concurrency {
void WorkerThread::doRun()
{
startWatchdog();
while (!wantExit) {
stopWatchdog();
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
static uint32_t lastPrint = 0;
if (timing::millis() - lastPrint > 10 * 1000L) {
lastPrint = timing::millis();
meshtastic::printThreadInfo("net");
}
#endif
loop();
}
stopWatchdog();
}
} // namespace concurrency

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Thread.h"
namespace concurrency {
/**
* @brief This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting
* old-school arduino loop() code. Use as a mixin base class for the classes you want to convert.
*
* @link https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html
*/
class WorkerThread : public Thread
{
protected:
/**
* A method that should block execution - either waiting ona queue/mutex or a "task notification"
*/
virtual void block() = 0;
virtual void loop() = 0;
/**
* The method that will be called when start is called.
*/
virtual void doRun();
};
} // namespace concurrency

View File

@@ -144,8 +144,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define I2C_SDA 21
#define I2C_SCL 22
#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
#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
#define RESET_GPIO 14
@@ -211,9 +211,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RF95_IRQ_GPIO 26
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(TTGO_LORA_V1)
#elif defined(TLORA_V1)
// 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 "tlora-v1"
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN 36
@@ -232,9 +232,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(TTGO_LORA_V2)
#elif defined(TLORA_V2)
// 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 "tlora-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_SCL 22
@@ -243,14 +248,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define BUTTON_PIN \
0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
// between this pin and ground
#define BUTTON_PIN 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one between this pin and ground
#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#elif defined(TLORA_V2_1_16)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR "tlora-v2-1-1.6"
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN 36
#define GPS_TX_PIN 39
#define I2C_SDA 21 // I2C pins for this board
#define I2C_SCL 22
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define BUTTON_PIN 12 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one between this pin and ground
#define RESET_GPIO 23 // If defined, this pin will be used to reset the LORA radio
#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio
#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number
#endif
#ifdef ARDUINO_NRF52840_PCA10056

View File

@@ -1,20 +0,0 @@
#include "debug.h"
#include <cstdint>
#include "freertosinc.h"
#include "configuration.h"
namespace meshtastic
{
void printThreadInfo(const char *extra)
{
#ifndef NO_ESP32
uint32_t taskHandle = reinterpret_cast<uint32_t>(xTaskGetCurrentTaskHandle());
DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(),
uxTaskGetStackHighWaterMark(nullptr));
#endif
}
} // namespace meshtastic

View File

@@ -1,10 +0,0 @@
#pragma once
namespace meshtastic
{
/// Dumps out which core we are running on, and min level of remaining stack
/// seen.
void printThreadInfo(const char *extra);
} // namespace meshtastic

View File

@@ -3,21 +3,22 @@
#include "CallbackCharacteristic.h"
#include "RadioLibInterface.h"
#include "configuration.h"
#include "lock.h"
#include "../concurrency/LockGuard.h"
#include "../timing.h"
#include <Arduino.h>
#include <BLE2902.h>
#include <CRC32.h>
#include <Update.h>
#include <esp_gatt_defs.h>
using namespace meshtastic;
//using namespace meshtastic;
CRC32 crc;
uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t updateExpectedSize, updateActualSize;
Lock *updateLock;
concurrency::Lock *updateLock;
class TotalSizeCharacteristic : public CallbackCharacteristic
{
@@ -30,7 +31,7 @@ class TotalSizeCharacteristic : public CallbackCharacteristic
void onWrite(BLECharacteristic *c)
{
LockGuard g(updateLock);
concurrency::LockGuard g(updateLock);
// Check if there is enough to OTA Update
uint32_t len = getValue32(c, 0);
updateExpectedSize = len;
@@ -65,7 +66,7 @@ class DataCharacteristic : public CallbackCharacteristic
void onWrite(BLECharacteristic *c)
{
LockGuard g(updateLock);
concurrency::LockGuard g(updateLock);
std::string value = c->getValue();
uint32_t len = value.length();
assert(len <= MAX_BLOCKSIZE);
@@ -89,7 +90,7 @@ class CRC32Characteristic : public CallbackCharacteristic
void onWrite(BLECharacteristic *c)
{
LockGuard g(updateLock);
concurrency::LockGuard g(updateLock);
uint32_t expectedCRC = getValue32(c, 0);
uint32_t actualCRC = crc.finalize();
DEBUG_MSG("expected CRC %u\n", expectedCRC);
@@ -106,7 +107,7 @@ class CRC32Characteristic : public CallbackCharacteristic
} else {
if (Update.end()) {
DEBUG_MSG("OTA done, rebooting in 5 seconds!\n");
rebootAtMsec = millis() + 5000;
rebootAtMsec = timing::millis() + 5000;
} else {
DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError());
}
@@ -124,7 +125,7 @@ class CRC32Characteristic : public CallbackCharacteristic
void bluetoothRebootCheck()
{
if (rebootAtMsec && millis() > rebootAtMsec) {
if (rebootAtMsec && timing::millis() > rebootAtMsec) {
DEBUG_MSG("Rebooting for update\n");
ESP.restart();
}
@@ -137,7 +138,7 @@ See bluetooth-api.md
BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion)
{
if (!updateLock)
updateLock = new Lock();
updateLock = new concurrency::Lock();
// Create the BLE Service
BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0);

View File

@@ -2,11 +2,13 @@
#include "MeshBluetoothService.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "esp_task_wdt.h"
#include "main.h"
#include "power.h"
#include "sleep.h"
#include "utils.h"
#include "target_specific.h"
#include "utils.h"
#include <nvs.h>
#include <nvs_flash.h>
bool bluetoothOn;
@@ -60,111 +62,6 @@ void getMacAddr(uint8_t *dmac)
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();
// If the AXP192 returns a valid battery percentage, use it
if (axp.getBattPercentage() >= 0) {
powerStatus.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
powerStatus.batteryChargePercent = clamp((int)(((powerStatus.batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
}
DEBUG_MSG("Battery %dmV %d%%\n", powerStatus.batteryVoltageMv, powerStatus.batteryChargePercent);
}
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() {
int dev_num = esp_ble_get_bond_device_num();
@@ -188,11 +85,23 @@ void esp32Setup()
DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize());
DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram());
nvs_stats_t nvs_stats;
auto res = nvs_get_stats(NULL, &nvs_stats);
assert(res == ESP_OK);
DEBUG_MSG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d\n", nvs_stats.used_entries, nvs_stats.free_entries,
nvs_stats.total_entries);
// enableModemSleep();
#ifdef AXP192_SLAVE_ADDRESS
axp192Init();
#endif
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
#define APP_WATCHDOG_SECS 45
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
@@ -215,51 +124,12 @@ uint32_t axpDebugRead()
Periodic axpDebugOutput(axpDebugRead);
#endif
/// loop code specific to ESP32 targets
void esp32Loop()
{
esp_task_wdt_reset(); // service our app level watchdog
loopBLE();
// for debug printing
// 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
}

View File

@@ -1,9 +1,9 @@
#include "GPS.h"
#include "configuration.h"
#include "time.h"
#include "timing.h"
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#ifdef GPS_RX_PIN
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
@@ -27,7 +27,7 @@ void readFromRTC()
struct timeval tv; /* btw settimeofday() is helpfull here too*/
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
uint32_t now = timing::millis();
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
timeStartMsec = now;
@@ -68,11 +68,9 @@ void perhapsSetRTC(struct tm &t)
perhapsSetRTC(&tv);
}
#include <time.h>
uint32_t getTime()
{
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
return ((timing::millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
}
uint32_t getValidTime()

View File

@@ -1,7 +1,8 @@
#pragma once
#include "Observer.h"
#include "PeriodicTask.h"
#include "GPSStatus.h"
#include "../concurrency/PeriodicTask.h"
#include "sys/time.h"
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
@@ -35,11 +36,15 @@ class GPS : public Observable<void *>
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)
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
uint32_t numSatellites = 0;
bool isConnected = false; // Do we have a GPS we are talking to
virtual ~GPS() {}
Observable<const meshtastic::GPSStatus *> newStatus;
/**
* Returns true if we succeeded
*/

View File

@@ -1,5 +1,6 @@
#include "NEMAGPS.h"
#include "configuration.h"
#include "timing.h"
static int32_t toDegInt(RawDegrees d)
{
@@ -19,7 +20,7 @@ void NEMAGPS::loop()
reader.encode(c);
}
uint32_t now = millis();
uint32_t now = timing::millis();
if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume
// serial chars at whatever rate)
lastUpdateMsec = now;
@@ -54,16 +55,26 @@ void NEMAGPS::loop()
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()) {
if (reader.hdop.isValid()) {
dop = reader.hdop.value();
}
if (reader.course.isValid()) {
heading = reader.course.value() * 1e3; //Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
}
if (reader.satellites.isValid()) {
numSatellites = reader.satellites.value();
}
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
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);
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2, heading * 1e-5);
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
if (hasValidLocation)
notifyObservers(NULL);
}
// Notify any status instances that are observing us
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
newStatus.notifyObservers(&status);
}
}

View File

@@ -2,7 +2,7 @@
#include "GPS.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "../concurrency/PeriodicTask.h"
#include "TinyGPS++.h"
/**

View File

@@ -2,7 +2,7 @@
#include "sleep.h"
#include <assert.h>
UBloxGPS::UBloxGPS() : PeriodicTask()
UBloxGPS::UBloxGPS() : concurrency::PeriodicTask()
{
notifySleepObserver.observe(&notifySleep);
}
@@ -55,7 +55,7 @@ bool UBloxGPS::setup()
ok = ublox.saveConfiguration(3000);
assert(ok);
PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
concurrency::PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
return true;
} else {
@@ -116,7 +116,8 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
longitude = ublox.getLongitude(0);
altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
heading = ublox.getHeading(0);
numSatellites = ublox.getSIV(0);
// 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!
@@ -129,6 +130,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
wantNewLocation = true;
// Notify any status instances that are observing us
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop, heading, numSatellites);
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
// the serial
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);

View File

@@ -2,7 +2,7 @@
#include "GPS.h"
#include "Observer.h"
#include "PeriodicTask.h"
#include "../concurrency/PeriodicTask.h"
#include "SparkFun_Ublox_Arduino_Library.h"
/**
@@ -10,7 +10,7 @@
*
* When new data is available it will notify observers.
*/
class UBloxGPS : public GPS, public PeriodicTask
class UBloxGPS : public GPS, public concurrency::PeriodicTask
{
SFE_UBLOX_GPS ublox;

View File

@@ -26,28 +26,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
#include "fonts.h"
#include "images.h"
#include "graphics/images.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "screen.h"
#include "Screen.h"
#include "utils.h"
#include "configs.h"
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
#ifdef USE_SH1106
#define SCREEN_WIDTH 132
#else
#define SCREEN_WIDTH 128
#endif
#define SCREEN_HEIGHT 64
#define TRANSITION_FRAMERATE 30 // fps
#define IDLE_FRAMERATE 10 // in fps
#define COMPASS_DIAM 44
using namespace meshtastic; /** @todo remove */
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
namespace meshtastic
namespace graphics
{
// A text message frame + debug frame + all the node infos
@@ -55,7 +43,14 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
static uint32_t targetFramerate = IDLE_FRAMERATE;
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 };
uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C };
uint8_t imgSatellite[8] = { 0x70, 0x71, 0x22, 0xFA, 0xFA, 0x22, 0x71, 0x70 };
uint32_t dopThresholds[5] = { 2000, 1000, 500, 200, 100 };
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
#endif
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -69,10 +64,11 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
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
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
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);
}
@@ -91,8 +87,8 @@ static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state,
display->setFont(ArialMT_Plain_10);
char buf[30];
const char *name = "Name: ";
strcpy(buf,name);
strcat(buf,getDeviceName());
strcpy(buf, name);
strcat(buf, getDeviceName());
display->drawString(64 + x, 48 + y, buf);
}
@@ -142,75 +138,106 @@ 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
/// @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);
#if 0
/// Draw a series of fields in a row, wrapping to multiple rows if needed
/// @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);
const char **f = fields;
int xo = x, yo = y;
const int COLUMNS = 2; // hardwired for two columns per row....
int col = 0; // track which column we are on
while (*f) {
display->drawString(xo, yo, *f);
xo += SCREEN_WIDTH / COLUMNS;
// Wrap to next row, if needed.
if (++col >= COLUMNS) {
xo = x;
yo += FONT_HEIGHT;
col = 0;
const char **f = fields;
int xo = x, yo = y;
const int COLUMNS = 2; // hardwired for two columns per row....
int col = 0; // track which column we are on
while (*f) {
display->drawString(xo, yo, *f);
xo += SCREEN_WIDTH / COLUMNS;
// Wrap to next row, if needed.
if (++col >= COLUMNS) {
xo = x;
yo += FONT_HEIGHT;
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, 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 };
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->charging) {
if (powerStatus->getIsCharging()) {
memcpy(imgBuffer + 3, lightning, 8);
// If not charging, Draw power bars
// If not charging, Draw power bars
} else {
for (int i = 0; i < 4; i++) {
if(powerStatus->batteryChargePercent >= 25 * i) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
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, int nodesOnline, int nodesTotal) {
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *nodeStatus)
{
char usersString[20];
sprintf(usersString, "%d/%d", nodesOnline, nodesTotal);
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, GPS *gps) {
if(!gps->isConnected) { display->drawString(x, y - 2, "No GPS"); return; }
display->drawFastImage(x, y, 6, 8, gps->hasLock() ? imgPositionSolid : imgPositionEmpty );
if(!gps->hasLock()) { display->drawString(x + 8, y - 2, "No sats"); return; }
if(gps->dop <= 100) { display->drawString(x + 8, y - 2, "Ideal"); return; }
if(gps->dop <= 200) { display->drawString(x + 8, y - 2, "Exc."); return; }
if(gps->dop <= 500) { display->drawString(x + 8, y - 2, "Good"); return; }
if(gps->dop <= 1000) { display->drawString(x + 8, y - 2, "Mod."); return; }
if(gps->dop <= 2000) { display->drawString(x + 8, y - 2, "Fair"); return; }
if(gps->dop > 0) { display->drawString(x + 8, y - 2, "Poor"); return; }
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;
}
else
{
char satsString[3];
uint8_t bar[2] = { 0 };
//Draw DOP signal bars
for(int i = 0; i < 5; i++)
{
if (gps->getDOP() <= dopThresholds[i])
bar[0] = ~((1 << (5 - i)) - 1);
else
bar[0] = 0b10000000;
//bar[1] = bar[0];
display->drawFastImage(x + 9 + (i * 2), y, 2, 8, bar);
}
//Draw satellite image
display->drawFastImage(x + 24, y, 8, 8, imgSatellite);
//Draw the number of satellites
sprintf(satsString, "%d", gps->getNumSatellites());
display->drawString(x + 34, y - 2, satsString);
}
}
/// Ported from my old java code, returns distance in meters along the globe
@@ -352,28 +379,41 @@ static bool hasPosition(NodeInfo *n)
static size_t nodeIndex;
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)
// Draw the arrow pointing to a node's location
static void drawNodeHeading(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};
Point *arrowPoints[] = {&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);
arrowPoints[i]->rotate(headingRadian);
arrowPoints[i]->scale(COMPASS_DIAM * 0.6);
arrowPoints[i]->translate(compassX, compassY);
}
drawLine(display, tip, tail);
drawLine(display, leftArrow, tip);
drawLine(display, rightArrow, tip);
}
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
// Draw the compass heading
static void drawCompassHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, float myHeading)
{
Point N1(-0.04f, -0.65f), N2( 0.04f, -0.65f);
Point N3(-0.04f, -0.55f), N4( 0.04f, -0.55f);
Point *rosePoints[] = {&N1, &N2, &N3, &N4};
for (int i = 0; i < 4; i++) {
rosePoints[i]->rotate(myHeading);
rosePoints[i]->scale(COMPASS_DIAM);
rosePoints[i]->translate(compassX, compassY);
}
drawLine(display, N1, N3);
drawLine(display, N2, N4);
drawLine(display, N1, N4);
}
/// Convert an integer GPS coords to a floating point
@@ -393,10 +433,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
n = nodeDB.getNodeByIndex(nodeIndex);
}
// We just changed to a new node screen, ask that node for updated state
displayedNodeNum = n->num;
service.sendNetworkPing(displayedNodeNum, true);
// We just changed to a new node screen, ask that node for updated state if it's older than 2 minutes
if(sinceLastSeen(n) > 120)
{
service.sendNetworkPing(displayedNodeNum, true);
}
}
NodeInfo *node = nodeDB.getNodeByIndex(nodeIndex);
@@ -421,36 +464,49 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
static char distStr[20];
*distStr = 0; // might not have location data
strcpy(distStr, "? km"); // might not have location data
float headingRadian;
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
drawColumns(display, x, y, fields);
// coordinates for the center of the compass/circle
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 5, compassY = y + SCREEN_HEIGHT / 2;
bool hasNodeHeading = false;
if (ourNode && hasPosition(ourNode) && hasPosition(node)) { // display direction toward node
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));
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
if(ourNode && hasPosition(ourNode))
{
Position &op = ourNode->position;
float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
drawCompassHeading(display, compassX, compassY, myHeading);
// FIXME, also keep the guess at the operators heading and add/substract
// it. currently we don't do this and instead draw north up only.
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));
headingRadian = bearingToOther - myHeading;
drawCompass(display, compassX, compassY, headingRadian);
} else { // direction to node is unknown so display question mark
if(hasPosition(node))
{
// display direction toward node
hasNodeHeading = true;
Position &p = node->position;
float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
// FIXME, also keep the guess at the operators heading and add/substract
// it. currently we don't do this and instead draw north up only.
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
headingRadian = bearingToOther - myHeading;
drawNodeHeading(display, compassX, compassY, headingRadian);
}
}
if(!hasNodeHeading)
// direction to node is unknown so display question mark
// Debug info for gps lock errors
// 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);
display->drawString(compassX - FONT_HEIGHT/4, compassY - FONT_HEIGHT/2, "?");
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
}
// Must be after distStr is populated
drawColumns(display, x, y, fields);
}
#if 0
@@ -498,7 +554,7 @@ void Screen::handleSetOn(bool on)
void Screen::setup()
{
PeriodicTask::setup();
concurrency::PeriodicTask::setup();
// We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device
// is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device.
@@ -545,6 +601,11 @@ void Screen::setup()
// twice initially.
ui.update();
ui.update();
// Subscribe to status updates
powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
}
void Screen::doTask()
@@ -557,7 +618,7 @@ void Screen::doTask()
// Process incoming commands.
for (;;) {
CmdItem cmd;
ScreenCmd cmd;
if (!cmdQueue.dequeue(&cmd, 0)) {
break;
}
@@ -606,14 +667,7 @@ void Screen::doTask()
// While showing the bootscreen or Bluetooth pair screen all of our
// standard screen switching is stopped.
if (showingNormalScreen) {
// TODO(girts): decouple nodeDB from screen.
// 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;
}
// standard screen loop handling here
}
ui.update();
@@ -638,8 +692,8 @@ void Screen::setFrames()
DEBUG_MSG("showing standard frames\n");
showingNormalScreen = true;
size_t numnodes = nodeDB.getNumNodes();
// 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)
numnodes--;
@@ -715,36 +769,61 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
char channelStr[20];
{
LockGuard guard(&lock);
concurrency::LockGuard guard(&lock);
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
// Display power status
if (powerStatus.haveBattery) drawBattery(display, x, y + 2, imgBattery, &powerStatus); else display->drawFastImage(x, y + 2, 16, 8, powerStatus.usb ? imgUSB : imgPower);
if (powerStatus->getHasBattery())
drawBattery(display, x, y + 2, imgBattery, powerStatus);
else
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
// Display nodes status
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodesOnline, nodesTotal);
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
// Display GPS status
drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps);
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
}
const char *fields[] = {channelStr, nullptr};
uint32_t yo = drawRows(display, x, y + FONT_HEIGHT, fields);
display->drawString(x, y + FONT_HEIGHT, channelStr);
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)
{
// adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
void Screen::adjustBrightness()
{
if (brightness == 254) {
brightness = 0;
} else {
brightness++;
brightness++;
}
int width = brightness / 1.984375;
dispdev.drawRect( 0, 30, 128, 4);
dispdev.fillRect(0, 30, width, 4);
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);
}
} // namespace meshtastic
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
{
//DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
switch(arg->getStatusType())
{
case STATUS_TYPE_NODE:
if (nodeDB.updateTextMessage || nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal())
setFrames();
prevFrame = -1;
nodeDB.updateGUI = false;
nodeDB.updateTextMessage = false;
break;
}
setPeriod(1); // Update the screen right away
return 0;
}
} // namespace graphics

View File

@@ -10,13 +10,14 @@
#include <SSD1306Wire.h>
#endif
#include "PeriodicTask.h"
#include "concurrency/PeriodicTask.h"
#include "TypedQueue.h"
#include "lock.h"
#include "concurrency/LockGuard.h"
#include "power.h"
#include "commands.h"
#include <string>
namespace meshtastic
namespace graphics
{
// Forward declarations
@@ -29,40 +30,13 @@ class DebugInfo
DebugInfo(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.
void setChannelNameStatus(const char *name)
{
LockGuard guard(&lock);
concurrency::LockGuard guard(&lock);
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:
friend Screen;
@@ -71,28 +45,25 @@ class DebugInfo
/// Renders the debug screen.
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 gpsStatus;
/// Protects all of internal state.
Lock lock;
concurrency::Lock lock;
};
/// Deals with showing things on the screen of the device.
//
// Other than setup(), this class is thread-safe. All state-changing calls are
// queued and executed when the main loop calls us.
//
// This class is thread-safe (as long as drawFrame is not called multiple times
// simultaneously).
class Screen : public PeriodicTask
/**
* @brief This class deals with showing things on the screen of the device.
*
* @details Other than setup(), this class is thread-safe as long as drawFrame is not called
* multiple times simultaneously. All state-changing calls are queued and executed
* when the main loop calls us.
*/
class Screen : public concurrency::PeriodicTask
{
CallbackObserver<Screen, const meshtastic::Status *> powerStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic::Status *> gpsStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver = CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
public:
Screen(uint8_t address, int sda = -1, int scl = -1);
@@ -111,15 +82,15 @@ class Screen : public PeriodicTask
handleSetOn(
false); // We handle off commands immediately, because they might be called because the CPU is shutting down
else
enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
}
/// Handles a button press.
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
// Implementation to Adjust Brightness
void adjustBrightness();
int brightness = 150;
uint8_t brightness = 150;
/// Starts showing the Bluetooth PIN screen.
//
@@ -127,22 +98,22 @@ class Screen : public PeriodicTask
// with the PIN.
void startBluetoothPinScreen(uint32_t pin)
{
CmdItem cmd;
ScreenCmd cmd;
cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN;
cmd.bluetooth_pin = pin;
enqueueCmd(cmd);
}
/// Stops showing the bluetooth PIN screen.
void stopBluetoothPinScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
/// Stops showing the boot screen.
void stopBootScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BOOT_SCREEN}); }
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
/// Writes a string to the screen.
void print(const char *text)
{
CmdItem cmd;
ScreenCmd cmd;
cmd.cmd = Cmd::PRINT;
// TODO(girts): strdup() here is scary, but we can't use std::string as
// FreeRTOS queue is just dumbly copying memory contents. It would be
@@ -187,7 +158,9 @@ class Screen : public PeriodicTask
/// Returns a handle to the DebugInfo screen.
//
// Use this handle to set things like battery status, user count, GPS status, etc.
DebugInfo *debug() { return &debugInfo; }
DebugInfo* debug_info() { return &debugInfo; }
int handleStatusUpdate(const meshtastic::Status *arg);
protected:
/// Updates the UI.
@@ -196,17 +169,7 @@ class Screen : public PeriodicTask
void doTask() final;
private:
enum class Cmd {
INVALID,
SET_ON,
SET_OFF,
ON_PRESS,
START_BLUETOOTH_PIN_SCREEN,
STOP_BLUETOOTH_PIN_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
};
struct CmdItem {
struct ScreenCmd {
Cmd cmd;
union {
uint32_t bluetooth_pin;
@@ -215,7 +178,7 @@ class Screen : public PeriodicTask
};
/// Enques given command item to be processed by main loop().
bool enqueueCmd(const CmdItem &cmd)
bool enqueueCmd(const ScreenCmd &cmd)
{
if (!useDisplay)
return true; // claim success if our display is not in use
@@ -239,7 +202,7 @@ class Screen : public PeriodicTask
static void drawDebugInfoTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
/// Queue of commands to execute in doTask.
TypedQueue<CmdItem> cmdQueue;
TypedQueue<ScreenCmd> cmdQueue;
/// Whether we are using a display
bool useDisplay = false;
/// Whether the display is currently powered
@@ -250,7 +213,9 @@ class Screen : public PeriodicTask
/// Holds state for debug information
DebugInfo debugInfo;
/// Display device
/** @todo display abstraction */
#ifdef USE_SH1106
SH1106Wire dispdev;
#else
@@ -260,4 +225,4 @@ class Screen : public PeriodicTask
OLEDDisplayUi ui;
};
} // namespace meshtastic
} // namespace graphics

17
src/graphics/configs.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "fonts.h"
#define FONT_HEIGHT 14 // actually 13 for "Arial 10" but want a little extra space
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
// This means the *visible* area (sh1106 can address 132, but shows 128 for example)
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define TRANSITION_FRAMERATE 30 // fps
#define IDLE_FRAMERATE 1 // in fps
#define COMPASS_DIAM 44
// DEBUG
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
// if defined a pixel will blink to show redraws
// #define SHOW_REDRAWS

View File

@@ -1,3 +1,5 @@
#pragma once
const uint8_t Custom_ArialMT_Plain_10[] PROGMEM = {
0x0A, // Width: 10
0x0A, // Height: 10

View File

@@ -1,3 +1,5 @@
#pragma once
#define SATELLITE_IMAGE_WIDTH 16
#define SATELLITE_IMAGE_HEIGHT 15
const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02,
@@ -10,12 +12,12 @@ const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99
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 "img/icon.xbm"
// We now programmatically draw our compass
#if 0
const
#include "compass.xbm"
#include "img/compass.xbm"
#endif
#if 0

View File

@@ -1,50 +0,0 @@
#include "lock.h"
#include <cassert>
namespace meshtastic
{
#ifdef configUSE_PREEMPTION
Lock::Lock()
{
handle = xSemaphoreCreateBinary();
assert(handle);
assert(xSemaphoreGive(handle));
}
void Lock::lock()
{
assert(xSemaphoreTake(handle, portMAX_DELAY));
}
void Lock::unlock()
{
assert(xSemaphoreGive(handle));
}
#else
Lock::Lock()
{
}
void Lock::lock()
{
}
void Lock::unlock()
{
}
#endif
LockGuard::LockGuard(Lock *lock) : lock(lock)
{
lock->lock();
}
LockGuard::~LockGuard()
{
lock->unlock();
}
} // namespace meshtastic

View File

@@ -1,47 +0,0 @@
#pragma once
#include "freertosinc.h"
namespace meshtastic
{
// Simple wrapper around FreeRTOS API for implementing a mutex lock.
class Lock
{
public:
Lock();
Lock(const Lock &) = delete;
Lock &operator=(const Lock &) = delete;
/// Locks the lock.
//
// Must not be called from an ISR.
void lock();
// Unlocks the lock.
//
// Must not be called from an ISR.
void unlock();
private:
#ifdef configUSE_PREEMPTION
SemaphoreHandle_t handle;
#endif
};
// RAII lock guard.
class LockGuard
{
public:
LockGuard(Lock *lock);
~LockGuard();
LockGuard(const LockGuard &) = delete;
LockGuard &operator=(const LockGuard &) = delete;
private:
Lock *lock;
};
} // namespace meshtastic

View File

@@ -25,7 +25,7 @@
#include "MeshService.h"
#include "NEMAGPS.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "concurrency/Periodic.h"
#include "PowerFSM.h"
#include "UBloxGPS.h"
#include "configuration.h"
@@ -35,10 +35,11 @@
#include "DSRRouter.h"
#include "debug.h"
#include "main.h"
#include "screen.h"
#include "graphics/Screen.h"
#include "sleep.h"
#include <Wire.h>
#include "timing.h"
#include <OneButton.h>
#include <Wire.h>
// #include <driver/rtc_io.h>
#ifndef NO_ESP32
@@ -54,10 +55,16 @@
#endif
// We always create a screen object, but we only init it if we find the hardware
meshtastic::Screen screen(SSD1306_ADDRESS);
graphics::Screen screen(SSD1306_ADDRESS);
// Global power status singleton
meshtastic::PowerStatus powerStatus;
// Global power status
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 axp192_found;
@@ -121,22 +128,24 @@ static uint32_t ledBlinker()
setLed(ledOn);
// 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);
concurrency::Periodic ledPeriodic(ledBlinker);
// Prepare for button presses
#ifdef BUTTON_PIN
OneButton userButton;
OneButton userButton;
#endif
#ifdef BUTTON_PIN_ALT
OneButton userButtonAlt;
OneButton userButtonAlt;
#endif
void userButtonPressed() {
void userButtonPressed()
{
powerFSM.trigger(EVENT_PRESS);
}
void userButtonPressedLong(){
void userButtonPressedLong()
{
screen.adjustBrightness();
}
@@ -225,6 +234,14 @@ void setup()
esp32Setup();
#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
nrf52Setup();
#endif
@@ -253,9 +270,10 @@ void setup()
gps = new NEMAGPS();
gps->setup();
#endif
gpsStatus->observe(&gps->newStatus);
nodeStatus->observe(&nodeDB.newStatus);
service.init();
#ifndef NO_ESP32
// Must be after we init the service, because the wifi settings are loaded by NodeDB (oops)
initWifi();
@@ -316,7 +334,7 @@ uint32_t axpDebugRead()
return 30 * 1000;
}
Periodic axpDebugOutput(axpDebugRead);
concurrency::Periodic axpDebugOutput(axpDebugRead);
axpDebugOutput.setup();
#endif
@@ -329,7 +347,7 @@ void loop()
powerFSM.run_machine();
service.loop();
periodicScheduler.loop();
concurrency::periodicScheduler.loop();
// axpDebugOutput.loop();
#ifdef DEBUG_PORT
@@ -341,6 +359,9 @@ void loop()
#ifndef NO_ESP32
esp32Loop();
#endif
#ifdef TBEAM_V10
power->loop();
#endif
#ifdef BUTTON_PIN
userButton.tick();
@@ -351,23 +372,22 @@ void loop()
// Show boot screen for first 3 seconds, then switch to normal operation.
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > 3000)) {
if (showingBootScreen && (timing::millis() > 3000)) {
screen.stopBootScreen();
showingBootScreen = false;
}
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (millis() - lastPrint > 10 * 1000L) {
lastPrint = millis();
if (timing::millis() - lastPrint > 10 * 1000L) {
lastPrint = timing::millis();
meshtastic::printThreadInfo("main");
}
#endif
// 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()->setPowerStatus(powerStatus);
screen.debug_info()->setChannelNameStatus(channelSettings.name);
// screen.debug()->setPowerStatus(powerStatus);
// 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.

View File

@@ -1,6 +1,9 @@
#pragma once
#include "screen.h"
#include "graphics/Screen.h"
#include "PowerStatus.h"
#include "GPSStatus.h"
#include "NodeStatus.h"
extern bool axp192_found;
extern bool ssd1306_found;
@@ -8,7 +11,12 @@ extern bool isCharging;
extern bool isUSBPowered;
// Global Screen singleton.
extern meshtastic::Screen screen;
extern graphics::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"
const char *getDeviceName();

View File

@@ -1,7 +1,7 @@
#pragma once
#include "PacketHistory.h"
#include "PeriodicTask.h"
#include "../concurrency/PeriodicTask.h"
#include "Router.h"
/**

View File

@@ -7,12 +7,13 @@
//#include "MeshBluetoothService.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Periodic.h"
#include "../concurrency/Periodic.h"
#include "PowerFSM.h"
#include "main.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
#include "timing.h"
/*
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
@@ -55,7 +56,7 @@ static uint32_t sendOwnerCb()
return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000;
}
static Periodic sendOwnerPeriod(sendOwnerCb);
static concurrency::Periodic sendOwnerPeriod(sendOwnerCb);
MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE)
{
@@ -303,12 +304,12 @@ int MeshService::onGPSChanged(void *unused)
}
// Include our current battery voltage in our position announcement
pos.battery_level = powerStatus.batteryChargePercent;
pos.battery_level = powerStatus->getBatteryChargePercent();
updateBatteryLevel(pos.battery_level);
// We limit our GPS broadcasts to a max rate
static uint32_t lastGpsSend;
uint32_t now = millis();
uint32_t now = timing::millis();
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
lastGpsSend = now;
DEBUG_MSG("Sending position to mesh\n");

View File

@@ -339,12 +339,8 @@ void NodeDB::updateFrom(const MeshPacket &mp)
const SubPacket &p = mp.decoded;
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
int oldNumNodes = *numNodes;
NodeInfo *info = getOrCreateNode(mp.from);
if (oldNumNodes != *numNodes)
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
info->has_position = true; // at least the time is valid
info->position.time = mp.rx_time;
@@ -360,6 +356,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
info->position.time = oldtime;
info->has_position = true;
updateGUIforNode = info;
notifyObservers(true); //Force an update whether or not our node counts have changed
break;
}
@@ -374,6 +371,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
devicestate.has_rx_text_message = true;
updateTextMessage = true;
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
notifyObservers(true); //Force an update whether or not our node counts have changed
}
}
break;
@@ -392,6 +390,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
if (changed) {
updateGUIforNode = info;
powerFSM.trigger(EVENT_NODEDB_UPDATED);
notifyObservers(true); //Force an update whether or not our node counts have changed
// Not really needed - we will save anyways when we go to sleep
// We just changed something important about the user, store our DB
@@ -399,6 +398,10 @@ void NodeDB::updateFrom(const MeshPacket &mp)
}
break;
}
default: {
notifyObservers(); //If the node counts have changed, notify observers
}
}
}
}

View File

@@ -2,9 +2,11 @@
#include <Arduino.h>
#include <assert.h>
#include "Observer.h"
#include "MeshTypes.h"
#include "mesh-pb-constants.h"
#include "NodeStatus.h"
extern DeviceState devicestate;
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
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
Observable<const meshtastic::NodeStatus *> newStatus;
/// 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
@@ -91,6 +94,13 @@ class NodeDB
/// Find a node in our DB, create an empty NodeInfo if missing
NodeInfo *getOrCreateNode(NodeNum n);
/// Notify observers of changes to the DB
void notifyObservers(bool forceUpdate = false) {
// Notify observers of the current node state
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes(), forceUpdate);
newStatus.notifyObservers(&status);
}
/// read our db from flash
void loadFromDisk();

View File

@@ -1,6 +1,7 @@
#include "PacketHistory.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "../timing.h"
PacketHistory::PacketHistory()
{
@@ -18,7 +19,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
return false; // Not a floodable message ID, so we don't care
}
uint32_t now = millis();
uint32_t now = timing::millis();
for (size_t i = 0; i < recentPackets.size();) {
PacketRecord &r = recentPackets[i];

View File

@@ -4,6 +4,7 @@
#include "PowerFSM.h"
#include "RadioInterface.h"
#include "GPS.h"
#include "timing.h"
#include <assert.h>
PhoneAPI::PhoneAPI()
@@ -20,7 +21,7 @@ void PhoneAPI::init()
void PhoneAPI::checkConnectionTimeout()
{
if (isConnected) {
bool newConnected = (millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L);
bool newConnected = (timing::millis() - lastContactMsec < radioConfig.preferences.phone_timeout_secs * 1000L);
if (!newConnected) {
isConnected = false;
onConnectionChanged(isConnected);
@@ -34,7 +35,7 @@ void PhoneAPI::checkConnectionTimeout()
void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
{
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
lastContactMsec = millis();
lastContactMsec = timing::millis();
if (!isConnected) {
isConnected = true;
onConnectionChanged(isConnected);

View File

@@ -6,6 +6,7 @@
#include "assert.h"
#include "configuration.h"
#include "sleep.h"
#include "timing.h"
#include <assert.h>
#include <pb_decode.h>
#include <pb_encode.h>
@@ -155,7 +156,7 @@ size_t RadioInterface::beginSending(MeshPacket *p)
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now
lastTxStart = millis();
lastTxStart = timing::millis();
PacketHeader *h = (PacketHeader *)radiobuf;

View File

@@ -4,7 +4,7 @@
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "WorkerThread.h"
#include "../concurrency/NotifiedWorkerThread.h"
#include "mesh.pb.h"
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
@@ -43,7 +43,7 @@ typedef enum {
*
* This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations)
*/
class RadioInterface : protected NotifiedWorkerThread
class RadioInterface : protected concurrency::NotifiedWorkerThread
{
friend class MeshRadio; // for debugging we let that class touch pool
PointerQueue<MeshPacket> *rxDest = NULL;

View File

@@ -10,7 +10,7 @@ static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi, PhysicalLayer *_iface)
: PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
: concurrency::PeriodicTask(0), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
{
assert(!instance); // We assume only one for now
instance = this;

View File

@@ -1,6 +1,6 @@
#pragma once
#include "PeriodicTask.h"
#include "../concurrency/PeriodicTask.h"
#include "RadioInterface.h"
#ifdef CubeCell_BoardPlus
@@ -16,7 +16,7 @@
#define INTERRUPT_ATTR
#endif
class RadioLibInterface : public RadioInterface, private PeriodicTask
class RadioLibInterface : public RadioInterface, private concurrency::PeriodicTask
{
/// Used as our notification from the ISR
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };

View File

@@ -2,6 +2,7 @@
#include "MeshTypes.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "timing.h"
// ReliableRouter::ReliableRouter() {}
@@ -162,7 +163,7 @@ PendingPacket *ReliableRouter::startRetransmission(MeshPacket *p)
*/
void ReliableRouter::doRetransmissions()
{
uint32_t now = millis();
uint32_t now = timing::millis();
// FIXME, we should use a better datastructure rather than walking through this map.
// for(auto el: pending) {

View File

@@ -1,7 +1,8 @@
#pragma once
#include "FloodingRouter.h"
#include "PeriodicTask.h"
#include "../concurrency/PeriodicTask.h"
#include "../timing.h"
#include <unordered_map>
/**
@@ -48,7 +49,7 @@ struct PendingPacket {
PendingPacket() {}
PendingPacket(MeshPacket *p);
void setNextTx() { nextTxMsec = millis() + random(20 * 1000L, 22 * 1000L); }
void setNextTx() { nextTxMsec = timing::millis() + random(20 * 1000L, 22 * 1000L); }
};
class GlobalPacketIdHashFunction

View File

@@ -45,6 +45,10 @@ typedef struct _ChannelSettings {
ChannelSettings_ModemConfig modem_config;
ChannelSettings_psk_t psk;
char name[12];
uint32_t bandwidth;
uint32_t spread_factor;
uint32_t coding_rate;
uint32_t channel_num;
} ChannelSettings;
typedef PB_BYTES_ARRAY_T(240) Data_payload_t;
@@ -241,7 +245,7 @@ typedef struct _ToRadio {
#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define SubPacket_init_default {0, {Position_init_default}, 0, 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}}, "", 0, 0, 0, 0}
#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}}
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
@@ -257,7 +261,7 @@ typedef struct _ToRadio {
#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
#define SubPacket_init_zero {0, {Position_init_zero}, 0, 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}}, "", 0, 0, 0, 0}
#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}}
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
@@ -271,6 +275,10 @@ typedef struct _ToRadio {
/* Field tags (for use in manual encoding/decoding) */
#define ChannelSettings_tx_power_tag 1
#define ChannelSettings_modem_config_tag 3
#define ChannelSettings_bandwidth_tag 6
#define ChannelSettings_spread_factor_tag 7
#define ChannelSettings_coding_rate_tag 8
#define ChannelSettings_channel_num_tag 9
#define ChannelSettings_psk_tag 4
#define ChannelSettings_name_tag 5
#define Data_typ_tag 1
@@ -436,7 +444,11 @@ X(a, STATIC, SINGULAR, BOOL, want_ack, 11)
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
X(a, STATIC, SINGULAR, BYTES, psk, 4) \
X(a, STATIC, SINGULAR, STRING, name, 5)
X(a, STATIC, SINGULAR, STRING, name, 5) \
X(a, STATIC, SINGULAR, UINT32, bandwidth, 6) \
X(a, STATIC, SINGULAR, UINT32, spread_factor, 7) \
X(a, STATIC, SINGULAR, UINT32, coding_rate, 8) \
X(a, STATIC, SINGULAR, UINT32, channel_num, 9)
#define ChannelSettings_CALLBACK NULL
#define ChannelSettings_DEFAULT NULL
@@ -597,12 +609,12 @@ extern const pb_msgdesc_t ManufacturingData_msg;
#define RouteDiscovery_size 88
#define SubPacket_size 274
#define MeshPacket_size 313
#define ChannelSettings_size 60
#define RadioConfig_size 253
#define ChannelSettings_size 84
#define RadioConfig_size 277
#define RadioConfig_UserPreferences_size 188
#define NodeInfo_size 132
#define MyNodeInfo_size 110
#define DeviceState_size 5403
#define DeviceState_size 5427
#define DebugString_size 258
#define FromRadio_size 322
#define ToRadio_size 316

View File

@@ -1,4 +1,6 @@
#pragma once
#include "concurrency/PeriodicTask.h"
#include "PowerStatus.h"
/**
* Per @spattinson
@@ -13,23 +15,26 @@
#define BAT_MILLIVOLTS_FULL 4100
#define BAT_MILLIVOLTS_EMPTY 3500
namespace meshtastic
class Power : public concurrency::PeriodicTask
{
/// Describes the state of the power system.
struct PowerStatus {
/// Whether we have a battery connected
bool haveBattery;
/// Battery voltage in mV, valid if haveBattery is true
int batteryVoltageMv;
/// Battery charge percentage, either read directly or estimated
int batteryChargePercent;
/// Whether USB is connected
bool usb;
/// Whether we are charging the battery
bool charging;
public:
Observable<const meshtastic::PowerStatus *> newStatus;
void readPowerStatus();
void loop();
virtual bool setup();
virtual void doTask();
void setStatusHandler(meshtastic::PowerStatus *handler)
{
statusHandler = handler;
}
protected:
meshtastic::PowerStatus *statusHandler;
virtual void axp192Init();
};
} // namespace meshtastic
extern meshtastic::PowerStatus powerStatus;
extern Power *power;

View File

@@ -5,7 +5,7 @@
#include "NodeDB.h"
#include "configuration.h"
#include "error.h"
#include "timing.h"
#include "main.h"
#include "target_specific.h"
@@ -123,11 +123,11 @@ bool doPreflightSleep()
/// Tell devices we are going to sleep and wait for them to handle things
static void waitEnterSleep()
{
uint32_t now = millis();
uint32_t now = timing::millis();
while (!doPreflightSleep()) {
delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep
if (timing::millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep
recordCriticalError(ErrSleepEnterWait);
assert(0); // FIXME - for now we just restart, need to fix bug #167
break;

10
src/timing.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "timing.h"
#include "freertosinc.h"
namespace timing {
uint32_t millis() {
return xTaskGetTickCount();
}
} // namespace timing

9
src/timing.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
namespace timing {
uint32_t millis();
} // namespace timing