mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-20 09:43:03 +00:00
Compare commits
35 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 |
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.
|
||||||
@@ -40,7 +40,7 @@ We currently support three models of radios.
|
|||||||
|
|
||||||
**Make sure to get the frequency for your country**
|
**Make sure to get the frequency for your country**
|
||||||
|
|
||||||
- US/JP/AU/NZ - 915MHz
|
- US/JP/AU/NZ/CA - 915MHz
|
||||||
- CN - 470MHz
|
- CN - 470MHz
|
||||||
- EU - 868MHz, 433MHz
|
- EU - 868MHz, 433MHz
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export VERSION=0.7.9
|
export VERSION=0.7.10
|
||||||
@@ -2,17 +2,7 @@
|
|||||||
|
|
||||||
You probably don't care about this section - skip to the next one.
|
You probably don't care about this section - skip to the next one.
|
||||||
|
|
||||||
- do device release (after testing BLE fixes) https://github.com/meshtastic/Meshtastic-device/pull/218#event-3475395948
|
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors (plan for geofence mode and battery save mode)
|
||||||
- some channel names don't work - check for illegal symbols per forum report
|
|
||||||
- auto invalidate old BLE handles
|
|
||||||
- disable software update button after update finishes
|
|
||||||
- mention translations in main readme
|
|
||||||
- check BLE handle stability across sleep - stress test sleep/wake - btu_init_core calls gatt_init - which assigns handles global
|
|
||||||
- @feh123 Sony Xperia Z1 C6903 running Android 5.1.1
|
|
||||||
- first message sent is still doubled for some people
|
|
||||||
- Android frontend should refetch the android messages from backend service on Resume
|
|
||||||
- let users set arbitrary params in android
|
|
||||||
- implement first cut of router mode: preferentially handle flooding, and change sleep and GPS behaviors
|
|
||||||
- NRF52 BLE support
|
- NRF52 BLE support
|
||||||
|
|
||||||
# Medium priority
|
# Medium priority
|
||||||
@@ -58,7 +48,6 @@ Items after the first final candidate release.
|
|||||||
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||||
- read the PMU battery fault indicators and blink/led/warn user on screen
|
- read the PMU battery fault indicators and blink/led/warn user on screen
|
||||||
- discard very old nodedb records (> 1wk)
|
- discard very old nodedb records (> 1wk)
|
||||||
- add a watchdog timer
|
|
||||||
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||||
- report esp32 device code bugs back to the mothership via android
|
- report esp32 device code bugs back to the mothership via android
|
||||||
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
|
- change BLE bonding to something more secure. see comment by pSecurity->setAuthenticationMode(ESP_LE_AUTH_BOND)
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ optimizations / low priority:
|
|||||||
|
|
||||||
- read this [this](http://pages.cs.wisc.edu/~suman/pubs/nadv-mobihoc05.pdf) paper and others and make our naive flood routing less naive
|
- read this [this](http://pages.cs.wisc.edu/~suman/pubs/nadv-mobihoc05.pdf) paper and others and make our naive flood routing less naive
|
||||||
- read @cyclomies long email with good ideas on optimizations and reply
|
- read @cyclomies long email with good ideas on optimizations and reply
|
||||||
- Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
|
- DONE Remove NodeNum assignment algorithm (now that we use 4 byte node nums)
|
||||||
- make android app warn if firmware is too old or too new to talk to
|
- DONE make android app warn if firmware is too old or too new to talk to
|
||||||
- change nodenums and packetids in protobuf to be fixed32
|
- change nodenums and packetids in protobuf to be fixed32
|
||||||
- low priority: think more careful about reliable retransmit intervals
|
- low priority: think more careful about reliable retransmit intervals
|
||||||
- make ReliableRouter.pending threadsafe
|
- make ReliableRouter.pending threadsafe
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ default_envs = tbeam ; Note: the github actions CI test build can't yet build NR
|
|||||||
|
|
||||||
[env]
|
[env]
|
||||||
|
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
; customize the partition table
|
; customize the partition table
|
||||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||||
board_build.partitions = partition-table.csv
|
board_build.partitions = partition-table.csv
|
||||||
@@ -78,7 +80,6 @@ lib_deps =
|
|||||||
; Common settings for ESP targes, mixin with extends = esp32_base
|
; Common settings for ESP targes, mixin with extends = esp32_base
|
||||||
[esp32_base]
|
[esp32_base]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
framework = arduino
|
|
||||||
src_filter =
|
src_filter =
|
||||||
${env.src_filter} -<nrf52/>
|
${env.src_filter} -<nrf52/>
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
@@ -133,7 +134,6 @@ build_flags =
|
|||||||
; For more details see my post in the forum.
|
; For more details see my post in the forum.
|
||||||
[env:cubecellplus]
|
[env:cubecellplus]
|
||||||
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
platform = https://github.com/HelTecAutomation/platform-asrmicro650x.git ; we use top-of-tree because stable version has too many bugs - asrmicro650x
|
||||||
framework = arduino
|
|
||||||
board = cubecell_board_plus
|
board = cubecell_board_plus
|
||||||
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
; FIXME, bug in cubecell arduino - they are supposed to set ARDUINO
|
||||||
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
build_flags = ${env.build_flags} -DARDUINO=100 -Isrc/cubecell
|
||||||
@@ -143,7 +143,6 @@ src_filter =
|
|||||||
; Common settings for NRF52 based targets
|
; Common settings for NRF52 based targets
|
||||||
[nrf52_base]
|
[nrf52_base]
|
||||||
platform = nordicnrf52
|
platform = nordicnrf52
|
||||||
framework = arduino
|
|
||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
build_type = debug ; I'm debugging with ICE a lot now
|
build_type = debug ; I'm debugging with ICE a lot now
|
||||||
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
|
||||||
|
|||||||
108
src/GPSStatus.h
Normal file
108
src/GPSStatus.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the GPS system.
|
||||||
|
class GPSStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<GPSStatus, const GPSStatus *> statusObserver = CallbackObserver<GPSStatus, const GPSStatus *>(this, &GPSStatus::updateStatus);
|
||||||
|
|
||||||
|
bool hasLock = false; // default to false, until we complete our first read
|
||||||
|
bool isConnected = false; // Do we have a GPS we are talking to
|
||||||
|
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
|
||||||
|
int32_t altitude = 0;
|
||||||
|
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs scaling before use)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
GPSStatus() {
|
||||||
|
statusType = STATUS_TYPE_GPS;
|
||||||
|
}
|
||||||
|
GPSStatus( bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop ) : Status()
|
||||||
|
{
|
||||||
|
this->hasLock = hasLock;
|
||||||
|
this->isConnected = isConnected;
|
||||||
|
this->latitude = latitude;
|
||||||
|
this->longitude = longitude;
|
||||||
|
this->altitude = altitude;
|
||||||
|
this->dop = dop;
|
||||||
|
}
|
||||||
|
GPSStatus(const GPSStatus &);
|
||||||
|
GPSStatus &operator=(const GPSStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const GPSStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasLock() const
|
||||||
|
{
|
||||||
|
return hasLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getIsConnected() const
|
||||||
|
{
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getLatitude() const
|
||||||
|
{
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getLongitude() const
|
||||||
|
{
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getAltitude() const
|
||||||
|
{
|
||||||
|
return altitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getDOP() const
|
||||||
|
{
|
||||||
|
return dop;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const GPSStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->hasLock != hasLock ||
|
||||||
|
newStatus->isConnected != isConnected ||
|
||||||
|
newStatus->latitude != latitude ||
|
||||||
|
newStatus->longitude != longitude ||
|
||||||
|
newStatus->altitude != altitude ||
|
||||||
|
newStatus->dop != dop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const GPSStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
hasLock = newStatus->hasLock;
|
||||||
|
isConnected = newStatus->isConnected;
|
||||||
|
latitude = newStatus->latitude;
|
||||||
|
longitude = newStatus->longitude;
|
||||||
|
altitude = newStatus->altitude;
|
||||||
|
dop = newStatus->dop;
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::GPSStatus *gpsStatus;
|
||||||
73
src/NodeStatus.h
Normal file
73
src/NodeStatus.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the NodeDB system.
|
||||||
|
class NodeStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver = CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||||
|
|
||||||
|
uint8_t numOnline = 0;
|
||||||
|
uint8_t numTotal = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
NodeStatus() {
|
||||||
|
statusType = STATUS_TYPE_NODE;
|
||||||
|
}
|
||||||
|
NodeStatus( uint8_t numOnline, uint8_t numTotal ) : Status()
|
||||||
|
{
|
||||||
|
this->numOnline = numOnline;
|
||||||
|
this->numTotal = numTotal;
|
||||||
|
}
|
||||||
|
NodeStatus(const NodeStatus &);
|
||||||
|
NodeStatus &operator=(const NodeStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const NodeStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getNumOnline() const
|
||||||
|
{
|
||||||
|
return numOnline;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getNumTotal() const
|
||||||
|
{
|
||||||
|
return numTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const NodeStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->getNumOnline() != numOnline ||
|
||||||
|
newStatus->getNumTotal() != numTotal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const NodeStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
numOnline = newStatus->getNumOnline();
|
||||||
|
numTotal = newStatus->getNumTotal();
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("Node status update: %d online, %d total\n", numOnline, numTotal);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::NodeStatus *nodeStatus;
|
||||||
180
src/Power.cpp
Normal file
180
src/Power.cpp
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#include "power.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "sleep.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
|
||||||
|
// FIXME. nasty hack cleanup how we load axp192
|
||||||
|
#undef AXP192_SLAVE_ADDRESS
|
||||||
|
#include "axp20x.h"
|
||||||
|
AXP20X_Class axp;
|
||||||
|
bool pmu_irq = false;
|
||||||
|
|
||||||
|
Power *power;
|
||||||
|
|
||||||
|
bool Power::setup()
|
||||||
|
{
|
||||||
|
|
||||||
|
axp192Init();
|
||||||
|
PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device
|
||||||
|
setPeriod(1);
|
||||||
|
|
||||||
|
return axp192_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads power status to powerStatus singleton.
|
||||||
|
//
|
||||||
|
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
||||||
|
void Power::readPowerStatus()
|
||||||
|
{
|
||||||
|
bool hasBattery = axp.isBatteryConnect();
|
||||||
|
int batteryVoltageMv = 0;
|
||||||
|
uint8_t batteryChargePercent = 0;
|
||||||
|
if (hasBattery) {
|
||||||
|
batteryVoltageMv = axp.getBattVoltage();
|
||||||
|
// If the AXP192 returns a valid battery percentage, use it
|
||||||
|
if (axp.getBattPercentage() >= 0) {
|
||||||
|
batteryChargePercent = axp.getBattPercentage();
|
||||||
|
} else {
|
||||||
|
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
||||||
|
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
||||||
|
batteryChargePercent = clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::PowerStatus powerStatus = meshtastic::PowerStatus(hasBattery, axp.isVBUSPlug(), axp.isChargeing(), batteryVoltageMv, batteryChargePercent);
|
||||||
|
newStatus.notifyObservers(&powerStatus);
|
||||||
|
|
||||||
|
// If we have a battery at all and it is less than 10% full, force deep sleep
|
||||||
|
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() &&
|
||||||
|
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS)
|
||||||
|
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Power::doTask()
|
||||||
|
{
|
||||||
|
readPowerStatus();
|
||||||
|
|
||||||
|
// Only read once every 20 seconds once the power status for the app has been initialized
|
||||||
|
if(statusHandler && statusHandler->isInitialized())
|
||||||
|
setPeriod(1000 * 20);
|
||||||
|
}
|
||||||
|
#endif // TBEAM_V10
|
||||||
|
|
||||||
|
#ifdef AXP192_SLAVE_ADDRESS
|
||||||
|
/**
|
||||||
|
* Init the power manager chip
|
||||||
|
*
|
||||||
|
* axp192 power
|
||||||
|
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
||||||
|
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
||||||
|
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||||
|
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||||
|
*/
|
||||||
|
void Power::axp192Init()
|
||||||
|
{
|
||||||
|
if (axp192_found) {
|
||||||
|
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||||
|
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||||
|
|
||||||
|
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||||
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("----------------------------------------\n");
|
||||||
|
|
||||||
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||||
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
||||||
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||||
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||||
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||||
|
axp.setDCDC1Voltage(3300); // for the OLED power
|
||||||
|
|
||||||
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
|
|
||||||
|
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
// Not connected
|
||||||
|
//val = 0xfc;
|
||||||
|
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
||||||
|
|
||||||
|
//not used
|
||||||
|
//val = 0x46;
|
||||||
|
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
||||||
|
#endif
|
||||||
|
axp.debugCharging();
|
||||||
|
|
||||||
|
#ifdef PMU_IRQ
|
||||||
|
pinMode(PMU_IRQ, INPUT);
|
||||||
|
attachInterrupt(
|
||||||
|
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||||
|
|
||||||
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||||
|
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
||||||
|
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
||||||
|
1);
|
||||||
|
|
||||||
|
axp.clearIRQ();
|
||||||
|
#endif
|
||||||
|
readPowerStatus();
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 Begin FAIL\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Power::loop()
|
||||||
|
{
|
||||||
|
|
||||||
|
#ifdef PMU_IRQ
|
||||||
|
if (pmu_irq) {
|
||||||
|
pmu_irq = false;
|
||||||
|
axp.readIRQ();
|
||||||
|
|
||||||
|
DEBUG_MSG("pmu irq!\n");
|
||||||
|
|
||||||
|
if (axp.isChargingIRQ()) {
|
||||||
|
DEBUG_MSG("Battery start charging\n");
|
||||||
|
}
|
||||||
|
if (axp.isChargingDoneIRQ()) {
|
||||||
|
DEBUG_MSG("Battery fully charged\n");
|
||||||
|
}
|
||||||
|
if (axp.isVbusRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("USB unplugged\n");
|
||||||
|
}
|
||||||
|
if (axp.isVbusPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("USB plugged In\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattPlugInIRQ()) {
|
||||||
|
DEBUG_MSG("Battery inserted\n");
|
||||||
|
}
|
||||||
|
if (axp.isBattRemoveIRQ()) {
|
||||||
|
DEBUG_MSG("Battery removed\n");
|
||||||
|
}
|
||||||
|
if (axp.isPEKShortPressIRQ()) {
|
||||||
|
DEBUG_MSG("PEK short button press\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
readPowerStatus();
|
||||||
|
axp.clearIRQ();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // T_BEAM_V10
|
||||||
|
|
||||||
|
}
|
||||||
103
src/PowerStatus.h
Normal file
103
src/PowerStatus.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Status.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic {
|
||||||
|
|
||||||
|
/// Describes the state of the GPS system.
|
||||||
|
class PowerStatus : public Status
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
CallbackObserver<PowerStatus, const PowerStatus *> statusObserver = CallbackObserver<PowerStatus, const PowerStatus *>(this, &PowerStatus::updateStatus);
|
||||||
|
|
||||||
|
/// Whether we have a battery connected
|
||||||
|
bool hasBattery;
|
||||||
|
/// Battery voltage in mV, valid if haveBattery is true
|
||||||
|
int batteryVoltageMv;
|
||||||
|
/// Battery charge percentage, either read directly or estimated
|
||||||
|
uint8_t batteryChargePercent;
|
||||||
|
/// Whether USB is connected
|
||||||
|
bool hasUSB;
|
||||||
|
/// Whether we are charging the battery
|
||||||
|
bool isCharging;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
PowerStatus() {
|
||||||
|
statusType = STATUS_TYPE_POWER;
|
||||||
|
}
|
||||||
|
PowerStatus( bool hasBattery, bool hasUSB, bool isCharging, int batteryVoltageMv, uint8_t batteryChargePercent ) : Status()
|
||||||
|
{
|
||||||
|
this->hasBattery = hasBattery;
|
||||||
|
this->hasUSB = hasUSB;
|
||||||
|
this->isCharging = isCharging;
|
||||||
|
this->batteryVoltageMv = batteryVoltageMv;
|
||||||
|
this->batteryChargePercent = batteryChargePercent;
|
||||||
|
}
|
||||||
|
PowerStatus(const PowerStatus &);
|
||||||
|
PowerStatus &operator=(const PowerStatus &);
|
||||||
|
|
||||||
|
void observe(Observable<const PowerStatus *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasBattery() const
|
||||||
|
{
|
||||||
|
return hasBattery;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getHasUSB() const
|
||||||
|
{
|
||||||
|
return hasUSB;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getIsCharging() const
|
||||||
|
{
|
||||||
|
return isCharging;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBatteryVoltageMv() const
|
||||||
|
{
|
||||||
|
return batteryVoltageMv;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getBatteryChargePercent() const
|
||||||
|
{
|
||||||
|
return batteryChargePercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matches(const PowerStatus *newStatus) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
newStatus->getHasBattery() != hasBattery ||
|
||||||
|
newStatus->getHasUSB() != hasUSB ||
|
||||||
|
newStatus->getBatteryVoltageMv() != batteryVoltageMv
|
||||||
|
);
|
||||||
|
}
|
||||||
|
int updateStatus(const PowerStatus *newStatus) {
|
||||||
|
// Only update the status if values have actually changed
|
||||||
|
bool isDirty;
|
||||||
|
{
|
||||||
|
isDirty = matches(newStatus);
|
||||||
|
initialized = true;
|
||||||
|
hasBattery = newStatus->getHasBattery();
|
||||||
|
batteryVoltageMv = newStatus->getBatteryVoltageMv();
|
||||||
|
batteryChargePercent = newStatus->getBatteryChargePercent();
|
||||||
|
hasUSB = newStatus->getHasUSB();
|
||||||
|
isCharging = newStatus->getIsCharging();
|
||||||
|
}
|
||||||
|
if(isDirty) {
|
||||||
|
DEBUG_MSG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
|
||||||
|
onNewStatus.notifyObservers(this);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern meshtastic::PowerStatus *powerStatus;
|
||||||
72
src/Status.h
Normal file
72
src/Status.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Observer.h"
|
||||||
|
|
||||||
|
// Constants for the various status types, so we can tell subclass instances apart
|
||||||
|
#define STATUS_TYPE_BASE 0
|
||||||
|
#define STATUS_TYPE_POWER 1
|
||||||
|
#define STATUS_TYPE_GPS 2
|
||||||
|
#define STATUS_TYPE_NODE 3
|
||||||
|
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
// A base class for observable status
|
||||||
|
class Status
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
// Allows us to observe an Observable
|
||||||
|
CallbackObserver<Status, const Status *> statusObserver = CallbackObserver<Status, const Status *>(this, &Status::updateStatus);
|
||||||
|
bool initialized = false;
|
||||||
|
// Workaround for no typeid support
|
||||||
|
int statusType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Allows us to generate observable events
|
||||||
|
Observable<const Status *> onNewStatus;
|
||||||
|
|
||||||
|
// Enable polymorphism ?
|
||||||
|
virtual ~Status() = default;
|
||||||
|
|
||||||
|
Status() {
|
||||||
|
if (!statusType)
|
||||||
|
{
|
||||||
|
statusType = STATUS_TYPE_BASE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent object copy/move
|
||||||
|
Status(const Status &) = delete;
|
||||||
|
Status &operator=(const Status &) = delete;
|
||||||
|
|
||||||
|
// Start observing a source of data
|
||||||
|
void observe(Observable<const Status *> *source)
|
||||||
|
{
|
||||||
|
statusObserver.observe(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines whether or not existing data matches the data in another Status instance
|
||||||
|
bool matches(const Status *otherStatus) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInitialized() const
|
||||||
|
{
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStatusType() const
|
||||||
|
{
|
||||||
|
return statusType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the Observable we're observing generates a new notification
|
||||||
|
int updateStatus(const Status *newStatus)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
0
src/StatusHandler.h
Normal file
0
src/StatusHandler.h
Normal file
@@ -17,8 +17,15 @@ void Thread::callRun(void *_this)
|
|||||||
|
|
||||||
void WorkerThread::doRun()
|
void WorkerThread::doRun()
|
||||||
{
|
{
|
||||||
|
startWatchdog();
|
||||||
|
|
||||||
while (!wantExit) {
|
while (!wantExit) {
|
||||||
|
stopWatchdog();
|
||||||
block();
|
block();
|
||||||
|
startWatchdog();
|
||||||
|
|
||||||
|
// no need - startWatchdog is guaranteed to give us one full watchdog interval
|
||||||
|
// serviceWatchdog(); // Let our loop worker have one full watchdog interval (at least) to run
|
||||||
|
|
||||||
#ifdef DEBUG_STACK
|
#ifdef DEBUG_STACK
|
||||||
static uint32_t lastPrint = 0;
|
static uint32_t lastPrint = 0;
|
||||||
@@ -30,6 +37,8 @@ void WorkerThread::doRun()
|
|||||||
|
|
||||||
loop();
|
loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopWatchdog();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include "esp_task_wdt.h"
|
||||||
#include "freertosinc.h"
|
#include "freertosinc.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef HAS_FREE_RTOS
|
#ifdef HAS_FREE_RTOS
|
||||||
|
|
||||||
@@ -26,6 +27,23 @@ class Thread
|
|||||||
*/
|
*/
|
||||||
virtual void doRun() = 0;
|
virtual void doRun() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All thread run methods must periodically call serviceWatchdog, or the system will declare them hung and panic.
|
||||||
|
*
|
||||||
|
* this only applies after startWatchdog() has been called. If you need to sleep for a long time call stopWatchdog()
|
||||||
|
*/
|
||||||
|
void serviceWatchdog() { esp_task_wdt_reset(); }
|
||||||
|
void startWatchdog()
|
||||||
|
{
|
||||||
|
auto r = esp_task_wdt_add(taskHandle);
|
||||||
|
assert(r == ESP_OK);
|
||||||
|
}
|
||||||
|
void stopWatchdog()
|
||||||
|
{
|
||||||
|
auto r = esp_task_wdt_delete(taskHandle);
|
||||||
|
assert(r == ESP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void callRun(void *_this);
|
static void callRun(void *_this);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -236,6 +236,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// This string must exactly match the case used in release file names or the android updater won't work
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
#define HW_VENDOR "ttgo-lora32-v2"
|
#define HW_VENDOR "ttgo-lora32-v2"
|
||||||
|
|
||||||
|
#undef GPS_RX_PIN
|
||||||
|
#undef GPS_TX_PIN
|
||||||
|
#define GPS_RX_PIN 36
|
||||||
|
#define GPS_TX_PIN 13 // per @eugene
|
||||||
|
|
||||||
#define I2C_SDA 21 // I2C pins for this board
|
#define I2C_SDA 21 // I2C pins for this board
|
||||||
#define I2C_SCL 22
|
#define I2C_SCL 22
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "power.h"
|
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "utils.h"
|
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
bool bluetoothOn;
|
bool bluetoothOn;
|
||||||
|
|
||||||
@@ -60,111 +60,6 @@ void getMacAddr(uint8_t *dmac)
|
|||||||
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef TBEAM_V10
|
|
||||||
|
|
||||||
// FIXME. nasty hack cleanup how we load axp192
|
|
||||||
#undef AXP192_SLAVE_ADDRESS
|
|
||||||
#include "axp20x.h"
|
|
||||||
AXP20X_Class axp;
|
|
||||||
bool pmu_irq = false;
|
|
||||||
|
|
||||||
/// Reads power status to powerStatus singleton.
|
|
||||||
//
|
|
||||||
// TODO(girts): move this and other axp stuff to power.h/power.cpp.
|
|
||||||
void readPowerStatus()
|
|
||||||
{
|
|
||||||
powerStatus.haveBattery = axp.isBatteryConnect();
|
|
||||||
if (powerStatus.haveBattery) {
|
|
||||||
powerStatus.batteryVoltageMv = axp.getBattVoltage();
|
|
||||||
// If the AXP192 returns a valid battery percentage, use it
|
|
||||||
if (axp.getBattPercentage() >= 0) {
|
|
||||||
powerStatus.batteryChargePercent = axp.getBattPercentage();
|
|
||||||
} else {
|
|
||||||
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
|
|
||||||
// In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in power.h
|
|
||||||
powerStatus.batteryChargePercent = clamp((int)(((powerStatus.batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)), 0, 100);
|
|
||||||
}
|
|
||||||
DEBUG_MSG("Battery %dmV %d%%\n", powerStatus.batteryVoltageMv, powerStatus.batteryChargePercent);
|
|
||||||
}
|
|
||||||
powerStatus.usb = axp.isVBUSPlug();
|
|
||||||
powerStatus.charging = axp.isChargeing();
|
|
||||||
}
|
|
||||||
#endif // TBEAM_V10
|
|
||||||
|
|
||||||
#ifdef AXP192_SLAVE_ADDRESS
|
|
||||||
/**
|
|
||||||
* Init the power manager chip
|
|
||||||
*
|
|
||||||
* axp192 power
|
|
||||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
|
||||||
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
|
||||||
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
|
||||||
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
|
||||||
*/
|
|
||||||
void axp192Init()
|
|
||||||
{
|
|
||||||
if (axp192_found) {
|
|
||||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
|
||||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
|
||||||
|
|
||||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("----------------------------------------\n");
|
|
||||||
|
|
||||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
|
||||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
|
||||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
|
||||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
|
||||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
|
||||||
axp.setDCDC1Voltage(3300); // for the OLED power
|
|
||||||
|
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
|
||||||
|
|
||||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
|
||||||
#if 0
|
|
||||||
|
|
||||||
// Not connected
|
|
||||||
//val = 0xfc;
|
|
||||||
//axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection
|
|
||||||
|
|
||||||
//not used
|
|
||||||
//val = 0x46;
|
|
||||||
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
|
||||||
#endif
|
|
||||||
axp.debugCharging();
|
|
||||||
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
pinMode(PMU_IRQ, INPUT);
|
|
||||||
attachInterrupt(
|
|
||||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
|
||||||
|
|
||||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
|
||||||
axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ |
|
|
||||||
AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ,
|
|
||||||
1);
|
|
||||||
|
|
||||||
axp.clearIRQ();
|
|
||||||
#endif
|
|
||||||
readPowerStatus();
|
|
||||||
} else {
|
|
||||||
DEBUG_MSG("AXP192 Begin FAIL\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG_MSG("AXP192 not found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
static void printBLEinfo() {
|
static void printBLEinfo() {
|
||||||
int dev_num = esp_ble_get_bond_device_num();
|
int dev_num = esp_ble_get_bond_device_num();
|
||||||
@@ -190,9 +85,15 @@ void esp32Setup()
|
|||||||
|
|
||||||
// enableModemSleep();
|
// enableModemSleep();
|
||||||
|
|
||||||
#ifdef AXP192_SLAVE_ADDRESS
|
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
||||||
axp192Init();
|
// false positives. The wait-to-sleep timeout for shutting down radios is 30 secs, so pick 45 for now.
|
||||||
#endif
|
#define APP_WATCHDOG_SECS 45
|
||||||
|
|
||||||
|
auto res = esp_task_wdt_init(APP_WATCHDOG_SECS, true);
|
||||||
|
assert(res == ESP_OK);
|
||||||
|
|
||||||
|
res = esp_task_wdt_add(NULL);
|
||||||
|
assert(res == ESP_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -215,51 +116,12 @@ uint32_t axpDebugRead()
|
|||||||
Periodic axpDebugOutput(axpDebugRead);
|
Periodic axpDebugOutput(axpDebugRead);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// loop code specific to ESP32 targets
|
/// loop code specific to ESP32 targets
|
||||||
void esp32Loop()
|
void esp32Loop()
|
||||||
{
|
{
|
||||||
|
esp_task_wdt_reset(); // service our app level watchdog
|
||||||
loopBLE();
|
loopBLE();
|
||||||
|
|
||||||
// for debug printing
|
// for debug printing
|
||||||
// radio.radioIf.canSleep();
|
// radio.radioIf.canSleep();
|
||||||
|
|
||||||
#ifdef PMU_IRQ
|
|
||||||
if (pmu_irq) {
|
|
||||||
pmu_irq = false;
|
|
||||||
axp.readIRQ();
|
|
||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
|
||||||
|
|
||||||
if (axp.isChargingIRQ()) {
|
|
||||||
DEBUG_MSG("Battery start charging\n");
|
|
||||||
}
|
|
||||||
if (axp.isChargingDoneIRQ()) {
|
|
||||||
DEBUG_MSG("Battery fully charged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("USB unplugged\n");
|
|
||||||
}
|
|
||||||
if (axp.isVbusPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("USB plugged In\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattPlugInIRQ()) {
|
|
||||||
DEBUG_MSG("Battery inserted\n");
|
|
||||||
}
|
|
||||||
if (axp.isBattRemoveIRQ()) {
|
|
||||||
DEBUG_MSG("Battery removed\n");
|
|
||||||
}
|
|
||||||
if (axp.isPEKShortPressIRQ()) {
|
|
||||||
DEBUG_MSG("PEK short button press\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
readPowerStatus();
|
|
||||||
axp.clearIRQ();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (powerStatus.haveBattery && !powerStatus.usb &&
|
|
||||||
axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep
|
|
||||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
|
||||||
|
|
||||||
#endif // T_BEAM_V10
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "sys/time.h"
|
#include "sys/time.h"
|
||||||
|
|
||||||
@@ -40,6 +41,8 @@ class GPS : public Observable<void *>
|
|||||||
|
|
||||||
virtual ~GPS() {}
|
virtual ~GPS() {}
|
||||||
|
|
||||||
|
Observable<const meshtastic::GPSStatus *> newStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we succeeded
|
* Returns true if we succeeded
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,5 +65,9 @@ void NEMAGPS::loop()
|
|||||||
if (hasValidLocation)
|
if (hasValidLocation)
|
||||||
notifyObservers(NULL);
|
notifyObservers(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
longitude = ublox.getLongitude(0);
|
longitude = ublox.getLongitude(0);
|
||||||
altitude = ublox.getAltitude(0) / 1000; // in mm convert to meters
|
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
|
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
|
||||||
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d, pdop=%f\n", latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2);
|
|
||||||
|
|
||||||
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
|
||||||
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
|
||||||
@@ -129,6 +128,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
|
|||||||
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||||
wantNewLocation = true;
|
wantNewLocation = true;
|
||||||
|
|
||||||
|
// Notify any status instances that are observing us
|
||||||
|
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasLock(), isConnected, latitude, longitude, altitude, dop);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
||||||
// the serial
|
// the serial
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||||
|
|||||||
41
src/main.cpp
41
src/main.cpp
@@ -37,8 +37,8 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include <Wire.h>
|
|
||||||
#include <OneButton.h>
|
#include <OneButton.h>
|
||||||
|
#include <Wire.h>
|
||||||
// #include <driver/rtc_io.h>
|
// #include <driver/rtc_io.h>
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
@@ -56,8 +56,14 @@
|
|||||||
// We always create a screen object, but we only init it if we find the hardware
|
// We always create a screen object, but we only init it if we find the hardware
|
||||||
meshtastic::Screen screen(SSD1306_ADDRESS);
|
meshtastic::Screen screen(SSD1306_ADDRESS);
|
||||||
|
|
||||||
// Global power status singleton
|
// Global power status
|
||||||
meshtastic::PowerStatus powerStatus;
|
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
|
||||||
|
|
||||||
|
// Global GPS status
|
||||||
|
meshtastic::GPSStatus *gpsStatus = new meshtastic::GPSStatus();
|
||||||
|
|
||||||
|
// Global Node status
|
||||||
|
meshtastic::NodeStatus *nodeStatus = new meshtastic::NodeStatus();
|
||||||
|
|
||||||
bool ssd1306_found;
|
bool ssd1306_found;
|
||||||
bool axp192_found;
|
bool axp192_found;
|
||||||
@@ -121,22 +127,24 @@ static uint32_t ledBlinker()
|
|||||||
setLed(ledOn);
|
setLed(ledOn);
|
||||||
|
|
||||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000);
|
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 2 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Periodic ledPeriodic(ledBlinker);
|
Periodic ledPeriodic(ledBlinker);
|
||||||
|
|
||||||
// Prepare for button presses
|
// Prepare for button presses
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
OneButton userButton;
|
OneButton userButton;
|
||||||
#endif
|
#endif
|
||||||
#ifdef BUTTON_PIN_ALT
|
#ifdef BUTTON_PIN_ALT
|
||||||
OneButton userButtonAlt;
|
OneButton userButtonAlt;
|
||||||
#endif
|
#endif
|
||||||
void userButtonPressed() {
|
void userButtonPressed()
|
||||||
|
{
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
}
|
}
|
||||||
void userButtonPressedLong(){
|
void userButtonPressedLong()
|
||||||
|
{
|
||||||
screen.adjustBrightness();
|
screen.adjustBrightness();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +233,14 @@ void setup()
|
|||||||
esp32Setup();
|
esp32Setup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
// Currently only the tbeam has a PMU
|
||||||
|
power = new Power();
|
||||||
|
power->setup();
|
||||||
|
power->setStatusHandler(powerStatus);
|
||||||
|
powerStatus->observe(&power->newStatus);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef NRF52_SERIES
|
#ifdef NRF52_SERIES
|
||||||
nrf52Setup();
|
nrf52Setup();
|
||||||
#endif
|
#endif
|
||||||
@@ -253,9 +269,10 @@ void setup()
|
|||||||
gps = new NEMAGPS();
|
gps = new NEMAGPS();
|
||||||
gps->setup();
|
gps->setup();
|
||||||
#endif
|
#endif
|
||||||
|
gpsStatus->observe(&gps->newStatus);
|
||||||
|
nodeStatus->observe(&nodeDB.newStatus);
|
||||||
|
|
||||||
service.init();
|
service.init();
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
// Must be after we init the service, because the wifi settings are loaded by NodeDB (oops)
|
// Must be after we init the service, because the wifi settings are loaded by NodeDB (oops)
|
||||||
initWifi();
|
initWifi();
|
||||||
@@ -341,6 +358,9 @@ void loop()
|
|||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp32Loop();
|
esp32Loop();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef TBEAM_V10
|
||||||
|
power->loop();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
userButton.tick();
|
userButton.tick();
|
||||||
@@ -365,9 +385,8 @@ void loop()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update the screen last, after we've figured out what to show.
|
// Update the screen last, after we've figured out what to show.
|
||||||
screen.debug()->setNodeNumbersStatus(nodeDB.getNumOnlineNodes(), nodeDB.getNumNodes());
|
|
||||||
screen.debug()->setChannelNameStatus(channelSettings.name);
|
screen.debug()->setChannelNameStatus(channelSettings.name);
|
||||||
screen.debug()->setPowerStatus(powerStatus);
|
// screen.debug()->setPowerStatus(powerStatus);
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
||||||
// i.e. don't just keep spinning in loop as fast as we can.
|
// i.e. don't just keep spinning in loop as fast as we can.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
extern bool ssd1306_found;
|
||||||
@@ -9,6 +12,11 @@ extern bool isUSBPowered;
|
|||||||
|
|
||||||
// Global Screen singleton.
|
// Global Screen singleton.
|
||||||
extern meshtastic::Screen screen;
|
extern meshtastic::Screen screen;
|
||||||
|
//extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
|
||||||
|
|
||||||
|
//extern meshtastic::PowerStatus *powerStatus;
|
||||||
|
//extern meshtastic::GPSStatus *gpsStatus;
|
||||||
|
//extern meshtastic::NodeStatusHandler *nodeStatusHandler;
|
||||||
|
|
||||||
// Return a human readable string of the form "Meshtastic_ab13"
|
// Return a human readable string of the form "Meshtastic_ab13"
|
||||||
const char *getDeviceName();
|
const char *getDeviceName();
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ int MeshService::onGPSChanged(void *unused)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Include our current battery voltage in our position announcement
|
// Include our current battery voltage in our position announcement
|
||||||
pos.battery_level = powerStatus.batteryChargePercent;
|
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||||
updateBatteryLevel(pos.battery_level);
|
updateBatteryLevel(pos.battery_level);
|
||||||
|
|
||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
|
|||||||
@@ -342,8 +342,7 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
int oldNumNodes = *numNodes;
|
int oldNumNodes = *numNodes;
|
||||||
NodeInfo *info = getOrCreateNode(mp.from);
|
NodeInfo *info = getOrCreateNode(mp.from);
|
||||||
|
|
||||||
if (oldNumNodes != *numNodes)
|
notifyObservers();
|
||||||
updateGUI = true; // we just created a nodeinfo
|
|
||||||
|
|
||||||
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
||||||
info->has_position = true; // at least the time is valid
|
info->has_position = true; // at least the time is valid
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include "Observer.h"
|
||||||
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
|
||||||
extern DeviceState devicestate;
|
extern DeviceState devicestate;
|
||||||
extern MyNodeInfo &myNodeInfo;
|
extern MyNodeInfo &myNodeInfo;
|
||||||
@@ -32,6 +34,7 @@ class NodeDB
|
|||||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||||
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
||||||
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
|
|
||||||
/// don't do mesh based algoritm for node id assignment (initially)
|
/// don't do mesh based algoritm for node id assignment (initially)
|
||||||
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
||||||
@@ -91,6 +94,13 @@ class NodeDB
|
|||||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
NodeInfo *getOrCreateNode(NodeNum n);
|
NodeInfo *getOrCreateNode(NodeNum n);
|
||||||
|
|
||||||
|
/// Notify observers of changes to the DB
|
||||||
|
void notifyObservers() {
|
||||||
|
// Notify observers of the current node state
|
||||||
|
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineNodes(), getNumNodes());
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
}
|
||||||
|
|
||||||
/// read our db from flash
|
/// read our db from flash
|
||||||
void loadFromDisk();
|
void loadFromDisk();
|
||||||
|
|
||||||
|
|||||||
37
src/power.h
37
src/power.h
@@ -1,4 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "PeriodicTask.h"
|
||||||
|
#include "PowerStatus.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per @spattinson
|
* Per @spattinson
|
||||||
@@ -13,23 +15,26 @@
|
|||||||
#define BAT_MILLIVOLTS_FULL 4100
|
#define BAT_MILLIVOLTS_FULL 4100
|
||||||
#define BAT_MILLIVOLTS_EMPTY 3500
|
#define BAT_MILLIVOLTS_EMPTY 3500
|
||||||
|
|
||||||
namespace meshtastic
|
class Power : public PeriodicTask
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Describes the state of the power system.
|
public:
|
||||||
struct PowerStatus {
|
|
||||||
/// Whether we have a battery connected
|
Observable<const meshtastic::PowerStatus *> newStatus;
|
||||||
bool haveBattery;
|
|
||||||
/// Battery voltage in mV, valid if haveBattery is true
|
void readPowerStatus();
|
||||||
int batteryVoltageMv;
|
void loop();
|
||||||
/// Battery charge percentage, either read directly or estimated
|
virtual bool setup();
|
||||||
int batteryChargePercent;
|
virtual void doTask();
|
||||||
/// Whether USB is connected
|
void setStatusHandler(meshtastic::PowerStatus *handler)
|
||||||
bool usb;
|
{
|
||||||
/// Whether we are charging the battery
|
statusHandler = handler;
|
||||||
bool charging;
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
meshtastic::PowerStatus *statusHandler;
|
||||||
|
virtual void axp192Init();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace meshtastic
|
extern Power *power;
|
||||||
|
|
||||||
extern meshtastic::PowerStatus powerStatus;
|
|
||||||
163
src/screen.cpp
163
src/screen.cpp
@@ -42,7 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#endif
|
#endif
|
||||||
#define SCREEN_HEIGHT 64
|
#define SCREEN_HEIGHT 64
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
#define TRANSITION_FRAMERATE 30 // fps
|
||||||
#define IDLE_FRAMERATE 10 // in fps
|
#define IDLE_FRAMERATE 1 // in fps
|
||||||
#define COMPASS_DIAM 44
|
#define COMPASS_DIAM 44
|
||||||
|
|
||||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||||
@@ -55,7 +55,13 @@ static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
|||||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||||
static char btPIN[16] = "888888";
|
static char btPIN[16] = "888888";
|
||||||
|
|
||||||
uint8_t imgBattery[16] = { 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C };
|
uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
|
||||||
|
|
||||||
|
// if defined a pixel will blink to show redraws
|
||||||
|
// #define SHOW_REDRAWS
|
||||||
|
#ifdef SHOW_REDRAWS
|
||||||
|
static bool heartbeat = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
@@ -69,10 +75,11 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
const char *region = xstr(HW_VERSION);
|
const char *region = xstr(HW_VERSION);
|
||||||
if(*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
if (*region && region[3] == '-') // Skip past 1.0- in the 1.0-EU865 string
|
||||||
region += 4;
|
region += 4;
|
||||||
char buf[16];
|
char buf[16];
|
||||||
snprintf(buf, sizeof(buf), "%s", xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
snprintf(buf, sizeof(buf), "%s",
|
||||||
|
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||||
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
display->drawString(SCREEN_WIDTH - 20, 0, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +98,8 @@ static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
char buf[30];
|
char buf[30];
|
||||||
const char *name = "Name: ";
|
const char *name = "Name: ";
|
||||||
strcpy(buf,name);
|
strcpy(buf, name);
|
||||||
strcat(buf,getDeviceName());
|
strcat(buf, getDeviceName());
|
||||||
display->drawString(64 + x, 48 + y, buf);
|
display->drawString(64 + x, 48 + y, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,10 +149,11 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
#if 0
|
||||||
/// @return the max y we ended up printing to
|
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
||||||
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
/// @return the max y we ended up printing to
|
||||||
{
|
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
|
{
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
@@ -170,47 +178,76 @@ static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
|
||||||
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, PowerStatus *powerStatus) {
|
static 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 };
|
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
|
// Clear the bar area on the battery image
|
||||||
for (int i = 1; i < 14; i++) {
|
for (int i = 1; i < 14; i++) {
|
||||||
imgBuffer[i] = 0x81;
|
imgBuffer[i] = 0x81;
|
||||||
}
|
}
|
||||||
// If charging, draw a charging indicator
|
// If charging, draw a charging indicator
|
||||||
if (powerStatus->charging) {
|
if (powerStatus->getIsCharging()) {
|
||||||
memcpy(imgBuffer + 3, lightning, 8);
|
memcpy(imgBuffer + 3, lightning, 8);
|
||||||
// If not charging, Draw power bars
|
// If not charging, Draw power bars
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if(powerStatus->batteryChargePercent >= 25 * i) memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
|
if (powerStatus->getBatteryChargePercent() >= 25 * i)
|
||||||
|
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
display->drawFastImage(x, y, 16, 8, imgBuffer);
|
display->drawFastImage(x, y, 16, 8, imgBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw nodes status
|
// Draw nodes status
|
||||||
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, int nodesOnline, int nodesTotal) {
|
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *nodeStatus)
|
||||||
|
{
|
||||||
char usersString[20];
|
char usersString[20];
|
||||||
sprintf(usersString, "%d/%d", nodesOnline, nodesTotal);
|
sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
|
||||||
display->drawFastImage(x, y, 8, 8, imgUser);
|
display->drawFastImage(x, y, 8, 8, imgUser);
|
||||||
display->drawString(x + 10, y - 2, usersString);
|
display->drawString(x + 10, y - 2, usersString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw GPS status summary
|
// Draw GPS status summary
|
||||||
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, GPS *gps) {
|
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||||
if(!gps->isConnected) { display->drawString(x, y - 2, "No GPS"); return; }
|
{
|
||||||
display->drawFastImage(x, y, 6, 8, gps->hasLock() ? imgPositionSolid : imgPositionEmpty );
|
if (!gps->getIsConnected()) {
|
||||||
if(!gps->hasLock()) { display->drawString(x + 8, y - 2, "No sats"); return; }
|
display->drawString(x, y - 2, "No GPS");
|
||||||
if(gps->dop <= 100) { display->drawString(x + 8, y - 2, "Ideal"); return; }
|
return;
|
||||||
if(gps->dop <= 200) { display->drawString(x + 8, y - 2, "Exc."); return; }
|
}
|
||||||
if(gps->dop <= 500) { display->drawString(x + 8, y - 2, "Good"); return; }
|
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
|
||||||
if(gps->dop <= 1000) { display->drawString(x + 8, y - 2, "Mod."); return; }
|
if (!gps->getHasLock()) {
|
||||||
if(gps->dop <= 2000) { display->drawString(x + 8, y - 2, "Fair"); return; }
|
display->drawString(x + 8, y - 2, "No sats");
|
||||||
if(gps->dop > 0) { display->drawString(x + 8, y - 2, "Poor"); return; }
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 100) {
|
||||||
|
display->drawString(x + 8, y - 2, "Ideal");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 200) {
|
||||||
|
display->drawString(x + 8, y - 2, "Exc.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 500) {
|
||||||
|
display->drawString(x + 8, y - 2, "Good");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 1000) {
|
||||||
|
display->drawString(x + 8, y - 2, "Mod.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() <= 2000) {
|
||||||
|
display->drawString(x + 8, y - 2, "Fair");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gps->getDOP() > 0) {
|
||||||
|
display->drawString(x + 8, y - 2, "Poor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ported from my old java code, returns distance in meters along the globe
|
/// Ported from my old java code, returns distance in meters along the globe
|
||||||
@@ -421,11 +458,10 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
*distStr = 0; // might not have location data
|
strcpy(distStr, "? km"); // might not have location data
|
||||||
float headingRadian;
|
float headingRadian;
|
||||||
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
||||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||||
drawColumns(display, x, y, fields);
|
|
||||||
|
|
||||||
// coordinates for the center of the compass/circle
|
// coordinates for the center of the compass/circle
|
||||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||||
@@ -448,9 +484,12 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
// Debug info for gps lock errors
|
// Debug info for gps lock errors
|
||||||
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
// DEBUG_MSG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasPosition(ourNode), hasPosition(node));
|
||||||
|
|
||||||
display->drawString(compassX - FONT_HEIGHT/4, compassY - FONT_HEIGHT/2, "?");
|
display->drawString(compassX - FONT_HEIGHT / 4, compassY - FONT_HEIGHT / 2, "?");
|
||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must be after distStr is populated
|
||||||
|
drawColumns(display, x, y, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -545,6 +584,11 @@ void Screen::setup()
|
|||||||
// twice initially.
|
// twice initially.
|
||||||
ui.update();
|
ui.update();
|
||||||
ui.update();
|
ui.update();
|
||||||
|
|
||||||
|
// Subscribe to status updates
|
||||||
|
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||||
|
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||||
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::doTask()
|
||||||
@@ -606,14 +650,7 @@ void Screen::doTask()
|
|||||||
// While showing the bootscreen or Bluetooth pair screen all of our
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
// standard screen switching is stopped.
|
// standard screen switching is stopped.
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// TODO(girts): decouple nodeDB from screen.
|
// standard screen loop handling here
|
||||||
// standard screen loop handling ehre
|
|
||||||
// If the # nodes changes, we need to regen our list of screens
|
|
||||||
if (nodeDB.updateGUI || nodeDB.updateTextMessage) {
|
|
||||||
setFrames();
|
|
||||||
nodeDB.updateGUI = false;
|
|
||||||
nodeDB.updateTextMessage = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.update();
|
ui.update();
|
||||||
@@ -638,8 +675,8 @@ void Screen::setFrames()
|
|||||||
DEBUG_MSG("showing standard frames\n");
|
DEBUG_MSG("showing standard frames\n");
|
||||||
showingNormalScreen = true;
|
showingNormalScreen = true;
|
||||||
|
|
||||||
size_t numnodes = nodeDB.getNumNodes();
|
|
||||||
// We don't show the node info our our node (if we have it yet - we should)
|
// We don't show the node info our our node (if we have it yet - we should)
|
||||||
|
size_t numnodes = nodeStatus->getNumTotal();
|
||||||
if (numnodes > 0)
|
if (numnodes > 0)
|
||||||
numnodes--;
|
numnodes--;
|
||||||
|
|
||||||
@@ -719,32 +756,52 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
|
snprintf(channelStr, sizeof(channelStr), "#%s", channelName.c_str());
|
||||||
|
|
||||||
// Display power status
|
// Display power status
|
||||||
if (powerStatus.haveBattery) drawBattery(display, x, y + 2, imgBattery, &powerStatus); else display->drawFastImage(x, y + 2, 16, 8, powerStatus.usb ? imgUSB : imgPower);
|
if (powerStatus->getHasBattery())
|
||||||
|
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||||||
|
else
|
||||||
|
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||||
// Display nodes status
|
// Display nodes status
|
||||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodesOnline, nodesTotal);
|
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||||
// Display GPS status
|
// Display GPS status
|
||||||
drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gps);
|
drawGPS(display, x + (SCREEN_WIDTH * 0.66), y + 2, gpsStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fields[] = {channelStr, nullptr};
|
display->drawString(x, y + FONT_HEIGHT, channelStr);
|
||||||
uint32_t yo = drawRows(display, x, y + FONT_HEIGHT, fields);
|
|
||||||
|
|
||||||
display->drawLogBuffer(x, yo);
|
display->drawLogBuffer(x, y + (FONT_HEIGHT * 2));
|
||||||
|
|
||||||
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
|
#ifdef SHOW_REDRAWS
|
||||||
|
if (heartbeat)
|
||||||
|
display->setPixel(0, 0);
|
||||||
|
heartbeat = !heartbeat;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
|
// adjust Brightness cycle trough 1 to 254 as long as attachDuringLongPress is true
|
||||||
void Screen::adjustBrightness(){
|
void Screen::adjustBrightness()
|
||||||
if (brightness == 254)
|
{
|
||||||
{
|
if (brightness == 254) {
|
||||||
brightness = 0;
|
brightness = 0;
|
||||||
} else {
|
} else {
|
||||||
brightness++;
|
brightness++;
|
||||||
}
|
}
|
||||||
int width = brightness / 1.984375;
|
int width = brightness / (254.00 / SCREEN_WIDTH);
|
||||||
dispdev.drawRect( 0, 30, 128, 4);
|
dispdev.drawRect(0, 30, SCREEN_WIDTH, 4);
|
||||||
dispdev.fillRect(0, 30, width, 4);
|
dispdev.fillRect(0, 31, width, 2);
|
||||||
dispdev.display();
|
dispdev.display();
|
||||||
dispdev.setBrightness(brightness);
|
dispdev.setBrightness(brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Screen::handleStatusUpdate(const Status *arg)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Screen got status update %d\n", arg->getStatusType());
|
||||||
|
switch (arg->getStatusType()) {
|
||||||
|
case STATUS_TYPE_NODE:
|
||||||
|
setFrames();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
setPeriod(1); // Update the screen right away
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
} // namespace meshtastic
|
} // namespace meshtastic
|
||||||
|
|||||||
45
src/screen.h
45
src/screen.h
@@ -13,7 +13,10 @@
|
|||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "TypedQueue.h"
|
#include "TypedQueue.h"
|
||||||
#include "lock.h"
|
#include "lock.h"
|
||||||
#include "power.h"
|
#include "PowerStatus.h"
|
||||||
|
#include "GPSStatus.h"
|
||||||
|
#include "NodeStatus.h"
|
||||||
|
#include "Observer.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace meshtastic
|
namespace meshtastic
|
||||||
@@ -29,14 +32,6 @@ class DebugInfo
|
|||||||
DebugInfo(const DebugInfo &) = delete;
|
DebugInfo(const DebugInfo &) = delete;
|
||||||
DebugInfo &operator=(const DebugInfo &) = delete;
|
DebugInfo &operator=(const DebugInfo &) = delete;
|
||||||
|
|
||||||
/// Sets user statistics.
|
|
||||||
void setNodeNumbersStatus(int online, int total)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
nodesOnline = online;
|
|
||||||
nodesTotal = total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the name of the channel.
|
/// Sets the name of the channel.
|
||||||
void setChannelNameStatus(const char *name)
|
void setChannelNameStatus(const char *name)
|
||||||
{
|
{
|
||||||
@@ -44,25 +39,6 @@ class DebugInfo
|
|||||||
channelName = name;
|
channelName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets battery/charging/etc status.
|
|
||||||
//
|
|
||||||
void setPowerStatus(const PowerStatus &status)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
powerStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets GPS status.
|
|
||||||
//
|
|
||||||
// If this function never gets called, we assume GPS does not exist on this
|
|
||||||
// device.
|
|
||||||
// TODO(girts): figure out what the format should be.
|
|
||||||
void setGPSStatus(const char *status)
|
|
||||||
{
|
|
||||||
LockGuard guard(&lock);
|
|
||||||
gpsStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend Screen;
|
friend Screen;
|
||||||
|
|
||||||
@@ -71,15 +47,8 @@ class DebugInfo
|
|||||||
/// Renders the debug screen.
|
/// Renders the debug screen.
|
||||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
int nodesOnline = 0;
|
|
||||||
int nodesTotal = 0;
|
|
||||||
|
|
||||||
PowerStatus powerStatus;
|
|
||||||
|
|
||||||
std::string channelName;
|
std::string channelName;
|
||||||
|
|
||||||
std::string gpsStatus;
|
|
||||||
|
|
||||||
/// Protects all of internal state.
|
/// Protects all of internal state.
|
||||||
Lock lock;
|
Lock lock;
|
||||||
};
|
};
|
||||||
@@ -93,6 +62,10 @@ class DebugInfo
|
|||||||
// simultaneously).
|
// simultaneously).
|
||||||
class Screen : public PeriodicTask
|
class Screen : public PeriodicTask
|
||||||
{
|
{
|
||||||
|
CallbackObserver<Screen, const Status *> powerStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
CallbackObserver<Screen, const Status *> gpsStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
CallbackObserver<Screen, const Status *> nodeStatusObserver = CallbackObserver<Screen, const Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Screen(uint8_t address, int sda = -1, int scl = -1);
|
Screen(uint8_t address, int sda = -1, int scl = -1);
|
||||||
|
|
||||||
@@ -189,6 +162,8 @@ class Screen : public PeriodicTask
|
|||||||
// Use this handle to set things like battery status, user count, GPS status, etc.
|
// Use this handle to set things like battery status, user count, GPS status, etc.
|
||||||
DebugInfo *debug() { return &debugInfo; }
|
DebugInfo *debug() { return &debugInfo; }
|
||||||
|
|
||||||
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Updates the UI.
|
/// Updates the UI.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user