mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-22 18:52:30 +00:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dec6af5dc | ||
|
|
11444621ae | ||
|
|
98f1b3296c | ||
|
|
26c43e7091 | ||
|
|
9b9447858a | ||
|
|
3151cfb064 | ||
|
|
c327fee986 | ||
|
|
a4f53270e8 | ||
|
|
a7456a1126 | ||
|
|
8381512ce4 | ||
|
|
20a669029b | ||
|
|
f2e6c6de58 | ||
|
|
8fa44c3590 | ||
|
|
f5b7c33d4e | ||
|
|
be8e663d39 | ||
|
|
0d4a9748e3 | ||
|
|
bd477f0fb2 | ||
|
|
5317895a5e | ||
|
|
542b8b26ce | ||
|
|
64da384fc1 | ||
|
|
a595fc4642 | ||
|
|
ac135be8cd | ||
|
|
5ea59a1c4d | ||
|
|
8bafd87b76 | ||
|
|
6b40e9a5e0 | ||
|
|
a2f6fd9298 | ||
|
|
bc604fc9ba | ||
|
|
9baaa13897 | ||
|
|
65e53be8b0 | ||
|
|
7f5283e95d | ||
|
|
ae4ab48ddc | ||
|
|
afccf1da02 | ||
|
|
fc07c7c01f | ||
|
|
aeb906414f | ||
|
|
a6c6b45576 | ||
|
|
13806cce93 | ||
|
|
e7eee0995a | ||
|
|
5687bd09c6 | ||
|
|
f3db895832 | ||
|
|
dd2ffe5d14 | ||
|
|
ec10e784e1 | ||
|
|
649faa1d93 | ||
|
|
669f96b367 | ||
|
|
996821d18e | ||
|
|
2e172b019e | ||
|
|
3df05cd5c1 | ||
|
|
8bb85cdc69 | ||
|
|
9f6e23754c | ||
|
|
5a7cfdffb3 | ||
|
|
caafddfdfa | ||
|
|
ee0e31be97 | ||
|
|
f1da6469a3 | ||
|
|
68e57dd3a7 | ||
|
|
2504311671 | ||
|
|
aa29315624 | ||
|
|
e3bcb87cf0 | ||
|
|
2530dc44c7 | ||
|
|
70a8fe30b7 | ||
|
|
30e538e5ed | ||
|
|
58dbc3c702 | ||
|
|
210c904604 | ||
|
|
27fdab7c8d | ||
|
|
ba3e1abb5e | ||
|
|
ce194e2162 | ||
|
|
595166db8e | ||
|
|
38d9e34a66 | ||
|
|
c1e0977db3 | ||
|
|
b4f32e7645 | ||
|
|
6bb0c95c95 | ||
|
|
4e958c9230 | ||
|
|
64cf1890f2 | ||
|
|
6a857b00db | ||
|
|
6d60a061bc | ||
|
|
6a09ddef18 | ||
|
|
d48e803b7b | ||
|
|
9061b3d8c3 | ||
|
|
2839539c6c | ||
|
|
f77a1798f3 | ||
|
|
664b558a36 | ||
|
|
c1865f127c | ||
|
|
3a69539192 | ||
|
|
cfcaf28ace | ||
|
|
d9e93f3944 | ||
|
|
60470211e5 | ||
|
|
c5851a4a0c | ||
|
|
0c0b2446b7 | ||
|
|
ce9352fd23 | ||
|
|
375ae5fe77 | ||
|
|
665d35196d | ||
|
|
9757f9ae53 | ||
|
|
e45d0c4dcf | ||
|
|
5c9f22bc18 | ||
|
|
d8287e9cdb | ||
|
|
c66e064f42 | ||
|
|
364fc84aaa | ||
|
|
fe4f86bc84 | ||
|
|
982b2e33ff | ||
|
|
8190098bb8 | ||
|
|
c99411311b | ||
|
|
da8a048dce | ||
|
|
b4de495154 |
38
.github/ISSUE_TEMPLATE/bug-report-or-feature-proposal.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug-report-or-feature-proposal.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report or feature proposal
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Please - if you just have a question (i.e. not a bug report or a feature proposal), post in our [forum](https://meshtastic.discourse.group/) instead.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Device info:**
|
||||
- Device model: [e.g. TBEAM]
|
||||
- Software Version [e.g. 0.7.8]
|
||||
|
||||
**Smartphone information (if relevant):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- App Version [e.g. 0.7.2]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
## Thank you for sending in a pull request, here's some tips to get started!
|
||||
|
||||
(Please delete all these tips and replace with your text)
|
||||
|
||||
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
|
||||
to say "hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
|
||||
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
|
||||
- Please do not check in files that don't have real changes
|
||||
- Please do not reformat lines that you didn't have to change the code on
|
||||
- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor,
|
||||
because automatically follows our indentation rules and it's auto reformatting will not cause spurious changes to lines.
|
||||
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
|
||||
- If your other co-developers have comments on your PR please tweak as needed.
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -59,6 +59,7 @@
|
||||
"cfsr",
|
||||
"descs",
|
||||
"ocrypto",
|
||||
"protobufs"
|
||||
"protobufs",
|
||||
"wifi"
|
||||
]
|
||||
}
|
||||
11
README.md
11
README.md
@@ -24,20 +24,23 @@ We currently support three models of radios.
|
||||
|
||||
- TTGO T-Beam
|
||||
|
||||
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended)
|
||||
- [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html)
|
||||
- [T-Beam V1.0 w/ NEO-6M - special Meshtastic version](https://www.aliexpress.com/item/4001178678568.html) (Includes built-in OLED display and they have **preinstalled** the meshtastic software)
|
||||
- [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (slightly better GPS)
|
||||
- 3D printable cases
|
||||
- [T-Beam V0](https://www.thingiverse.com/thing:3773717)
|
||||
- [T-Beam V1](https://www.thingiverse.com/thing:3830711)
|
||||
|
||||
- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS
|
||||
|
||||
- 3D printable case
|
||||
- [TTGO LORA32 v1](https://www.thingiverse.com/thing:3385109)
|
||||
|
||||
- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS
|
||||
- [3D Printable case](https://www.thingiverse.com/thing:3125854)
|
||||
|
||||
**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
|
||||
|
||||
@@ -47,6 +50,8 @@ Getting a version that includes a screen is optional, but highly recommended.
|
||||
|
||||
Prebuilt binaries for the supported radios are available in our [releases](https://github.com/meshtastic/Meshtastic-esp32/releases). Your initial installation has to happen over USB from your Mac, Windows or Linux PC. Once our software is installed, all future software updates happen over bluetooth from your phone.
|
||||
|
||||
Be **very careful** to install the correct load for your board. In particular the popular 'T-BEAM' radio from TTGO is not called 'TTGO-Lora' (that is a different board). So don't install the 'TTGO-Lora' build on a TBEAM, it won't work correctly.
|
||||
|
||||
Please post comments on our [group chat](https://meshtastic.discourse.group/) if you have problems or successes.
|
||||
|
||||
### Installing from a GUI - Windows and Mac
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
export VERSION=0.7.8
|
||||
export VERSION=0.7.10
|
||||
@@ -39,6 +39,7 @@ This software is 100% open source and developed by a group of hobbyist experimen
|
||||
|
||||
Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum.
|
||||
|
||||
- 06/24/2020 - 0.7.x Now with over 1000 android users, over 600 people using the radios and translated into 13 languages. Fairly stable and we are working through bugs to get to 1.0.
|
||||
- 06/04/2020 - 0.6.7 Beta releases of both the application and the device code are released. Features are fairly solid now with a sizable number of users.
|
||||
- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects.
|
||||
- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon.
|
||||
@@ -63,8 +64,9 @@ If you'd like to help with development, the source code is [on github](https://g
|
||||
|
||||
## Supported hardware
|
||||
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Make
|
||||
sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
||||
We currently support two brands of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4001178678568.html) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most people should buy the T-Beam and a 18650 battery (total cost less than \$35). Also, the version of the T-Beam we link to is shipped with Meshtastic **preinstalled** by TTGO, so you don't have to install it yourself.
|
||||
|
||||
Make sure to buy the frequency range which is legal for your country. For the USA, you should buy the 915MHz version. Getting a version that include a screen is optional, but highly recommended.
|
||||
|
||||
Instructions for installing prebuilt firmware can be found [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md).
|
||||
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
|
||||
You probably don't care about this section - skip to the next one.
|
||||
|
||||
- bluetooth toggle enable stress test, we are not properly restarting our connect
|
||||
- make new android release
|
||||
- check in our modified arduino binaries
|
||||
- post bug on esp32-arduino
|
||||
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
|
||||
- let users set arbitrary params in android
|
||||
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors (plan for geofence mode and battery save mode)
|
||||
- NRF52 BLE support
|
||||
|
||||
# Medium priority
|
||||
@@ -53,11 +48,17 @@ Items after the first final candidate release.
|
||||
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||
- 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)
|
||||
|
||||
Changes related to wifi support on ESP32:
|
||||
|
||||
- iram space: https://esp32.com/viewtopic.php?t=8460
|
||||
- set https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html spi ram bss
|
||||
- figure out if iram or bluetooth classic caused ble problems
|
||||
- post bug on esp32-arduino with BLE bug findings
|
||||
|
||||
# Spinoff project ideas
|
||||
|
||||
- an open source version of https://www.burnair.ch/skynet/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
We build our own custom version of esp32-arduino, in order to get some fixes we've made but haven't yet been merged in master.
|
||||
|
||||
These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because
|
||||
These are a set of currently unformatted notes on how to build and install them. Most developers should not care about this, because
|
||||
you'll automatically get our fixed libraries.
|
||||
|
||||
```
|
||||
@@ -12,5 +12,5 @@ you'll automatically get our fixed libraries.
|
||||
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/get-started/linux-setup.html
|
||||
kevinh@kevin-server:~/development/meshtastic/esp32-arduino-lib-builder\$ python /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/esp-idf/components/esptool*py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 /home/kevinh/development/meshtastic/esp32-arduino-lib-builder/build/bootloader/bootloader.bin
|
||||
cp -a out/tools/sdk/* components/arduino/tools/sdk
|
||||
cp -ar components/arduino/ ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
||||
cp -ar components/arduino/* ~/.platformio/packages/framework-arduinoespressif32@src-fba9d33740f719f712e9f8b07da6ea13/
|
||||
```
|
||||
|
||||
@@ -19,20 +19,21 @@ reliable messaging tasks (stage one for DSR):
|
||||
- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender
|
||||
- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service)
|
||||
- DONE test one hop ack/nak with the python framework
|
||||
- Do stress test with acks
|
||||
- DONE Do stress test with acks
|
||||
|
||||
dsr tasks
|
||||
|
||||
- oops I might have broken message reception
|
||||
- DONE oops I might have broken message reception
|
||||
- DONE Don't use broadcasts for the network pings (close open github issue)
|
||||
- DONE add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders
|
||||
- test multihop delivery with the python framework
|
||||
- DONE test multihop delivery with the python framework
|
||||
|
||||
optimizations / low priority:
|
||||
|
||||
- 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
|
||||
|
||||
18
linker/esp32.extram.bss.ld
Normal file
18
linker/esp32.extram.bss.ld
Normal file
@@ -0,0 +1,18 @@
|
||||
/* This section is only included if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
|
||||
is set, to link some sections to BSS in PSRAM */
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* external memory bss, from any global variable with EXT_RAM_ATTR attribute*/
|
||||
.ext_ram.bss (NOLOAD) :
|
||||
{
|
||||
_ext_ram_bss_start = ABSOLUTE(.);
|
||||
*(.ext_ram.bss*)
|
||||
*libnet80211.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||
*libpp.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||
*liblwip.a:(.dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||
*libbt.a:(EXCLUDE_FILE (libbtdm_app.a) .dynsbss .sbss .sbss.* .gnu.linkonce.sb.* .scommon .sbss2.* .gnu.linkonce.sb2.* .dynbss .bss .bss.* .share.mem .gnu.linkonce.b.* COMMON)
|
||||
. = ALIGN(4);
|
||||
_ext_ram_bss_end = ABSOLUTE(.);
|
||||
} > extern_ram_seg
|
||||
}
|
||||
@@ -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
|
||||
@@ -67,7 +69,7 @@ debug_tool = jlink
|
||||
lib_deps =
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||
SPI
|
||||
; 1260 ; OneButton - not used yet
|
||||
1260 ; OneButton library for non-blocking button debounce
|
||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||
https://github.com/meshtastic/arduino-fsm.git
|
||||
@@ -78,16 +80,17 @@ lib_deps =
|
||||
; 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
|
||||
debug_init_break = tbreak setup
|
||||
build_flags =
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32
|
||||
${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue
|
||||
# Hmm - this doesn't work yet
|
||||
# board_build.ldscript = linker/esp32.extram.bss.ld
|
||||
lib_ignore = segger_rtt
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git
|
||||
framework-arduinoespressif32 @ https://github.com/meshtastic/arduino-esp32.git#f26c4f96fefd13ed0ed042e27954f8aba6328f6b
|
||||
|
||||
; The 1.0 release of the TBEAM board
|
||||
[env:tbeam]
|
||||
@@ -96,6 +99,7 @@ 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
|
||||
|
||||
@@ -130,7 +134,6 @@ build_flags =
|
||||
; 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
|
||||
@@ -140,7 +143,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
2
proto
Submodule proto updated: 72cbde93ff...ab281311c4
108
src/GPSStatus.h
Normal file
108
src/GPSStatus.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
|
||||
namespace meshtastic {
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class GPSStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||
|
||||
bool hasLock = false; // default to false, until we complete our first read
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||
int32_t altitude = 0;
|
||||
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
||||
|
||||
public:
|
||||
|
||||
GPSStatus() {
|
||||
statusType = STATUS_TYPE_GPS;
|
||||
}
|
||||
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop ) : Status()
|
||||
{
|
||||
this->hasLock = hasLock;
|
||||
this->isConnected = isConnected;
|
||||
this->latitude = latitude;
|
||||
this->longitude = longitude;
|
||||
this->altitude = altitude;
|
||||
this->dop = dop;
|
||||
}
|
||||
GPSStatus(const GPSStatus &);
|
||||
GPSStatus &operator=(const GPSStatus &);
|
||||
|
||||
void observe(Observable<const GPSStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
}
|
||||
|
||||
bool getHasLock() const
|
||||
{
|
||||
return hasLock;
|
||||
}
|
||||
|
||||
bool getIsConnected() const
|
||||
{
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
int32_t getLatitude() const
|
||||
{
|
||||
return latitude;
|
||||
}
|
||||
|
||||
int32_t getLongitude() const
|
||||
{
|
||||
return longitude;
|
||||
}
|
||||
|
||||
int32_t getAltitude() const
|
||||
{
|
||||
return altitude;
|
||||
}
|
||||
|
||||
uint32_t getDOP() const
|
||||
{
|
||||
return dop;
|
||||
}
|
||||
|
||||
bool matches(const GPSStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->hasLock != hasLock ||
|
||||
newStatus->isConnected != isConnected ||
|
||||
newStatus->latitude != latitude ||
|
||||
newStatus->longitude != longitude ||
|
||||
newStatus->altitude != altitude ||
|
||||
newStatus->dop != dop
|
||||
);
|
||||
}
|
||||
int updateStatus(const GPSStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasLock = newStatus->hasLock;
|
||||
isConnected = newStatus->isConnected;
|
||||
latitude = newStatus->latitude;
|
||||
longitude = newStatus->longitude;
|
||||
altitude = newStatus->altitude;
|
||||
dop = newStatus->dop;
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern meshtastic::GPSStatus *gpsStatus;
|
||||
73
src/NodeStatus.h
Normal file
73
src/NodeStatus.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
|
||||
namespace meshtastic {
|
||||
|
||||
/// Describes the state of the NodeDB system.
|
||||
class NodeStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver = CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||
|
||||
uint8_t numOnline = 0;
|
||||
uint8_t numTotal = 0;
|
||||
|
||||
public:
|
||||
|
||||
NodeStatus() {
|
||||
statusType = STATUS_TYPE_NODE;
|
||||
}
|
||||
NodeStatus( uint8_t numOnline, uint8_t numTotal ) : Status()
|
||||
{
|
||||
this->numOnline = numOnline;
|
||||
this->numTotal = numTotal;
|
||||
}
|
||||
NodeStatus(const NodeStatus &);
|
||||
NodeStatus &operator=(const NodeStatus &);
|
||||
|
||||
void observe(Observable<const NodeStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
}
|
||||
|
||||
uint8_t getNumOnline() const
|
||||
{
|
||||
return numOnline;
|
||||
}
|
||||
|
||||
uint8_t getNumTotal() const
|
||||
{
|
||||
return numTotal;
|
||||
}
|
||||
|
||||
bool matches(const NodeStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->getNumOnline() != numOnline ||
|
||||
newStatus->getNumTotal() != numTotal
|
||||
);
|
||||
}
|
||||
int updateStatus(const NodeStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
numOnline = newStatus->getNumOnline();
|
||||
numTotal = newStatus->getNumTotal();
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("Node status update: %d online, %d total\n", numOnline, numTotal);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern meshtastic::NodeStatus *nodeStatus;
|
||||
180
src/Power.cpp
Normal file
180
src/Power.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "power.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "main.h"
|
||||
#include "utils.h"
|
||||
#include "sleep.h"
|
||||
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
|
||||
// FIXME. nasty hack cleanup how we load axp192
|
||||
#undef AXP192_SLAVE_ADDRESS
|
||||
#include "axp20x.h"
|
||||
AXP20X_Class axp;
|
||||
bool pmu_irq = false;
|
||||
|
||||
Power *power;
|
||||
|
||||
bool Power::setup()
|
||||
{
|
||||
|
||||
axp192Init();
|
||||
PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||
setPeriod(1);
|
||||
|
||||
return axp192_found;
|
||||
}
|
||||
|
||||
/// Reads power status to powerStatus singleton.
|
||||
//
|
||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||
void Power::readPowerStatus()
|
||||
{
|
||||
bool hasBattery = axp.isBatteryConnect();
|
||||
int batteryVoltageMv = 0;
|
||||
uint8_t batteryChargePercent = 0;
|
||||
if (hasBattery) {
|
||||
batteryVoltageMv = axp.getBattVoltage();
|
||||
// If the AXP192 returns a valid battery percentage, use it
|
||||
if (axp.getBattPercentage() >= 0) {
|
||||
batteryChargePercent = axp.getBattPercentage();
|
||||
} else {
|
||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
||||
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
||||
newStatus.notifyObservers(&powerStatus);
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
||||
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
}
|
||||
|
||||
void Power::doTask()
|
||||
{
|
||||
readPowerStatus();
|
||||
|
||||
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||
if(statusHandler && statusHandler->isInitialized())
|
||||
setPeriod(1000 * 20);
|
||||
}
|
||||
#endif // TBEAM_V10
|
||||
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
/**
|
||||
* Init the power manager chip
|
||||
*
|
||||
* axp192 power
|
||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
||||
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
||||
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||
*/
|
||||
void Power::axp192Init()
|
||||
{
|
||||
if (axp192_found) {
|
||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||
|
||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("----------------------------------------\n");
|
||||
|
||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||
axp.setDCDC1Voltage(3300); // for the OLED power
|
||||
|
||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
||||
#if 0
|
||||
|
||||
// Not connected
|
||||
//val = 0xfc;
|
||||
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
||||
|
||||
//not used
|
||||
//val = 0x46;
|
||||
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
||||
#endif
|
||||
axp.debugCharging();
|
||||
|
||||
#ifdef PMU_IRQ
|
||||
pinMode(PMU_IRQ, INPUT);
|
||||
attachInterrupt(
|
||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||
|
||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
||||
1);
|
||||
|
||||
axp.clearIRQ();
|
||||
#endif
|
||||
readPowerStatus();
|
||||
} else {
|
||||
DEBUG_MSG("AXP192 Begin FAIL\n");
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("AXP192 not found\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Power::loop()
|
||||
{
|
||||
|
||||
#ifdef PMU_IRQ
|
||||
if (pmu_irq) {
|
||||
pmu_irq = false;
|
||||
axp.readIRQ();
|
||||
|
||||
DEBUG_MSG("pmu irq!\n");
|
||||
|
||||
if (axp.isChargingIRQ()) {
|
||||
DEBUG_MSG("Battery start charging\n");
|
||||
}
|
||||
if (axp.isChargingDoneIRQ()) {
|
||||
DEBUG_MSG("Battery fully charged\n");
|
||||
}
|
||||
if (axp.isVbusRemoveIRQ()) {
|
||||
DEBUG_MSG("USB unplugged\n");
|
||||
}
|
||||
if (axp.isVbusPlugInIRQ()) {
|
||||
DEBUG_MSG("USB plugged In\n");
|
||||
}
|
||||
if (axp.isBattPlugInIRQ()) {
|
||||
DEBUG_MSG("Battery inserted\n");
|
||||
}
|
||||
if (axp.isBattRemoveIRQ()) {
|
||||
DEBUG_MSG("Battery removed\n");
|
||||
}
|
||||
if (axp.isPEKShortPressIRQ()) {
|
||||
DEBUG_MSG("PEK short button press\n");
|
||||
}
|
||||
|
||||
readPowerStatus();
|
||||
axp.clearIRQ();
|
||||
}
|
||||
|
||||
#endif // T_BEAM_V10
|
||||
|
||||
}
|
||||
103
src/PowerStatus.h
Normal file
103
src/PowerStatus.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include "Status.h"
|
||||
#include "configuration.h"
|
||||
|
||||
namespace meshtastic {
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
class PowerStatus : public Status
|
||||
{
|
||||
|
||||
private:
|
||||
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver = CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||
|
||||
/// Whether we have a battery connected
|
||||
bool hasBattery;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv;
|
||||
/// Battery charge percentage, either read directly or estimated
|
||||
uint8_t batteryChargePercent;
|
||||
/// Whether USB is connected
|
||||
bool hasUSB;
|
||||
/// Whether we are charging the battery
|
||||
bool isCharging;
|
||||
|
||||
public:
|
||||
|
||||
PowerStatus() {
|
||||
statusType = STATUS_TYPE_POWER;
|
||||
}
|
||||
PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
|
||||
{
|
||||
this->hasBattery = hasBattery;
|
||||
this->hasUSB = hasUSB;
|
||||
this->isCharging = isCharging;
|
||||
this->batteryVoltageMv = batteryVoltageMv;
|
||||
this->batteryChargePercent = batteryChargePercent;
|
||||
}
|
||||
PowerStatus(const PowerStatus &);
|
||||
PowerStatus &operator=(const PowerStatus &);
|
||||
|
||||
void observe(Observable<const PowerStatus *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
}
|
||||
|
||||
bool getHasBattery() const
|
||||
{
|
||||
return hasBattery;
|
||||
}
|
||||
|
||||
bool getHasUSB() const
|
||||
{
|
||||
return hasUSB;
|
||||
}
|
||||
|
||||
bool getIsCharging() const
|
||||
{
|
||||
return isCharging;
|
||||
}
|
||||
|
||||
int getBatteryVoltageMv() const
|
||||
{
|
||||
return batteryVoltageMv;
|
||||
}
|
||||
|
||||
uint8_t getBatteryChargePercent() const
|
||||
{
|
||||
return batteryChargePercent;
|
||||
}
|
||||
|
||||
bool matches(const PowerStatus *newStatus) const
|
||||
{
|
||||
return (
|
||||
newStatus->getHasBattery() != hasBattery ||
|
||||
newStatus->getHasUSB() != hasUSB ||
|
||||
newStatus->getBatteryVoltageMv() != batteryVoltageMv
|
||||
);
|
||||
}
|
||||
int updateStatus(const PowerStatus *newStatus) {
|
||||
// Only update the status if values have actually changed
|
||||
bool isDirty;
|
||||
{
|
||||
isDirty = matches(newStatus);
|
||||
initialized = true;
|
||||
hasBattery = newStatus->getHasBattery();
|
||||
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||
hasUSB = newStatus->getHasUSB();
|
||||
isCharging = newStatus->getIsCharging();
|
||||
}
|
||||
if(isDirty) {
|
||||
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern meshtastic::PowerStatus *powerStatus;
|
||||
72
src/Status.h
Normal file
72
src/Status.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
// Constants for the various status types, so we can tell subclass instances apart
|
||||
#define STATUS_TYPE_BASE 0
|
||||
#define STATUS_TYPE_POWER 1
|
||||
#define STATUS_TYPE_GPS 2
|
||||
#define STATUS_TYPE_NODE 3
|
||||
|
||||
|
||||
namespace meshtastic
|
||||
{
|
||||
|
||||
// A base class for observable status
|
||||
class Status
|
||||
{
|
||||
protected:
|
||||
// Allows us to observe an Observable
|
||||
CallbackObserver<Status, const Status *> statusObserver = CallbackObserver<Status, const Status *>(this, &Status::updateStatus);
|
||||
bool initialized = false;
|
||||
// Workaround for no typeid support
|
||||
int statusType;
|
||||
|
||||
public:
|
||||
// Allows us to generate observable events
|
||||
Observable<const Status *> onNewStatus;
|
||||
|
||||
// Enable polymorphism ?
|
||||
virtual ~Status() = default;
|
||||
|
||||
Status() {
|
||||
if (!statusType)
|
||||
{
|
||||
statusType = STATUS_TYPE_BASE;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent object copy/move
|
||||
Status(const Status &) = delete;
|
||||
Status &operator=(const Status &) = delete;
|
||||
|
||||
// Start observing a source of data
|
||||
void observe(Observable<const Status *> *source)
|
||||
{
|
||||
statusObserver.observe(source);
|
||||
}
|
||||
|
||||
// Determines whether or not existing data matches the data in another Status instance
|
||||
bool matches(const Status *otherStatus) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isInitialized() const
|
||||
{
|
||||
return initialized;
|
||||
}
|
||||
|
||||
int getStatusType() const
|
||||
{
|
||||
return statusType;
|
||||
}
|
||||
|
||||
// Called when the Observable we're observing generates a new notification
|
||||
int updateStatus(const Status *newStatus)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
0
src/StatusHandler.h
Normal file
0
src/StatusHandler.h
Normal file
@@ -17,8 +17,15 @@ void Thread::callRun(void *_this)
|
||||
|
||||
void WorkerThread::doRun()
|
||||
{
|
||||
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;
|
||||
@@ -30,6 +37,8 @@ void WorkerThread::doRun()
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
stopWatchdog();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include "esp_task_wdt.h"
|
||||
#include "freertosinc.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef HAS_FREE_RTOS
|
||||
|
||||
@@ -26,6 +27,23 @@ class Thread
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -144,7 +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
|
||||
#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
|
||||
@@ -213,6 +214,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#elif defined(TTGO_LORA_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"
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_TX_PIN 37
|
||||
|
||||
#define I2C_SDA 4 // I2C pins for this board
|
||||
#define I2C_SCL 15
|
||||
@@ -231,6 +236,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR "ttgo-lora32-v2"
|
||||
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_TX_PIN 13 // per @eugene
|
||||
|
||||
#define I2C_SDA 21 // I2C pins for this board
|
||||
#define I2C_SCL 22
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ class DataCharacteristic : public CallbackCharacteristic
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); // Not exactly correct, but we want to force the device to not sleep now
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,8 +124,10 @@ class CRC32Characteristic : public CallbackCharacteristic
|
||||
|
||||
void bluetoothRebootCheck()
|
||||
{
|
||||
if (rebootAtMsec && millis() > rebootAtMsec)
|
||||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||
DEBUG_MSG("Rebooting for update\n");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -122,7 +122,7 @@ BLEService *createBatteryService(BLEServer *server)
|
||||
addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100");
|
||||
batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification
|
||||
|
||||
// I don't think we need to advertise this
|
||||
// I don't think we need to advertise this? and some phones only see the first thing advertised anyways...
|
||||
// server->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||
pBattery->start();
|
||||
|
||||
@@ -135,8 +135,8 @@ BLEService *createBatteryService(BLEServer *server)
|
||||
*/
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
// Pretend to update battery levels - fixme do elsewhere
|
||||
if (batteryLevelC) {
|
||||
DEBUG_MSG("set BLE battery level %u\n", level);
|
||||
batteryLevelC->setValue(&level, 1);
|
||||
batteryLevelC->notify();
|
||||
}
|
||||
@@ -215,7 +215,7 @@ class MySecurity : public BLESecurityCallbacks
|
||||
|
||||
BLEServer *pServer;
|
||||
|
||||
BLEService *pDevInfo, *pUpdate;
|
||||
BLEService *pDevInfo, *pUpdate, *pBattery;
|
||||
|
||||
void deinitBLE()
|
||||
{
|
||||
@@ -230,6 +230,9 @@ void deinitBLE()
|
||||
pUpdate->executeDelete();
|
||||
}
|
||||
|
||||
pBattery->stop();
|
||||
pBattery->executeDelete();
|
||||
|
||||
pDevInfo->stop();
|
||||
pDevInfo->executeDelete();
|
||||
|
||||
@@ -242,6 +245,7 @@ void deinitBLE()
|
||||
if (pUpdate != NULL)
|
||||
delete pUpdate;
|
||||
delete pDevInfo;
|
||||
delete pBattery;
|
||||
delete pServer;
|
||||
|
||||
batteryLevelC = NULL; // Don't let anyone generate bogus notifies
|
||||
@@ -279,10 +283,9 @@ BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoo
|
||||
|
||||
pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion);
|
||||
|
||||
// We now let users create the battery service only if they really want (not all devices have a battery)
|
||||
// BLEService *pBattery = createBatteryService(pServer);
|
||||
pBattery = createBatteryService(pServer);
|
||||
|
||||
// #define BLE_SOFTWARE_UPDATE
|
||||
#define BLE_SOFTWARE_UPDATE
|
||||
#ifdef BLE_SOFTWARE_UPDATE
|
||||
pUpdate = createUpdateService(pServer, hwVendor, swVersion,
|
||||
hwVersion); // We need to advertise this so our android ble scan operation can see it
|
||||
|
||||
@@ -35,5 +35,8 @@ BLECharacteristic *addBLECharacteristic(BLECharacteristic *c);
|
||||
/// Add a characteristic that we will delete when we restart
|
||||
BLEDescriptor *addBLEDescriptor(BLEDescriptor *c);
|
||||
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level);
|
||||
|
||||
/// Any bluetooth objects you allocate _must_ come from this pool if you want to be able to call deinitBLE()
|
||||
extern SimpleAllocator btPool;
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
#include "MeshBluetoothService.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
bool bluetoothOn;
|
||||
|
||||
@@ -59,102 +60,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();
|
||||
}
|
||||
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();
|
||||
@@ -180,9 +85,15 @@ void esp32Setup()
|
||||
|
||||
// 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
|
||||
|
||||
auto res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||
assert(res == ESP_OK);
|
||||
|
||||
res = esp_task_wdt_add(NULL);
|
||||
assert(res == ESP_OK);
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -205,60 +116,12 @@ uint32_t axpDebugRead()
|
||||
Periodic axpDebugOutput(axpDebugRead);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Per @spattinson
|
||||
* MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other
|
||||
* charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph
|
||||
* in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html)
|
||||
* looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks
|
||||
* to be a shade above 3.2V
|
||||
*/
|
||||
#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
|
||||
|
||||
/// loop code specific to ESP32 targets
|
||||
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
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Observer.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "PeriodicTask.h"
|
||||
#include "sys/time.h"
|
||||
|
||||
@@ -8,6 +9,9 @@
|
||||
void perhapsSetRTC(const struct timeval *tv);
|
||||
void perhapsSetRTC(struct tm &t);
|
||||
|
||||
// Generate a string representation of DOP
|
||||
const char *getDOPString(uint32_t dop);
|
||||
|
||||
/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero
|
||||
uint32_t getTime();
|
||||
|
||||
@@ -31,10 +35,14 @@ class GPS : public Observable<void *>
|
||||
public:
|
||||
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)
|
||||
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
|
||||
virtual ~GPS() {}
|
||||
|
||||
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||
|
||||
/**
|
||||
* Returns true if we succeeded
|
||||
*/
|
||||
|
||||
@@ -53,13 +53,21 @@ void NEMAGPS::loop()
|
||||
latitude = toDegInt(loc.lat);
|
||||
longitude = toDegInt(loc.lng);
|
||||
}
|
||||
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
|
||||
if(reader.hdop.isValid()) {
|
||||
dop = reader.hdop.value();
|
||||
}
|
||||
|
||||
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
|
||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
|
||||
DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d, hdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
||||
|
||||
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||
if (hasValidLocation)
|
||||
notifyObservers(NULL);
|
||||
}
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
}
|
||||
@@ -99,12 +99,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
||||
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||
*/
|
||||
struct tm t;
|
||||
t.tm_sec = ublox.getSecond();
|
||||
t.tm_min = ublox.getMinute();
|
||||
t.tm_hour = ublox.getHour();
|
||||
t.tm_mday = ublox.getDay();
|
||||
t.tm_mon = ublox.getMonth() - 1;
|
||||
t.tm_year = ublox.getYear() - 1900;
|
||||
t.tm_sec = ublox.getSecond(0);
|
||||
t.tm_min = ublox.getMinute(0);
|
||||
t.tm_hour = ublox.getHour(0);
|
||||
t.tm_mday = ublox.getDay(0);
|
||||
t.tm_mon = ublox.getMonth(0) - 1;
|
||||
t.tm_year = ublox.getYear(0) - 1900;
|
||||
t.tm_isdst = false;
|
||||
perhapsSetRTC(t);
|
||||
}
|
||||
@@ -112,10 +112,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP(0)) // rd fixes only
|
||||
{
|
||||
// we only notify if position has changed
|
||||
latitude = ublox.getLatitude();
|
||||
longitude = ublox.getLongitude();
|
||||
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
|
||||
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude);
|
||||
latitude = ublox.getLatitude(0);
|
||||
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
|
||||
|
||||
// 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!
|
||||
@@ -128,6 +128,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||
wantNewLocation = true;
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||
newStatus.notifyObservers(&status);
|
||||
|
||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
||||
// the serial
|
||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||
|
||||
@@ -4,7 +4,12 @@ const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0
|
||||
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
|
||||
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
|
||||
|
||||
const
|
||||
const uint8_t imgUSB[] PROGMEM = { 0x60, 0x60, 0x30, 0x18, 0x18, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x24, 0x24, 0x24, 0x3C };
|
||||
const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08, 0x08, 0x08, 0x1C, 0x22, 0x22, 0x41, 0x7F, 0x22, 0x22, 0x22 };
|
||||
const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C };
|
||||
const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF };
|
||||
const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF };
|
||||
|
||||
#include "icon.xbm"
|
||||
|
||||
// We now programmatically draw our compass
|
||||
|
||||
105
src/main.cpp
105
src/main.cpp
@@ -37,11 +37,13 @@
|
||||
#include "main.h"
|
||||
#include "screen.h"
|
||||
#include "sleep.h"
|
||||
#include <OneButton.h>
|
||||
#include <Wire.h>
|
||||
// #include <driver/rtc_io.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "BluetoothUtil.h"
|
||||
#include "WiFi.h"
|
||||
#endif
|
||||
|
||||
#include "RF95Interface.h"
|
||||
@@ -54,8 +56,14 @@
|
||||
// We always create a screen object, but we only init it if we find the hardware
|
||||
meshtastic::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;
|
||||
@@ -119,12 +127,49 @@ 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);
|
||||
|
||||
// Prepare for button presses
|
||||
#ifdef BUTTON_PIN
|
||||
OneButton userButton;
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
OneButton userButtonAlt;
|
||||
#endif
|
||||
void userButtonPressed()
|
||||
{
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
void userButtonPressedLong()
|
||||
{
|
||||
screen.adjustBrightness();
|
||||
}
|
||||
|
||||
#ifndef NO_ESP32
|
||||
void initWifi()
|
||||
{
|
||||
strcpy(radioConfig.preferences.wifi_ssid, "geeksville");
|
||||
strcpy(radioConfig.preferences.wifi_password, "xxx");
|
||||
if (radioConfig.has_preferences) {
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
|
||||
if (*wifiName) {
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
if (radioConfig.preferences.wifi_ap_mode) {
|
||||
// DEBUG_MSG("STARTING WIFI AP: ssid=%s, ok=%d\n", wifiName, WiFi.softAP(wifiName, wifiPsw));
|
||||
} else {
|
||||
// WiFi.mode(WIFI_MODE_STA);
|
||||
DEBUG_MSG("JOINING WIFI: ssid=%s\n", wifiName);
|
||||
// WiFi.begin(wifiName, wifiPsw);
|
||||
}
|
||||
}
|
||||
} else
|
||||
DEBUG_MSG("Not using WIFI\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
@@ -161,8 +206,14 @@ void setup()
|
||||
|
||||
// Buttons & LED
|
||||
#ifdef BUTTON_PIN
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
digitalWrite(BUTTON_PIN, 1);
|
||||
userButton = OneButton(BUTTON_PIN, true, true);
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
|
||||
userButtonAlt.attachClick(userButtonPressed);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
#endif
|
||||
#ifdef LED_PIN
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
@@ -182,6 +233,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
|
||||
@@ -210,8 +269,14 @@ 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();
|
||||
#endif
|
||||
|
||||
#ifdef SX1262_ANT_SW
|
||||
// make analog PA vs not PA switch on SX1262 eval board work properly
|
||||
@@ -293,26 +358,15 @@ void loop()
|
||||
#ifndef NO_ESP32
|
||||
esp32Loop();
|
||||
#endif
|
||||
#ifdef TBEAM_V10
|
||||
power->loop();
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of
|
||||
// this boilerplate)
|
||||
static bool wasPressed = false;
|
||||
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
if (!wasPressed) { // just started a new press
|
||||
DEBUG_MSG("pressing\n");
|
||||
|
||||
// doLightSleep();
|
||||
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
||||
wasPressed = true;
|
||||
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
}
|
||||
} else if (wasPressed) {
|
||||
// we just did a release
|
||||
wasPressed = false;
|
||||
}
|
||||
userButton.tick();
|
||||
#endif
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
userButtonAlt.tick();
|
||||
#endif
|
||||
|
||||
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||
@@ -331,11 +385,8 @@ void loop()
|
||||
#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);
|
||||
// TODO(#4): use something based on hdop to show GPS "signal" strength.
|
||||
screen.debug()->setGPSStatus(gps->isConnected ? (gps->hasLock() ? "GPS ok" : "No Sats") : "No GPS");
|
||||
// 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.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "screen.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "NodeStatus.h"
|
||||
|
||||
extern bool axp192_found;
|
||||
extern bool ssd1306_found;
|
||||
@@ -9,6 +12,11 @@ extern bool isUSBPowered;
|
||||
|
||||
// Global Screen singleton.
|
||||
extern meshtastic::Screen screen;
|
||||
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||
|
||||
//extern meshtastic::PowerStatus *powerStatus;
|
||||
//extern meshtastic::GPSStatus *gpsStatus;
|
||||
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||
|
||||
// Return a human readable string of the form "Meshtastic_ab13"
|
||||
const char *getDeviceName();
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#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
|
||||
|
||||
/*
|
||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||
@@ -280,6 +282,8 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies)
|
||||
sendToMesh(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int MeshService::onGPSChanged(void *unused)
|
||||
{
|
||||
// DEBUG_MSG("got gps notify\n");
|
||||
@@ -298,6 +302,10 @@ int MeshService::onGPSChanged(void *unused)
|
||||
pos.time = getValidTime();
|
||||
}
|
||||
|
||||
// Include our current battery voltage in our position announcement
|
||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(pos.battery_level);
|
||||
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
static uint32_t lastGpsSend;
|
||||
uint32_t now = millis();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
NodeDB nodeDB;
|
||||
|
||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||
DeviceState devicestate;
|
||||
EXT_RAM_ATTR DeviceState devicestate;
|
||||
MyNodeInfo &myNodeInfo = devicestate.my_node;
|
||||
RadioConfig &radioConfig = devicestate.radio;
|
||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||
@@ -103,13 +103,16 @@ void NodeDB::resetRadioConfig()
|
||||
crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes);
|
||||
|
||||
// temp hack for quicker testing
|
||||
// devicestate.no_save = true;
|
||||
if (devicestate.no_save) {
|
||||
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE *****\n");
|
||||
|
||||
/*
|
||||
radioConfig.preferences.screen_on_secs = 30;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||
radioConfig.preferences.ls_secs = 60;
|
||||
*/
|
||||
// Sleep quite frequently to stress test the BLE comms, broadcast position every 6 mins
|
||||
radioConfig.preferences.screen_on_secs = 30;
|
||||
radioConfig.preferences.wait_bluetooth_secs = 30;
|
||||
radioConfig.preferences.position_broadcast_secs = 6 * 60;
|
||||
radioConfig.preferences.ls_secs = 60;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeDB::installDefaultDeviceState()
|
||||
@@ -257,32 +260,36 @@ void NodeDB::loadFromDisk()
|
||||
void NodeDB::saveToDisk()
|
||||
{
|
||||
#ifdef FS
|
||||
auto f = FS.open(preftmp, FILE_O_WRITE);
|
||||
if (f) {
|
||||
DEBUG_MSG("Writing preferences\n");
|
||||
if (!devicestate.no_save) {
|
||||
auto f = FS.open(preftmp, FILE_O_WRITE);
|
||||
if (f) {
|
||||
DEBUG_MSG("Writing preferences\n");
|
||||
|
||||
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
||||
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
||||
|
||||
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||
|
||||
devicestate.version = DEVICESTATE_CUR_VER;
|
||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||
// FIXME - report failure to phone
|
||||
devicestate.version = DEVICESTATE_CUR_VER;
|
||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||
// FIXME - report failure to phone
|
||||
|
||||
f.close();
|
||||
f.close();
|
||||
} else {
|
||||
// Success - replace the old file
|
||||
f.close();
|
||||
|
||||
// brief window of risk here ;-)
|
||||
if (!FS.remove(preffile))
|
||||
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
||||
if (!FS.rename(preftmp, preffile))
|
||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||
}
|
||||
} else {
|
||||
// Success - replace the old file
|
||||
f.close();
|
||||
|
||||
// brief window of risk here ;-)
|
||||
if (!FS.remove(preffile))
|
||||
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
||||
if (!FS.rename(preftmp, preffile))
|
||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
||||
DEBUG_MSG("***** DEVELOPMENT MODE - DO NOT RELEASE - not saving to flash *****\n");
|
||||
}
|
||||
#else
|
||||
DEBUG_MSG("ERROR filesystem not implemented\n");
|
||||
@@ -335,8 +342,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
int oldNumNodes = *numNodes;
|
||||
NodeInfo *info = getOrCreateNode(mp.from);
|
||||
|
||||
if (oldNumNodes != *numNodes)
|
||||
updateGUI = true; // we just created a nodeinfo
|
||||
notifyObservers();
|
||||
|
||||
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
|
||||
|
||||
@@ -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() {
|
||||
// Notify observers of the current node state
|
||||
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes());
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
|
||||
/// read our db from flash
|
||||
void loadFromDisk();
|
||||
|
||||
|
||||
@@ -101,8 +101,9 @@ typedef struct _RadioConfig_UserPreferences {
|
||||
uint32_t sds_secs;
|
||||
uint32_t ls_secs;
|
||||
uint32_t min_wake_secs;
|
||||
bool keep_all_packets;
|
||||
bool promiscuous_mode;
|
||||
char wifi_ssid[33];
|
||||
char wifi_password[64];
|
||||
bool wifi_ap_mode;
|
||||
pb_size_t ignore_incoming_count;
|
||||
uint32_t ignore_incoming[3];
|
||||
} RadioConfig_UserPreferences;
|
||||
@@ -187,6 +188,7 @@ typedef struct _DeviceState {
|
||||
bool has_rx_text_message;
|
||||
MeshPacket rx_text_message;
|
||||
uint32_t version;
|
||||
bool no_save;
|
||||
} DeviceState;
|
||||
|
||||
typedef struct _FromRadio {
|
||||
@@ -241,10 +243,10 @@ typedef struct _ToRadio {
|
||||
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {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, 0}}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}}
|
||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0}
|
||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default}, false, MeshPacket_init_default, 0, 0}
|
||||
#define DebugString_init_default {""}
|
||||
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||
@@ -257,10 +259,10 @@ typedef struct _ToRadio {
|
||||
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {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, 0}}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, 0, {0, 0, 0}}
|
||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
|
||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero}, false, MeshPacket_init_zero, 0, 0}
|
||||
#define DebugString_init_zero {""}
|
||||
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||
@@ -308,8 +310,9 @@ typedef struct _ToRadio {
|
||||
#define RadioConfig_UserPreferences_sds_secs_tag 9
|
||||
#define RadioConfig_UserPreferences_ls_secs_tag 10
|
||||
#define RadioConfig_UserPreferences_min_wake_secs_tag 11
|
||||
#define RadioConfig_UserPreferences_keep_all_packets_tag 100
|
||||
#define RadioConfig_UserPreferences_promiscuous_mode_tag 101
|
||||
#define RadioConfig_UserPreferences_wifi_ssid_tag 12
|
||||
#define RadioConfig_UserPreferences_wifi_password_tag 13
|
||||
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
|
||||
#define RadioConfig_UserPreferences_ignore_incoming_tag 102
|
||||
#define RouteDiscovery_route_tag 2
|
||||
#define User_id_tag 1
|
||||
@@ -351,6 +354,7 @@ typedef struct _ToRadio {
|
||||
#define DeviceState_receive_queue_tag 5
|
||||
#define DeviceState_version_tag 8
|
||||
#define DeviceState_rx_text_message_tag 7
|
||||
#define DeviceState_no_save_tag 9
|
||||
#define FromRadio_packet_tag 2
|
||||
#define FromRadio_my_info_tag 3
|
||||
#define FromRadio_node_info_tag 4
|
||||
@@ -456,8 +460,9 @@ X(a, STATIC, SINGULAR, UINT32, mesh_sds_timeout_secs, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, sds_secs, 9) \
|
||||
X(a, STATIC, SINGULAR, UINT32, ls_secs, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, min_wake_secs, 11) \
|
||||
X(a, STATIC, SINGULAR, BOOL, keep_all_packets, 100) \
|
||||
X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) \
|
||||
X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \
|
||||
X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
|
||||
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 102)
|
||||
#define RadioConfig_UserPreferences_CALLBACK NULL
|
||||
#define RadioConfig_UserPreferences_DEFAULT NULL
|
||||
@@ -498,7 +503,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, owner, 3) \
|
||||
X(a, STATIC, REPEATED, MESSAGE, node_db, 4) \
|
||||
X(a, STATIC, REPEATED, MESSAGE, receive_queue, 5) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, rx_text_message, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, version, 8)
|
||||
X(a, STATIC, SINGULAR, UINT32, version, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, no_save, 9)
|
||||
#define DeviceState_CALLBACK NULL
|
||||
#define DeviceState_DEFAULT NULL
|
||||
#define DeviceState_radio_MSGTYPE RadioConfig
|
||||
@@ -592,11 +598,11 @@ extern const pb_msgdesc_t ManufacturingData_msg;
|
||||
#define SubPacket_size 274
|
||||
#define MeshPacket_size 313
|
||||
#define ChannelSettings_size 60
|
||||
#define RadioConfig_size 157
|
||||
#define RadioConfig_UserPreferences_size 93
|
||||
#define RadioConfig_size 253
|
||||
#define RadioConfig_UserPreferences_size 188
|
||||
#define NodeInfo_size 132
|
||||
#define MyNodeInfo_size 110
|
||||
#define DeviceState_size 5305
|
||||
#define DeviceState_size 5403
|
||||
#define DebugString_size 258
|
||||
#define FromRadio_size 322
|
||||
#define ToRadio_size 316
|
||||
|
||||
48
src/power.h
48
src/power.h
@@ -1,20 +1,40 @@
|
||||
#pragma once
|
||||
#include "PeriodicTask.h"
|
||||
#include "PowerStatus.h"
|
||||
|
||||
namespace meshtastic
|
||||
/**
|
||||
* Per @spattinson
|
||||
* MIN_BAT_MILLIVOLTS seems high. Typical 18650 are different chemistry to LiPo, even for LiPos that chart seems a bit off, other
|
||||
* charts put 3690mV at about 30% for a lipo, for 18650 i think 10% remaining iis in the region of 3.2-3.3V. Reference 1st graph
|
||||
* in [this test report](https://lygte-info.dk/review/batteries2012/Samsung%20INR18650-30Q%203000mAh%20%28Pink%29%20UK.html)
|
||||
* looking at the red line - discharge at 0.2A - he gets a capacity of 2900mah, 90% of 2900 = 2610, that point in the graph looks
|
||||
* to be a shade above 3.2V
|
||||
*/
|
||||
#define MIN_BAT_MILLIVOLTS 3250 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/
|
||||
|
||||
#define BAT_MILLIVOLTS_FULL 4100
|
||||
#define BAT_MILLIVOLTS_EMPTY 3500
|
||||
|
||||
class Power : public PeriodicTask
|
||||
{
|
||||
|
||||
/// Describes the state of the power system.
|
||||
struct PowerStatus {
|
||||
/// Whether we have a battery connected
|
||||
bool haveBattery;
|
||||
/// Battery voltage in mV, valid if haveBattery is true
|
||||
int batteryVoltageMv;
|
||||
/// 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;
|
||||
311
src/screen.cpp
311
src/screen.cpp
@@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "screen.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
||||
@@ -41,7 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endif
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define TRANSITION_FRAMERATE 30 // fps
|
||||
#define IDLE_FRAMERATE 10 // in fps
|
||||
#define IDLE_FRAMERATE 1 // in fps
|
||||
#define COMPASS_DIAM 44
|
||||
|
||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||
@@ -54,6 +55,14 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||
static char btPIN[16] = "888888";
|
||||
|
||||
uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
|
||||
|
||||
// if defined a pixel will blink to show redraws
|
||||
// #define SHOW_REDRAWS
|
||||
#ifdef SHOW_REDRAWS
|
||||
static bool heartbeat = false;
|
||||
#endif
|
||||
|
||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// draw an xbm image.
|
||||
@@ -64,20 +73,34 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
const char *region = xstr(HW_VERSION);
|
||||
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
||||
region += 4;
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%s",
|
||||
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
||||
}
|
||||
|
||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(ArialMT_Plain_16);
|
||||
display->drawString(64 + x, 2 + y, "Bluetooth");
|
||||
display->drawString(64 + x, y, "Bluetooth");
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT + y, "Enter this code");
|
||||
display->drawString(64 + x, FONT_HEIGHT + y + 2, "Enter this code");
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(ArialMT_Plain_24);
|
||||
display->drawString(64 + x, 22 + y, btPIN);
|
||||
display->drawString(64 + x, 26 + y, btPIN);
|
||||
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
char buf[30];
|
||||
const char *name = "Name: ";
|
||||
strcpy(buf, name);
|
||||
strcat(buf, getDeviceName());
|
||||
display->drawString(64 + x, 48 + y, buf);
|
||||
}
|
||||
|
||||
/// Draw the last text message we received
|
||||
@@ -126,34 +149,105 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
||||
/// @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, const PowerStatus *powerStatus)
|
||||
{
|
||||
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
|
||||
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
|
||||
// Clear the bar area on the battery image
|
||||
for (int i = 1; i < 14; i++) {
|
||||
imgBuffer[i] = 0x81;
|
||||
}
|
||||
// If charging, draw a charging indicator
|
||||
if (powerStatus->getIsCharging()) {
|
||||
memcpy(imgBuffer + 3, lightning, 8);
|
||||
// If not charging, Draw power bars
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (powerStatus->getBatteryChargePercent() >= 25 * i)
|
||||
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
|
||||
}
|
||||
}
|
||||
display->drawFastImage(x, y, 16, 8, imgBuffer);
|
||||
}
|
||||
|
||||
// Draw nodes status
|
||||
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *nodeStatus)
|
||||
{
|
||||
char usersString[20];
|
||||
sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
|
||||
display->drawFastImage(x, y, 8, 8, imgUser);
|
||||
display->drawString(x + 10, y - 2, usersString);
|
||||
}
|
||||
|
||||
// Draw GPS status summary
|
||||
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
if (!gps->getIsConnected()) {
|
||||
display->drawString(x, y - 2, "No GPS");
|
||||
return;
|
||||
}
|
||||
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
|
||||
if (!gps->getHasLock()) {
|
||||
display->drawString(x + 8, y - 2, "No sats");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() <= 100) {
|
||||
display->drawString(x + 8, y - 2, "Ideal");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() <= 200) {
|
||||
display->drawString(x + 8, y - 2, "Exc.");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() <= 500) {
|
||||
display->drawString(x + 8, y - 2, "Good");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() <= 1000) {
|
||||
display->drawString(x + 8, y - 2, "Mod.");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() <= 2000) {
|
||||
display->drawString(x + 8, y - 2, "Fair");
|
||||
return;
|
||||
}
|
||||
if (gps->getDOP() > 0) {
|
||||
display->drawString(x + 8, y - 2, "Poor");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ported from my old java code, returns distance in meters along the globe
|
||||
@@ -295,6 +389,30 @@ 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)
|
||||
{
|
||||
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
||||
// (const uint8_t *)compass_bits);
|
||||
|
||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
||||
|
||||
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
points[i]->rotate(headingRadian);
|
||||
points[i]->scale(COMPASS_DIAM * 0.6);
|
||||
points[i]->translate(compassX, compassY);
|
||||
}
|
||||
drawLine(display, tip, tail);
|
||||
drawLine(display, leftArrow, tip);
|
||||
drawLine(display, rightArrow, tip);
|
||||
|
||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||
}
|
||||
|
||||
/// Convert an integer GPS coords to a floating point
|
||||
#define DegD(i) (i * 1e-7)
|
||||
|
||||
@@ -328,7 +446,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
|
||||
|
||||
static char signalStr[20];
|
||||
snprintf(signalStr, sizeof(signalStr), "Signal: %.0f", node->snr);
|
||||
snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
|
||||
|
||||
uint32_t agoSecs = sinceLastSeen(node);
|
||||
static char lastStr[20];
|
||||
@@ -339,15 +457,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
else
|
||||
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
||||
|
||||
static float simRadian;
|
||||
simRadian += 0.1; // For testing, have the compass spin unless both
|
||||
// locations are valid
|
||||
|
||||
static char distStr[20];
|
||||
*distStr = 0; // might not have location data
|
||||
float headingRadian = simRadian;
|
||||
strcpy(distStr, "? km"); // might not have location data
|
||||
float headingRadian;
|
||||
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
if (ourNode && hasPosition(ourNode) && hasPosition(node)) {
|
||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||
|
||||
// coordinates for the center of the compass/circle
|
||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||
|
||||
if (ourNode && hasPosition(ourNode) && hasPosition(node)) { // display direction toward node
|
||||
Position &op = ourNode->position, &p = node->position;
|
||||
float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (d < 2000)
|
||||
@@ -360,35 +479,17 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i));
|
||||
headingRadian = bearingToOther - myHeading;
|
||||
} else {
|
||||
drawCompass(display, compassX, compassY, headingRadian);
|
||||
} else { // direction to node is unknown so display question mark
|
||||
// Debug info for gps lock errors
|
||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||
|
||||
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||
}
|
||||
|
||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||
// Must be after distStr is populated
|
||||
drawColumns(display, x, y, fields);
|
||||
|
||||
// coordinates for the center of the compass
|
||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
||||
// (const uint8_t *)compass_bits);
|
||||
|
||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
|
||||
|
||||
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
points[i]->rotate(headingRadian);
|
||||
points[i]->scale(COMPASS_DIAM * 0.6);
|
||||
points[i]->translate(compassX, compassY);
|
||||
}
|
||||
drawLine(display, tip, tail);
|
||||
drawLine(display, leftArrow, tip);
|
||||
drawLine(display, rightArrow, tip);
|
||||
|
||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -456,6 +557,9 @@ void Screen::setup()
|
||||
// Store a pointer to Screen so we can get to it from static functions.
|
||||
ui.getUiState()->userData = this;
|
||||
|
||||
// Set the utf8 conversion function
|
||||
dispdev.setFontTableLookupFunction(customFontTableLookup);
|
||||
|
||||
// Add frames.
|
||||
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||
@@ -480,6 +584,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()
|
||||
@@ -541,14 +650,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();
|
||||
@@ -573,8 +675,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--;
|
||||
|
||||
@@ -648,35 +750,58 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
char usersStr[20];
|
||||
char channelStr[20];
|
||||
char batStr[20];
|
||||
char gpsStr[20];
|
||||
{
|
||||
LockGuard guard(&lock);
|
||||
snprintf(usersStr, sizeof(usersStr), "Users %d/%d", nodesOnline, nodesTotal);
|
||||
snprintf(channelStr, sizeof(channelStr), "%s", channelName.c_str());
|
||||
if (powerStatus.haveBattery) {
|
||||
// TODO: draw a battery icon instead of letter "B".
|
||||
int batV = powerStatus.batteryVoltageMv / 1000;
|
||||
int batCv = (powerStatus.batteryVoltageMv % 1000) / 10;
|
||||
snprintf(batStr, sizeof(batStr), "B %01d.%02dV%c%c", batV, batCv, powerStatus.charging ? '+' : ' ',
|
||||
powerStatus.usb ? 'U' : ' ');
|
||||
} else {
|
||||
snprintf(batStr, sizeof(batStr), "%s", powerStatus.usb ? "USB" : "");
|
||||
}
|
||||
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
|
||||
|
||||
if (!gpsStatus.empty()) {
|
||||
snprintf(gpsStr, sizeof(gpsStr), "%s", gpsStatus.c_str());
|
||||
} else {
|
||||
gpsStr[0] = '\0'; // Just show empty string.
|
||||
}
|
||||
// Display power status
|
||||
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, nodeStatus);
|
||||
// Display GPS status
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gpsStatus);
|
||||
}
|
||||
|
||||
const char *fields[] = {batStr, gpsStr, usersStr, channelStr, nullptr};
|
||||
uint32_t yo = drawRows(display, x, y, 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) {
|
||||
brightness = 0;
|
||||
} else {
|
||||
brightness++;
|
||||
}
|
||||
int width = brightness / (254.00 / SCREEN_WIDTH);
|
||||
dispdev.drawRect(0, 30, SCREEN_WIDTH, 4);
|
||||
dispdev.fillRect(0, 31, width, 2);
|
||||
dispdev.display();
|
||||
dispdev.setBrightness(brightness);
|
||||
}
|
||||
|
||||
int Screen::handleStatusUpdate(const Status *arg)
|
||||
{
|
||||
DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||
switch (arg->getStatusType()) {
|
||||
case STATUS_TYPE_NODE:
|
||||
setFrames();
|
||||
break;
|
||||
}
|
||||
setPeriod(1); // Update the screen right away
|
||||
return 0;
|
||||
}
|
||||
} // namespace meshtastic
|
||||
|
||||
80
src/screen.h
80
src/screen.h
@@ -13,7 +13,10 @@
|
||||
#include "PeriodicTask.h"
|
||||
#include "TypedQueue.h"
|
||||
#include "lock.h"
|
||||
#include "power.h"
|
||||
#include "PowerStatus.h"
|
||||
#include "GPSStatus.h"
|
||||
#include "NodeStatus.h"
|
||||
#include "Observer.h"
|
||||
#include <string>
|
||||
|
||||
namespace meshtastic
|
||||
@@ -29,14 +32,6 @@ 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)
|
||||
{
|
||||
@@ -44,25 +39,6 @@ class DebugInfo
|
||||
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,15 +47,8 @@ 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;
|
||||
};
|
||||
@@ -93,6 +62,10 @@ class DebugInfo
|
||||
// simultaneously).
|
||||
class Screen : public PeriodicTask
|
||||
{
|
||||
CallbackObserver<Screen, const Status *> powerStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const Status *> gpsStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||
CallbackObserver<Screen, const Status *> nodeStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||
|
||||
@@ -117,6 +90,10 @@ class Screen : public PeriodicTask
|
||||
/// Handles a button press.
|
||||
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
|
||||
|
||||
// Implementation to Adjust Brightness
|
||||
void adjustBrightness();
|
||||
int brightness = 150;
|
||||
|
||||
/// Starts showing the Bluetooth PIN screen.
|
||||
//
|
||||
// Switches over to a static frame showing the Bluetooth pairing screen
|
||||
@@ -149,11 +126,44 @@ class Screen : public PeriodicTask
|
||||
}
|
||||
}
|
||||
|
||||
/// Overrides the default utf8 character conversion, to replace empty space with question marks
|
||||
static char customFontTableLookup(const uint8_t ch) {
|
||||
// UTF-8 to font table index converter
|
||||
// Code form http://playground.arduino.cc/Main/Utf8ascii
|
||||
static uint8_t LASTCHAR;
|
||||
static bool SKIPREST; // Only display a single unconvertable-character symbol per sequence of unconvertable characters
|
||||
|
||||
if (ch < 128) { // Standard ASCII-set 0..0x7F handling
|
||||
LASTCHAR = 0;
|
||||
SKIPREST = false;
|
||||
return ch;
|
||||
}
|
||||
|
||||
uint8_t last = LASTCHAR; // get last char
|
||||
LASTCHAR = ch;
|
||||
|
||||
switch (last) { // conversion depnding on first UTF8-character
|
||||
case 0xC2: { SKIPREST = false; return (uint8_t) ch; }
|
||||
case 0xC3: { SKIPREST = false; return (uint8_t) (ch | 0xC0); }
|
||||
}
|
||||
|
||||
// We want to strip out prefix chars for two-byte char formats
|
||||
if (ch == 0xC2 || ch == 0xC3 || ch == 0x82) return (uint8_t) 0;
|
||||
|
||||
// If we already returned an unconvertable-character symbol for this unconvertable-character sequence, return NULs for the rest of it
|
||||
if (SKIPREST) return (uint8_t) 0;
|
||||
SKIPREST = true;
|
||||
|
||||
return (uint8_t) 191; // otherwise: return ¿ if character can't be converted (note that the font map we're using doesn't stick to standard EASCII codes)
|
||||
}
|
||||
|
||||
/// Returns a handle to the DebugInfo screen.
|
||||
//
|
||||
// Use this handle to set things like battery status, user count, GPS status, etc.
|
||||
DebugInfo *debug() { return &debugInfo; }
|
||||
|
||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||
|
||||
protected:
|
||||
/// Updates the UI.
|
||||
//
|
||||
|
||||
7
src/utils.h
Normal file
7
src/utils.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
/// C++ v17+ clamp function, limits a given value to a range defined by lo and hi
|
||||
template<class T>
|
||||
constexpr const T& clamp( const T& v, const T& lo, const T& hi ) {
|
||||
return (v < lo) ? lo : (hi < v) ? hi : v;
|
||||
}
|
||||
Reference in New Issue
Block a user