mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-19 16:27:28 +00:00
Compare commits
20 Commits
status-mes
...
crowpanelV
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86fa917362 | ||
|
|
3575c48e36 | ||
|
|
469abe8dec | ||
|
|
fefce15b1a | ||
|
|
50ea7ace98 | ||
|
|
96f371eafd | ||
|
|
dbc52c7576 | ||
|
|
232e0412ff | ||
|
|
b94659ff6a | ||
|
|
059e17b3e2 | ||
|
|
b09f6db9e2 | ||
|
|
64706153e0 | ||
|
|
f94043e4f2 | ||
|
|
bc82cd803c | ||
|
|
c15b6de138 | ||
|
|
fbf32b93b6 | ||
|
|
b7289f7f2d | ||
|
|
d3d932ccee | ||
|
|
3a267b440d | ||
|
|
b1821bfd4f |
@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
|
||||
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
pip install --no-cache-dir -U \
|
||||
platformio==6.1.16 \
|
||||
|
||||
4
.github/workflows/build_firmware.yml
vendored
4
.github/workflows/build_firmware.yml
vendored
@@ -91,8 +91,8 @@ jobs:
|
||||
if [[ -f "$manifest" ]]; then
|
||||
echo "Updating $manifest with $OTA_FILE (md5: $OTA_MD5, size: $OTA_SIZE)"
|
||||
# Add OTA entry to files array if not already present
|
||||
jq --arg name "$OTA_FILE" --arg md5 "$OTA_MD5" --argjson bytes "$OTA_SIZE" --arg part "app1" \
|
||||
'if .files | map(select(.name == $name)) | length == 0 then .files += [{"name": $name, "md5": $md5, "bytes": $bytes, "part_name": $part}] else . end' \
|
||||
jq --arg name "$OTA_FILE" --arg md5 "$OTA_MD5" --argjson bytes "$OTA_SIZE" \
|
||||
'if .files | map(select(.name == $name)) | length == 0 then .files += [{"name": $name, "md5": $md5, "bytes": $bytes}] else . end' \
|
||||
"$manifest" > "${manifest}.tmp" && mv "${manifest}.tmp" "$manifest"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -9,10 +9,10 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.497
|
||||
- renovate@42.81.8
|
||||
- prettier@3.8.0
|
||||
- renovate@42.78.2
|
||||
- prettier@3.7.4
|
||||
- trufflehog@3.92.4
|
||||
- yamllint@1.38.0
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.9.2
|
||||
- trivy@0.68.2
|
||||
- taplo@0.10.0
|
||||
|
||||
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl wget g++ zip git ca-certificates pkg-config \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
|
||||
@@ -60,14 +60,6 @@ def manifest_gather(source, target, env):
|
||||
board_platform = env.BoardConfig().get("platform")
|
||||
board_mcu = env.BoardConfig().get("build.mcu").lower()
|
||||
needs_ota_suffix = board_platform == "nordicnrf52"
|
||||
|
||||
# Mapping of bin files to their target partition names
|
||||
# Maps the filename pattern to the partition name where it should be flashed
|
||||
partition_map = {
|
||||
f"{progname}.bin": "app0", # primary application slot (app0 / OTA_0)
|
||||
lfsbin: "spiffs", # filesystem image flashed to spiffs
|
||||
}
|
||||
|
||||
check_paths = [
|
||||
progname,
|
||||
f"{progname}.elf",
|
||||
@@ -93,9 +85,6 @@ def manifest_gather(source, target, env):
|
||||
"md5": f.get_content_hash(), # Returns MD5 hash
|
||||
"bytes": f.get_size() # Returns file size in bytes
|
||||
}
|
||||
# Add part_name if this file represents a partition that should be flashed
|
||||
if p in partition_map:
|
||||
d["part_name"] = partition_map[p]
|
||||
out.append(d)
|
||||
print(d)
|
||||
manifest_write(out, env)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default.csv",
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "CDEBYTE_EoRa-Hub",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.cdebyte.com/products/EoRa-HUB-900TB",
|
||||
"vendor": "CDEBYTE"
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M4 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "elecrow_thinknode_m4",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M4",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ELECROW ThinkNode m4",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.elecrow.com/thinknode-m4-power-bank-lora-device-with-meshtastic-lora-tracker-function-powered-by-nrf52840.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DT_WATCH_S3",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
3
debian/control
vendored
3
debian/control
vendored
@@ -25,8 +25,7 @@ Build-Depends: debhelper-compat (= 13),
|
||||
liborcania-dev,
|
||||
libx11-dev,
|
||||
libinput-dev,
|
||||
libxkbcommon-x11-dev,
|
||||
libsqlite3-dev
|
||||
libxkbcommon-x11-dev
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/meshtastic/firmware
|
||||
Rules-Requires-Root: no
|
||||
|
||||
@@ -39,7 +39,6 @@ BuildRequires: pkgconfig(bluez)
|
||||
BuildRequires: pkgconfig(libusb-1.0)
|
||||
BuildRequires: libi2c-devel
|
||||
BuildRequires: pkgconfig(libuv)
|
||||
BuildRequires: pkgconfig(sqlite3)
|
||||
# Web components:
|
||||
BuildRequires: pkgconfig(openssl)
|
||||
BuildRequires: pkgconfig(liborcania)
|
||||
|
||||
@@ -119,7 +119,7 @@ lib_deps =
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip
|
||||
https://github.com/meshtastic/device-ui/archive/14dc991a4b57066e0a83599be1a87ff08e5e5308.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -129,7 +129,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
|
||||
adafruit/Adafruit Unified Sensor@1.1.15
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
|
||||
adafruit/Adafruit BMP280 Library@3.0.0
|
||||
adafruit/Adafruit BMP280 Library@2.6.8
|
||||
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
|
||||
adafruit/Adafruit BMP085 Library@1.2.4
|
||||
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
|
||||
@@ -142,6 +142,8 @@ lib_deps =
|
||||
adafruit/Adafruit INA260 Library@1.5.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
|
||||
adafruit/Adafruit INA219@1.2.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
|
||||
adafruit/Adafruit PM25 AQI Sensor@2.0.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
|
||||
adafruit/Adafruit MPU6050@2.2.6
|
||||
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
|
||||
|
||||
Submodule protobufs updated: 4b9f104a18...61219de748
@@ -13,11 +13,6 @@
|
||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
||||
#endif
|
||||
|
||||
// Default autosave interval 2 hours, override per device later with -DMESSAGE_AUTOSAVE_INTERVAL_SEC=300 (etc)
|
||||
#ifndef MESSAGE_AUTOSAVE_INTERVAL_SEC
|
||||
#define MESSAGE_AUTOSAVE_INTERVAL_SEC (2 * 60 * 60)
|
||||
#endif
|
||||
|
||||
// Global message text pool and state
|
||||
static char *g_messagePool = nullptr;
|
||||
static size_t g_poolWritePos = 0;
|
||||
@@ -107,60 +102,6 @@ void MessageStore::addLiveMessage(const StoredMessage &msg)
|
||||
pushWithLimit(liveMessages, msg);
|
||||
}
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
static bool g_messageStoreHasUnsavedChanges = false;
|
||||
static uint32_t g_lastAutoSaveMs = 0; // last time we actually saved
|
||||
|
||||
static inline uint32_t autosaveIntervalMs()
|
||||
{
|
||||
uint32_t sec = (uint32_t)MESSAGE_AUTOSAVE_INTERVAL_SEC;
|
||||
if (sec < 60)
|
||||
sec = 60;
|
||||
return sec * 1000UL;
|
||||
}
|
||||
|
||||
static inline bool reachedMs(uint32_t now, uint32_t target)
|
||||
{
|
||||
return (int32_t)(now - target) >= 0;
|
||||
}
|
||||
|
||||
// Mark new messages in RAM that need to be saved later
|
||||
static inline void markMessageStoreUnsaved()
|
||||
{
|
||||
g_messageStoreHasUnsavedChanges = true;
|
||||
|
||||
if (g_lastAutoSaveMs == 0) {
|
||||
g_lastAutoSaveMs = millis();
|
||||
}
|
||||
}
|
||||
|
||||
// Called periodically from the main loop in main.cpp
|
||||
static inline void autosaveTick(MessageStore *store)
|
||||
{
|
||||
if (!store)
|
||||
return;
|
||||
|
||||
uint32_t now = millis();
|
||||
|
||||
if (g_lastAutoSaveMs == 0) {
|
||||
g_lastAutoSaveMs = now;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reachedMs(now, g_lastAutoSaveMs + autosaveIntervalMs()))
|
||||
return;
|
||||
|
||||
// Autosave interval reached, only save if there are unsaved messages.
|
||||
if (g_messageStoreHasUnsavedChanges) {
|
||||
LOG_INFO("Autosaving MessageStore to flash");
|
||||
store->saveToFlash();
|
||||
} else {
|
||||
LOG_INFO("Autosave skipped, no changes to save");
|
||||
g_lastAutoSaveMs = now;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add from incoming/outgoing packet
|
||||
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
||||
{
|
||||
@@ -190,11 +131,6 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
||||
}
|
||||
|
||||
addLiveMessage(sm);
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
markMessageStoreUnsaved();
|
||||
#endif
|
||||
|
||||
return liveMessages.back();
|
||||
}
|
||||
|
||||
@@ -219,10 +155,6 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
||||
sm.ackStatus = AckStatus::NONE;
|
||||
|
||||
addLiveMessage(sm);
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
markMessageStoreUnsaved();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
@@ -307,10 +239,6 @@ void MessageStore::saveToFlash()
|
||||
|
||||
f.close();
|
||||
#endif
|
||||
|
||||
// Reset autosave state after any save
|
||||
g_messageStoreHasUnsavedChanges = false;
|
||||
g_lastAutoSaveMs = millis();
|
||||
}
|
||||
|
||||
void MessageStore::loadFromFlash()
|
||||
@@ -342,9 +270,6 @@ void MessageStore::loadFromFlash()
|
||||
|
||||
f.close();
|
||||
#endif
|
||||
// Loading messages does not trigger an autosave
|
||||
g_messageStoreHasUnsavedChanges = false;
|
||||
g_lastAutoSaveMs = millis();
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -365,11 +290,6 @@ void MessageStore::clearAllMessages()
|
||||
f.write(&count, 1); // write "0 messages"
|
||||
f.close();
|
||||
#endif
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
g_messageStoreHasUnsavedChanges = false;
|
||||
g_lastAutoSaveMs = millis();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Internal helper: erase first or last message matching a predicate
|
||||
@@ -501,14 +421,6 @@ uint16_t MessageStore::storeText(const char *src, size_t len)
|
||||
return storeTextInPool(src, len);
|
||||
}
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
void messageStoreAutosaveTick()
|
||||
{
|
||||
// Called from the main loop to check autosave timing
|
||||
autosaveTick(&messageStore);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Global definition
|
||||
MessageStore messageStore("default");
|
||||
#endif
|
||||
#endif
|
||||
@@ -125,11 +125,6 @@ class MessageStore
|
||||
std::string filename; // Flash filename for persistence
|
||||
};
|
||||
|
||||
#if ENABLE_MESSAGE_PERSISTENCE
|
||||
// Called periodically from main loop to trigger time based autosave
|
||||
void messageStoreAutosaveTick();
|
||||
#endif
|
||||
|
||||
// Global instance (defined in MessageStore.cpp)
|
||||
extern MessageStore messageStore;
|
||||
|
||||
|
||||
138
src/Power.cpp
138
src/Power.cpp
@@ -476,9 +476,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
|
||||
}
|
||||
#endif
|
||||
#if defined(ELECROW_ThinkNode_M6)
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
|
||||
#elif EXT_CHRG_DETECT
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
#elif defined(BATTERY_CHARGING_INV)
|
||||
return !digitalRead(BATTERY_CHARGING_INV);
|
||||
@@ -695,8 +693,6 @@ bool Power::setup()
|
||||
found = true;
|
||||
} else if (lipoChargerInit()) {
|
||||
found = true;
|
||||
} else if (serialBatteryInit()) {
|
||||
found = true;
|
||||
} else if (meshSolarInit()) {
|
||||
found = true;
|
||||
} else if (analogInit()) {
|
||||
@@ -1573,135 +1569,3 @@ bool Power::meshSolarInit()
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SERIAL_BATTERY_LEVEL
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
/**
|
||||
* SerialBatteryLevel class for pulling battery information from a secondary MCU over serial.
|
||||
*/
|
||||
class SerialBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Init the I2C meshSolar battery level sensor
|
||||
*/
|
||||
bool runOnce()
|
||||
{
|
||||
BatterySerial.begin(4800);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*/
|
||||
virtual int getBatteryPercent() override { return v_percent; }
|
||||
|
||||
/**
|
||||
* The raw voltage of the battery in millivolts, or NAN if unknown
|
||||
*/
|
||||
virtual uint16_t getBattVoltage() override { return voltage * 1000; }
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() override
|
||||
{
|
||||
// definitely need to gobble up more bytes at once
|
||||
if (BatterySerial.available() > 5) {
|
||||
// LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available());
|
||||
while (BatterySerial.available() > 11) {
|
||||
BatterySerial.read(); // flush old data
|
||||
}
|
||||
// LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available());
|
||||
int tries = 0;
|
||||
while (BatterySerial.read() != 0xFE) {
|
||||
tries++; // wait for start byte
|
||||
if (tries > 10) {
|
||||
LOG_WARN("SerialBatteryLevel: no start byte found");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Data[1] = BatterySerial.read();
|
||||
Data[2] = BatterySerial.read();
|
||||
Data[3] = BatterySerial.read();
|
||||
Data[4] = BatterySerial.read();
|
||||
Data[5] = BatterySerial.read();
|
||||
if (Data[5] != 0xFD) {
|
||||
LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]);
|
||||
return true;
|
||||
}
|
||||
v_percent = Data[1];
|
||||
voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000);
|
||||
voltage *= 2;
|
||||
// LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]);
|
||||
return true;
|
||||
}
|
||||
// This function runs first, so use it to grab the latest data from the secondary MCU
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if there is an external power source detected
|
||||
*/
|
||||
virtual bool isVbusIn() override
|
||||
{
|
||||
#if defined(EXT_CHRG_DETECT)
|
||||
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
// by default, we check the battery voltage only
|
||||
return isVbusIn();
|
||||
}
|
||||
|
||||
private:
|
||||
SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX);
|
||||
uint8_t Data[6] = {0};
|
||||
int v_percent = 0;
|
||||
float voltage = 0.0;
|
||||
};
|
||||
|
||||
SerialBatteryLevel serialBatteryLevel;
|
||||
|
||||
/**
|
||||
* Init the serial battery level sensor
|
||||
*/
|
||||
bool Power::serialBatteryInit()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||
#endif
|
||||
|
||||
bool result = serialBatteryLevel.runOnce();
|
||||
LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet");
|
||||
if (!result)
|
||||
return false;
|
||||
batteryLevel = &serialBatteryLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* If this device has no serial battery level sensor, don't try to use it.
|
||||
*/
|
||||
bool Power::serialBatteryInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -54,7 +54,7 @@ size_t SafeFile::write(const uint8_t *buffer, size_t size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically close the file (overwriting any old version) and readback the contents to confirm the hash matches
|
||||
* Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
|
||||
*
|
||||
* @return false for failure
|
||||
*/
|
||||
@@ -73,7 +73,15 @@ bool SafeFile::close()
|
||||
if (!testReadback())
|
||||
return false;
|
||||
|
||||
// Rename or overwrite (atomic operation)
|
||||
{ // Scope for lock
|
||||
concurrency::LockGuard g(spiLock);
|
||||
// brief window of risk here ;-)
|
||||
if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
|
||||
LOG_ERROR("Can't remove old pref file");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String filenameTmp = filename;
|
||||
filenameTmp += ".tmp";
|
||||
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
|
||||
|
||||
@@ -172,12 +172,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
// OLED & Input
|
||||
// -----------------------------------------------------------------------------
|
||||
#define SSD1306_ADDRESS_L 0x3C // Addr = 0
|
||||
#define SSD1306_ADDRESS_H 0x3D // Addr = 1
|
||||
|
||||
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
|
||||
#define SSD1306_ADDRESS SSD1306_ADDRESS_H
|
||||
#define SSD1306_ADDRESS 0x3D
|
||||
#define USE_SH1106
|
||||
#else
|
||||
#define SSD1306_ADDRESS 0x3C
|
||||
#endif
|
||||
#define ST7567_ADDRESS 0x3F
|
||||
|
||||
@@ -206,7 +205,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define INA_ADDR_WAVESHARE_UPS 0x43
|
||||
#define INA3221_ADDR 0x42
|
||||
#define MAX1704X_ADDR 0x36
|
||||
#define QMC6310U_ADDR 0x1C
|
||||
#define QMC6310_ADDR 0x1C
|
||||
#define QMI8658_ADDR 0x6B
|
||||
#define QMC5883L_ADDR 0x0D
|
||||
#define HMC5883L_ADDR 0x1E
|
||||
@@ -215,7 +214,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
#define SHT31_4x_ADDR 0x44
|
||||
#define SHT31_4x_ADDR_ALT 0x45
|
||||
#define PMSA003I_ADDR 0x12
|
||||
#define PMSA0031_ADDR 0x12
|
||||
#define QMA6100P_ADDR 0x12
|
||||
#define AHT10_ADDR 0x38
|
||||
#define RCWL9620_ADDR 0x57
|
||||
@@ -270,6 +269,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define TCA9535_ADDR 0x20
|
||||
#define TCA9555_ADDR 0x26
|
||||
|
||||
// used for display brightness control
|
||||
#define STC8H1K28_ADDR 0x30
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Touchscreen
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -481,7 +483,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MESHTASTIC_EXCLUDE_AUDIO 1
|
||||
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR 1
|
||||
#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
|
||||
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
|
||||
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
|
||||
|
||||
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
|
||||
ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
|
||||
return firstOfOrNONE(2, types);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,12 +35,11 @@ class ScanI2C
|
||||
SHT4X,
|
||||
SHTC3,
|
||||
LPS22HB,
|
||||
QMC6310U,
|
||||
QMC6310N,
|
||||
QMC6310,
|
||||
QMI8658,
|
||||
QMC5883L,
|
||||
HMC5883L,
|
||||
PMSA003I,
|
||||
PMSA0031,
|
||||
QMA6100P,
|
||||
MPU6050,
|
||||
LIS3DH,
|
||||
@@ -63,6 +62,7 @@ class ScanI2C
|
||||
NAU7802,
|
||||
FT6336U,
|
||||
STK8BAXX,
|
||||
STC8H1K28,
|
||||
ICM20948,
|
||||
SCD4X,
|
||||
MAX30102,
|
||||
|
||||
@@ -63,10 +63,6 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
|
||||
if (i2cBus->available()) {
|
||||
r = i2cBus->read();
|
||||
}
|
||||
if (r == 0x80) {
|
||||
LOG_INFO("QMC6310N found at address 0x%02X", addr.address);
|
||||
return ScanI2C::DeviceType::QMC6310N;
|
||||
}
|
||||
r &= 0x0f;
|
||||
|
||||
if (r == 0x08 || r == 0x00) {
|
||||
@@ -110,7 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
|
||||
if (i2cBus->available())
|
||||
i2cBus->read();
|
||||
}
|
||||
LOG_DEBUG("Register value from 0x%x: 0x%x", registerLocation.i2cAddress.address, value);
|
||||
LOG_DEBUG("Register value: 0x%x", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -179,8 +175,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
type = NONE;
|
||||
if (err == 0) {
|
||||
switch (addr.address) {
|
||||
case SSD1306_ADDRESS_H:
|
||||
case SSD1306_ADDRESS_L:
|
||||
case SSD1306_ADDRESS:
|
||||
type = probeOLED(addr);
|
||||
break;
|
||||
|
||||
@@ -243,6 +238,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
|
||||
#else
|
||||
SCAN_SIMPLE_CASE(STC8H1K28_ADDR, LP5562, "STC8H1K28", (uint8_t)addr.address);
|
||||
#endif
|
||||
case XPOWERS_AXP192_AXP2101_ADDRESS:
|
||||
// Do we have the axp2101/192 or the TCA8418
|
||||
@@ -387,11 +384,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
|
||||
if (registerValue == 0x5449) {
|
||||
type = OPT3001;
|
||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) !=
|
||||
0) { // unique SHT4x serial number (6 bytes inc. CRC)
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else {
|
||||
@@ -417,7 +414,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
|
||||
case LPS22HB_ADDR_ALT:
|
||||
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
|
||||
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
|
||||
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310", (uint8_t)addr.address)
|
||||
|
||||
case QMI8658_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
|
||||
@@ -447,7 +444,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
#ifdef HAS_QMA6100P
|
||||
SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address)
|
||||
#else
|
||||
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
|
||||
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031", (uint8_t)addr.address)
|
||||
#endif
|
||||
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
#include "ScanI2CTwoWire.h"
|
||||
|
||||
uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus)
|
||||
{
|
||||
|
||||
uint32_t currentClock;
|
||||
|
||||
/* See https://github.com/arduino/Arduino/issues/11457
|
||||
Currently, only ESP32 can getClock()
|
||||
While all cores can setClock()
|
||||
https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
|
||||
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
|
||||
https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
|
||||
For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
|
||||
we need to reclock I2C and set it back to the previous desired speed.
|
||||
Only for cases where we can know OR predefine the speed, we can do this.
|
||||
*/
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
currentClock = i2cBus->getClock();
|
||||
#elif defined(ARCH_NRF52)
|
||||
// TODO add getClock function or return a predefined clock speed per variant?
|
||||
return 0;
|
||||
#elif defined(ARCH_RP2040)
|
||||
// TODO add getClock function or return a predefined clock speed per variant
|
||||
return 0;
|
||||
#elif defined(ARCH_STM32WL)
|
||||
// TODO add getClock function or return a predefined clock speed per variant
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if (currentClock != desiredClock) {
|
||||
LOG_DEBUG("Changing I2C clock to %u", desiredClock);
|
||||
i2cBus->setClock(desiredClock);
|
||||
}
|
||||
return currentClock;
|
||||
}
|
||||
#endif
|
||||
@@ -896,11 +896,14 @@ void GPS::writePinEN(bool on)
|
||||
void GPS::writePinStandby(bool standby)
|
||||
{
|
||||
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
|
||||
bool val;
|
||||
if (standby)
|
||||
val = GPS_STANDBY_ACTIVE;
|
||||
else
|
||||
val = !GPS_STANDBY_ACTIVE;
|
||||
|
||||
// Determine the new value for the pin
|
||||
// Normally: active HIGH for awake
|
||||
#ifdef PIN_GPS_STANDBY_INVERTED
|
||||
bool val = standby;
|
||||
#else
|
||||
bool val = !standby;
|
||||
#endif
|
||||
|
||||
// Write and log
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
#define GPS_EN_ACTIVE 1
|
||||
#endif
|
||||
|
||||
// Allow defining the polarity of the STANDBY output. default is LOW for standby
|
||||
#ifndef GPS_STANDBY_ACTIVE
|
||||
#define GPS_STANDBY_ACTIVE LOW
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
|
||||
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
|
||||
|
||||
|
||||
@@ -9,15 +9,6 @@
|
||||
#include "GxEPD2Multi.h"
|
||||
#endif
|
||||
|
||||
// Limit how often we push a full E-Ink refresh. T-Deck Pro needs faster updates for typing.
|
||||
#ifndef EINK_FORCE_DISPLAY_THROTTLE_MS
|
||||
#if defined(T_DECK_PRO)
|
||||
#define EINK_FORCE_DISPLAY_THROTTLE_MS 200
|
||||
#else
|
||||
#define EINK_FORCE_DISPLAY_THROTTLE_MS 1000
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
|
||||
*
|
||||
@@ -51,7 +42,7 @@ class EInkDisplay : public OLEDDisplay
|
||||
*
|
||||
* @return true if we did draw the screen
|
||||
*/
|
||||
virtual bool forceDisplay(uint32_t msecLimit = EINK_FORCE_DISPLAY_THROTTLE_MS);
|
||||
virtual bool forceDisplay(uint32_t msecLimit = 1000);
|
||||
|
||||
/**
|
||||
* Run any code needed to complete an update, after the physical refresh has completed.
|
||||
|
||||
@@ -825,7 +825,7 @@ int32_t Screen::runOnce()
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0 && !suppressRebootBanner) {
|
||||
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
|
||||
showSimpleBanner("Rebooting...", 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp
|
||||
} // namespace
|
||||
|
||||
menuHandler::screenMenus menuHandler::menuQueue = menu_none;
|
||||
uint32_t menuHandler::pickedNodeNum = 0;
|
||||
bool test_enabled = false;
|
||||
uint8_t test_count = 0;
|
||||
|
||||
@@ -450,7 +449,7 @@ void menuHandler::clockMenu()
|
||||
}
|
||||
void menuHandler::messageResponseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back = 0, ViewMode, DeleteMenu, ReplyMenu, MuteChannel, Aloud, enumEnd };
|
||||
enum optionsNumbers { Back = 0, ViewMode, DeleteAll, DeleteOldest, ReplyMenu, MuteChannel, Aloud, enumEnd };
|
||||
|
||||
static const char *optionsArray[enumEnd];
|
||||
static int optionsEnumArray[enumEnd];
|
||||
@@ -480,7 +479,7 @@ void menuHandler::messageResponseMenu()
|
||||
|
||||
// Delete submenu
|
||||
optionsArray[options] = "Delete";
|
||||
optionsEnumArray[options++] = DeleteMenu;
|
||||
optionsEnumArray[options++] = 900;
|
||||
|
||||
#ifdef HAS_I2S
|
||||
optionsArray[options] = "Read Aloud";
|
||||
@@ -521,10 +520,34 @@ void menuHandler::messageResponseMenu()
|
||||
nodeDB->saveToDisk();
|
||||
}
|
||||
|
||||
} else if (selected == DeleteMenu) {
|
||||
// Delete submenu
|
||||
} else if (selected == 900) {
|
||||
menuHandler::menuQueue = menuHandler::delete_messages_menu;
|
||||
screen->runNow();
|
||||
|
||||
// Delete oldest FIRST (only change)
|
||||
} else if (selected == DeleteOldest) {
|
||||
auto mode = graphics::MessageRenderer::getThreadMode();
|
||||
int ch = graphics::MessageRenderer::getThreadChannel();
|
||||
uint32_t peer = graphics::MessageRenderer::getThreadPeer();
|
||||
|
||||
if (mode == graphics::MessageRenderer::ThreadMode::ALL) {
|
||||
// Global oldest
|
||||
messageStore.deleteOldestMessage();
|
||||
} else if (mode == graphics::MessageRenderer::ThreadMode::CHANNEL) {
|
||||
// Oldest in current channel
|
||||
messageStore.deleteOldestMessageInChannel(ch);
|
||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||
// Oldest in current DM
|
||||
messageStore.deleteOldestMessageWithPeer(peer);
|
||||
}
|
||||
|
||||
// Delete all messages
|
||||
} else if (selected == DeleteAll) {
|
||||
messageStore.clearAllMessages();
|
||||
graphics::MessageRenderer::clearThreadRegistries();
|
||||
graphics::MessageRenderer::clearMessageCache();
|
||||
|
||||
#ifdef HAS_I2S
|
||||
} else if (selected == Aloud) {
|
||||
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
|
||||
@@ -693,6 +716,7 @@ void menuHandler::deleteMessagesMenu()
|
||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||
messageStore.deleteOldestMessageWithPeer(peer);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -705,6 +729,7 @@ void menuHandler::deleteMessagesMenu()
|
||||
} else if (mode == graphics::MessageRenderer::ThreadMode::DIRECT) {
|
||||
messageStore.deleteAllMessagesWithPeer(peer);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1214,13 +1239,20 @@ void menuHandler::positionBaseMenu()
|
||||
|
||||
void menuHandler::nodeListMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
|
||||
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Node Actions / Settings";
|
||||
optionsEnumArray[options++] = NodePicker;
|
||||
optionsArray[options] = "Add Favorite";
|
||||
optionsEnumArray[options++] = Favorite;
|
||||
optionsArray[options] = "Trace Route";
|
||||
optionsEnumArray[options++] = TraceRoute;
|
||||
|
||||
if (currentResolution != ScreenResolution::UltraLow) {
|
||||
optionsArray[options] = "Key Verification";
|
||||
optionsEnumArray[options++] = Verify;
|
||||
}
|
||||
|
||||
if (currentResolution != ScreenResolution::UltraLow) {
|
||||
optionsArray[options] = "Show Long/Short Name";
|
||||
@@ -1235,12 +1267,18 @@ void menuHandler::nodeListMenu()
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == NodePicker) {
|
||||
menuQueue = NodePicker_menu;
|
||||
if (selected == Favorite) {
|
||||
menuQueue = add_favorite;
|
||||
screen->runNow();
|
||||
} else if (selected == Verify) {
|
||||
menuQueue = key_verification_init;
|
||||
screen->runNow();
|
||||
} else if (selected == Reset) {
|
||||
menuQueue = reset_node_db_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == TraceRoute) {
|
||||
menuQueue = trace_route_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == NodeNameLength) {
|
||||
menuHandler::menuQueue = menuHandler::node_name_length_menu;
|
||||
screen->runNow();
|
||||
@@ -1249,159 +1287,6 @@ void menuHandler::nodeListMenu()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::NodePicker()
|
||||
{
|
||||
const char *NODE_PICKER_TITLE;
|
||||
if (currentResolution == ScreenResolution::UltraLow) {
|
||||
NODE_PICKER_TITLE = "Pick Node";
|
||||
} else {
|
||||
NODE_PICKER_TITLE = "Pick A Node";
|
||||
}
|
||||
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
|
||||
LOG_INFO("Nodenum: %u", nodenum);
|
||||
// Store the selection so the Manage Node menu knows which node to operate on
|
||||
menuHandler::pickedNodeNum = nodenum;
|
||||
// Keep UI favorite context in sync (used elsewhere for some node-based actions)
|
||||
graphics::UIRenderer::currentFavoriteNodeNum = nodenum;
|
||||
menuQueue = Manage_Node_menu;
|
||||
screen->runNow();
|
||||
});
|
||||
}
|
||||
|
||||
void menuHandler::ManageNodeMenu()
|
||||
{
|
||||
// If we don't have a node selected yet, go fast exit
|
||||
auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
enum optionsNumbers { Back, Favorite, Mute, TraceRoute, KeyVerification, Ignore, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
if (node->is_favorite) {
|
||||
optionsArray[options] = "Unfavorite";
|
||||
} else {
|
||||
optionsArray[options] = "Favorite";
|
||||
}
|
||||
optionsEnumArray[options++] = Favorite;
|
||||
|
||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
||||
if (isMuted) {
|
||||
optionsArray[options] = "Unmute Notifications";
|
||||
} else {
|
||||
optionsArray[options] = "Mute Notifications";
|
||||
}
|
||||
optionsEnumArray[options++] = Mute;
|
||||
|
||||
optionsArray[options] = "Trace Route";
|
||||
optionsEnumArray[options++] = TraceRoute;
|
||||
|
||||
optionsArray[options] = "Key Verification";
|
||||
optionsEnumArray[options++] = KeyVerification;
|
||||
|
||||
if (node->is_ignored) {
|
||||
optionsArray[options] = "Unignore Node";
|
||||
} else {
|
||||
optionsArray[options] = "Ignore Node";
|
||||
}
|
||||
optionsEnumArray[options++] = Ignore;
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
|
||||
std::string title = "";
|
||||
if (node->has_user && node->user.long_name && node->user.long_name[0]) {
|
||||
title += sanitizeString(node->user.long_name).substr(0, 15);
|
||||
} else {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "%08X", (unsigned int)node->num);
|
||||
title += buf;
|
||||
}
|
||||
bannerOptions.message = title.c_str();
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == Back) {
|
||||
menuQueue = node_base_menu;
|
||||
screen->runNow();
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == Favorite) {
|
||||
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
if (n->is_favorite) {
|
||||
LOG_INFO("Removing node %08X from favorites", menuHandler::pickedNodeNum);
|
||||
nodeDB->set_favorite(false, menuHandler::pickedNodeNum);
|
||||
} else {
|
||||
LOG_INFO("Adding node %08X to favorites", menuHandler::pickedNodeNum);
|
||||
nodeDB->set_favorite(true, menuHandler::pickedNodeNum);
|
||||
}
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == Mute) {
|
||||
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) {
|
||||
n->bitfield &= ~NODEINFO_BITFIELD_IS_MUTED_MASK;
|
||||
LOG_INFO("Unmuted node %08X", menuHandler::pickedNodeNum);
|
||||
} else {
|
||||
n->bitfield |= NODEINFO_BITFIELD_IS_MUTED_MASK;
|
||||
LOG_INFO("Muted node %08X", menuHandler::pickedNodeNum);
|
||||
}
|
||||
nodeDB->notifyObservers(true);
|
||||
nodeDB->saveToDisk();
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == TraceRoute) {
|
||||
LOG_INFO("Starting traceroute to %08X", menuHandler::pickedNodeNum);
|
||||
if (traceRouteModule) {
|
||||
traceRouteModule->startTraceRoute(menuHandler::pickedNodeNum);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == KeyVerification) {
|
||||
LOG_INFO("Initiating key verification with %08X", menuHandler::pickedNodeNum);
|
||||
if (keyVerificationModule) {
|
||||
keyVerificationModule->sendInitialRequest(menuHandler::pickedNodeNum);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == Ignore) {
|
||||
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (n->is_ignored) {
|
||||
n->is_ignored = false;
|
||||
LOG_INFO("Unignoring node %08X", menuHandler::pickedNodeNum);
|
||||
} else {
|
||||
n->is_ignored = true;
|
||||
LOG_INFO("Ignoring node %08X", menuHandler::pickedNodeNum);
|
||||
}
|
||||
nodeDB->notifyObservers(true);
|
||||
nodeDB->saveToDisk();
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
return;
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::nodeNameLengthMenu()
|
||||
{
|
||||
static const NodeNameOption nodeNameOptions[] = {
|
||||
@@ -1430,7 +1315,6 @@ void menuHandler::nodeNameLengthMenu()
|
||||
}
|
||||
|
||||
config.display.use_long_node_name = option.value;
|
||||
saveUIConfig();
|
||||
LOG_INFO("Setting names to %s", option.value ? "long" : "short");
|
||||
});
|
||||
|
||||
@@ -2100,6 +1984,21 @@ void menuHandler::shutdownMenu()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::addFavoriteMenu()
|
||||
{
|
||||
const char *NODE_PICKER_TITLE;
|
||||
if (currentResolution == ScreenResolution::UltraLow) {
|
||||
NODE_PICKER_TITLE = "Node Favorite";
|
||||
} else {
|
||||
NODE_PICKER_TITLE = "Node To Favorite";
|
||||
}
|
||||
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
|
||||
LOG_WARN("Nodenum: %u", nodenum);
|
||||
nodeDB->set_favorite(true, nodenum);
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||
});
|
||||
}
|
||||
|
||||
void menuHandler::removeFavoriteMenu()
|
||||
{
|
||||
|
||||
@@ -2374,8 +2273,7 @@ void menuHandler::FrameToggles_menu()
|
||||
lora,
|
||||
clock,
|
||||
show_favorites,
|
||||
show_env_telemetry,
|
||||
show_aq_telemetry,
|
||||
show_telemetry,
|
||||
show_power,
|
||||
enumEnd
|
||||
};
|
||||
@@ -2420,11 +2318,8 @@ void menuHandler::FrameToggles_menu()
|
||||
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
|
||||
optionsEnumArray[options++] = show_favorites;
|
||||
|
||||
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Env. Telemetry" : "Show Env. Telemetry";
|
||||
optionsEnumArray[options++] = show_env_telemetry;
|
||||
|
||||
optionsArray[options] = moduleConfig.telemetry.air_quality_screen_enabled ? "Hide AQ Telemetry" : "Show AQ Telemetry";
|
||||
optionsEnumArray[options++] = show_aq_telemetry;
|
||||
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
|
||||
optionsEnumArray[options++] = show_telemetry;
|
||||
|
||||
optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
|
||||
optionsEnumArray[options++] = show_power;
|
||||
@@ -2487,14 +2382,10 @@ void menuHandler::FrameToggles_menu()
|
||||
screen->toggleFrameVisibility("show_favorites");
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == show_env_telemetry) {
|
||||
} else if (selected == show_telemetry) {
|
||||
moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == show_aq_telemetry) {
|
||||
moduleConfig.telemetry.air_quality_screen_enabled = !moduleConfig.telemetry.air_quality_screen_enabled;
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == show_power) {
|
||||
moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
@@ -2619,11 +2510,8 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case shutdown_menu:
|
||||
shutdownMenu();
|
||||
break;
|
||||
case NodePicker_menu:
|
||||
NodePicker();
|
||||
break;
|
||||
case Manage_Node_menu:
|
||||
ManageNodeMenu();
|
||||
case add_favorite:
|
||||
addFavoriteMenu();
|
||||
break;
|
||||
case remove_favorite:
|
||||
removeFavoriteMenu();
|
||||
|
||||
@@ -33,8 +33,7 @@ class menuHandler
|
||||
brightness_picker,
|
||||
reboot_menu,
|
||||
shutdown_menu,
|
||||
NodePicker_menu,
|
||||
Manage_Node_menu,
|
||||
add_favorite,
|
||||
remove_favorite,
|
||||
test_menu,
|
||||
number_test,
|
||||
@@ -56,7 +55,6 @@ class menuHandler
|
||||
DisplayUnits
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
|
||||
|
||||
static void OnboardMessage();
|
||||
static void LoraRegionPicker(uint32_t duration = 30000);
|
||||
@@ -92,8 +90,6 @@ class menuHandler
|
||||
static void BrightnessPickerMenu();
|
||||
static void rebootMenu();
|
||||
static void shutdownMenu();
|
||||
static void NodePicker();
|
||||
static void ManageNodeMenu();
|
||||
static void addFavoriteMenu();
|
||||
static void removeFavoriteMenu();
|
||||
static void traceRouteMenu();
|
||||
@@ -153,7 +149,6 @@ using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
|
||||
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
|
||||
using NodeNameOption = MenuOption<bool>;
|
||||
using PositionMenuOption = MenuOption<int>;
|
||||
using ManageNodeOption = MenuOption<int>;
|
||||
using ClockFaceOption = MenuOption<bool>;
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -176,7 +176,6 @@ int calculateMaxScroll(int totalEntries, int visibleRows)
|
||||
|
||||
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
||||
{
|
||||
x = (currentResolution == ScreenResolution::High) ? x - 2 : (currentResolution == ScreenResolution::Low) ? x - 1 : x;
|
||||
for (int y = yStart; y <= yEnd; y += 2) {
|
||||
display->setPixel(x, y);
|
||||
}
|
||||
@@ -206,11 +205,9 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
int nameMaxWidth = columnWidth - 25;
|
||||
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
||||
|
||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
||||
|
||||
char timeStr[10];
|
||||
uint32_t seconds = sinceLastSeen(node);
|
||||
@@ -237,13 +234,6 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||
}
|
||||
}
|
||||
if (node->is_ignored || isMuted) {
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||
} else {
|
||||
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
|
||||
}
|
||||
}
|
||||
|
||||
int rightEdge = x + columnWidth - timeOffset;
|
||||
if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time
|
||||
@@ -263,7 +253,6 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
int barsXOffset = columnWidth - barsOffset;
|
||||
|
||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
@@ -276,13 +265,6 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||
}
|
||||
}
|
||||
if (node->is_ignored || isMuted) {
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||
} else {
|
||||
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw signal strength bars
|
||||
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
|
||||
@@ -316,7 +298,6 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
|
||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
||||
char distStr[10] = "";
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
@@ -377,13 +358,6 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||
}
|
||||
}
|
||||
if (node->is_ignored || isMuted) {
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||
} else {
|
||||
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(distStr) > 0) {
|
||||
int offset = (currentResolution == ScreenResolution::High)
|
||||
@@ -418,7 +392,6 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
|
||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
@@ -430,13 +403,6 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||
}
|
||||
}
|
||||
if (node->is_ignored || isMuted) {
|
||||
if (currentResolution == ScreenResolution::High) {
|
||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||
} else {
|
||||
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
|
||||
@@ -93,8 +93,6 @@ int32_t RotaryEncoderInterruptBase::runOnce()
|
||||
|
||||
if (!pressDetected) {
|
||||
this->action = ROTARY_ACTION_NONE;
|
||||
} else if (now - pressStartTime < LONG_PRESS_DURATION) {
|
||||
return (20); // keep checking for long/short until time expires
|
||||
}
|
||||
|
||||
return INT32_MAX;
|
||||
|
||||
17
src/main.cpp
17
src/main.cpp
@@ -38,9 +38,6 @@
|
||||
#include "target_specific.h"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#if HAS_SCREEN
|
||||
#include "MessageStore.h"
|
||||
#endif
|
||||
|
||||
#ifdef ELECROW_ThinkNode_M5
|
||||
PCA9557 io(0x18, &Wire);
|
||||
@@ -574,7 +571,6 @@ void setup()
|
||||
Wire.setSCL(I2C_SCL);
|
||||
Wire.begin();
|
||||
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
|
||||
LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL);
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
if (portduino_config.i2cdev != "") {
|
||||
@@ -759,12 +755,11 @@ void setup()
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310U, meshtastic_TelemetrySensorType_QMC6310);
|
||||
// TODO: Types need to be added meshtastic_TelemetrySensorType_QMC6310N
|
||||
// scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310N, meshtastic_TelemetrySensorType_QMC6310N);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
@@ -1543,9 +1538,8 @@ void setup()
|
||||
}
|
||||
|
||||
#endif
|
||||
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
|
||||
bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for OTA handoff)
|
||||
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
|
||||
|
||||
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
|
||||
// This will suppress the current delay and instead try to run ASAP.
|
||||
@@ -1658,9 +1652,6 @@ void loop()
|
||||
if (dispdev)
|
||||
static_cast<TFTDisplay *>(dispdev)->sdlLoop();
|
||||
}
|
||||
#endif
|
||||
#if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE
|
||||
messageStoreAutosaveTick();
|
||||
#endif
|
||||
long delayMsec = mainController.runOrDelay();
|
||||
|
||||
|
||||
@@ -81,7 +81,6 @@ extern uint32_t timeLastPowered;
|
||||
|
||||
extern uint32_t rebootAtMsec;
|
||||
extern uint32_t shutdownAtMsec;
|
||||
extern bool suppressRebootBanner;
|
||||
|
||||
extern uint32_t serialSinceMsec;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
|
||||
#include <MeshtasticOTA.h>
|
||||
#include <WiFiOTA.h>
|
||||
#endif
|
||||
|
||||
NodeDB *nodeDB = nullptr;
|
||||
@@ -756,8 +756,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
config.display.compass_orientation = COMPASS_ORIENTATION;
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
|
||||
if (MeshtasticOTA::isUpdated()) {
|
||||
MeshtasticOTA::recoverConfig(&config.network);
|
||||
if (WiFiOTA::isUpdated()) {
|
||||
WiFiOTA::recoverConfig(&config.network);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -823,7 +823,7 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
|
||||
defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6)
|
||||
defined(ELECROW_ThinkNode_M6)
|
||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = PIN_LED2;
|
||||
@@ -1264,23 +1264,6 @@ void NodeDB::loadFromDisk()
|
||||
if ((state != LoadFileResult::LOAD_SUCCESS) || (devicestate.version < DEVICESTATE_MIN_VER)) {
|
||||
LOG_WARN("Devicestate %d is old or invalid, discard", devicestate.version);
|
||||
installDefaultDeviceState();
|
||||
|
||||
// Attempt recovery of owner fields from our own NodeDB entry if available.
|
||||
meshtastic_NodeInfoLite *us = getMeshNode(getNodeNum());
|
||||
if (us && us->has_user) {
|
||||
LOG_WARN("Restoring owner fields (long_name/short_name/is_licensed/is_unmessagable) from NodeDB for our node 0x%08x",
|
||||
us->num);
|
||||
memcpy(owner.long_name, us->user.long_name, sizeof(owner.long_name));
|
||||
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
||||
memcpy(owner.short_name, us->user.short_name, sizeof(owner.short_name));
|
||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||
owner.is_licensed = us->user.is_licensed;
|
||||
owner.has_is_unmessagable = us->user.has_is_unmessagable;
|
||||
owner.is_unmessagable = us->user.is_unmessagable;
|
||||
|
||||
// Save the recovered owner to device state on disk
|
||||
saveToDisk(SEGMENT_DEVICESTATE);
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Loaded saved devicestate version %d", devicestate.version);
|
||||
}
|
||||
@@ -1410,14 +1393,6 @@ void NodeDB::loadFromDisk()
|
||||
if (portduino_config.has_configDisplayMode) {
|
||||
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
|
||||
}
|
||||
if (portduino_config.has_statusMessage) {
|
||||
moduleConfig.has_statusmessage = true;
|
||||
strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(), sizeof(moduleConfig.statusmessage.node_status));
|
||||
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
|
||||
}
|
||||
if (portduino_config.enable_UDP) {
|
||||
config.network.enabled_protocols = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -1518,7 +1493,6 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||
moduleConfig.has_ambient_lighting = true;
|
||||
moduleConfig.has_audio = true;
|
||||
moduleConfig.has_paxcounter = true;
|
||||
moduleConfig.has_statusmessage = true;
|
||||
|
||||
success &=
|
||||
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
||||
|
||||
@@ -66,7 +66,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops. */
|
||||
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
|
||||
/* Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
|
||||
/* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
|
||||
|
||||
@@ -298,8 +298,6 @@ typedef enum _meshtastic_HardwareModel {
|
||||
meshtastic_HardwareModel_MESHSTICK_1262 = 121,
|
||||
/* LilyGo T-Beam 1W */
|
||||
meshtastic_HardwareModel_TBEAM_1_WATT = 122,
|
||||
/* LilyGo T5 S3 ePaper Pro (V1 and V2) */
|
||||
meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
res->print("");
|
||||
// res->print(""); @todo remove
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
res->print("");
|
||||
// res->print(""); @todo remove
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
#include "meshUtils.h"
|
||||
#include <FSCommon.h>
|
||||
#include <ctype.h> // for better whitespace handling
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#include "BleOta.h"
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
|
||||
#include "MeshtasticOTA.h"
|
||||
#include "WiFiOTA.h"
|
||||
#endif
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
@@ -233,27 +236,26 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
reboot(r->reboot_seconds);
|
||||
break;
|
||||
}
|
||||
case meshtastic_AdminMessage_ota_request_tag: {
|
||||
case meshtastic_AdminMessage_reboot_ota_seconds_tag: {
|
||||
int32_t s = r->reboot_ota_seconds;
|
||||
#if defined(ARCH_ESP32)
|
||||
if (r->ota_request.ota_hash.size != 32) {
|
||||
suppressRebootBanner = true;
|
||||
LOG_INFO("OTA Failed: Invalid `ota_hash` provided");
|
||||
break;
|
||||
}
|
||||
|
||||
meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode;
|
||||
if (MeshtasticOTA::trySwitchToOTA()) {
|
||||
LOG_INFO("OTA Requested");
|
||||
suppressRebootBanner = true;
|
||||
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
if (!BleOta::getOtaAppVersion().isEmpty()) {
|
||||
if (screen)
|
||||
screen->startFirmwareUpdateScreen();
|
||||
MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes);
|
||||
LOG_INFO("Rebooting to WiFi OTA");
|
||||
} else {
|
||||
LOG_INFO("WIFI OTA Failed");
|
||||
BleOta::switchToOtaApp();
|
||||
LOG_INFO("Rebooting to BLE OTA");
|
||||
}
|
||||
#endif
|
||||
int s = 1; // Reboot in 1 second, hard coded
|
||||
#if !MESHTASTIC_EXCLUDE_WIFI
|
||||
if (WiFiOTA::trySwitchToOTA()) {
|
||||
if (screen)
|
||||
screen->startFirmwareUpdateScreen();
|
||||
WiFiOTA::saveConfig(&config.network);
|
||||
LOG_INFO("Rebooting to WiFi OTA");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
LOG_INFO("Reboot in %d seconds", s);
|
||||
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
|
||||
break;
|
||||
@@ -976,11 +978,6 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
moduleConfig.has_paxcounter = true;
|
||||
moduleConfig.paxcounter = c.payload_variant.paxcounter;
|
||||
break;
|
||||
case meshtastic_ModuleConfig_statusmessage_tag:
|
||||
LOG_INFO("Set module config: StatusMessage");
|
||||
moduleConfig.has_statusmessage = true;
|
||||
moduleConfig.statusmessage = c.payload_variant.statusmessage;
|
||||
break;
|
||||
}
|
||||
saveChanges(SEGMENT_MODULECONFIG);
|
||||
return true;
|
||||
@@ -1161,11 +1158,6 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
|
||||
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
|
||||
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
|
||||
break;
|
||||
case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG:
|
||||
LOG_INFO("Get module config: StatusMessage");
|
||||
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag;
|
||||
res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage;
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.
|
||||
|
||||
@@ -108,9 +108,6 @@
|
||||
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
||||
#include "modules/DropzoneModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||
#include "modules/StatusMessageModule.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
|
||||
@@ -168,9 +165,6 @@ void setupModules()
|
||||
#if !MESHTASTIC_EXCLUDE_DROPZONE
|
||||
dropzoneModule = new DropzoneModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||
statusMessageModule = new StatusMessageModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
|
||||
new GenericThreadModule();
|
||||
#endif
|
||||
@@ -258,9 +252,9 @@ void setupModules()
|
||||
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
||||
new EnvironmentTelemetryModule();
|
||||
}
|
||||
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
if (moduleConfig.has_telemetry &&
|
||||
(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled)) {
|
||||
#if __has_include("Adafruit_PM25AQI.h")
|
||||
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
|
||||
new AirQualityTelemetryModule();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -64,9 +64,8 @@ SerialModule *serialModule;
|
||||
SerialModuleRadio *serialModuleRadio;
|
||||
|
||||
#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \
|
||||
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \
|
||||
defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
|
||||
|
||||
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || \
|
||||
defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
|
||||
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
@@ -206,9 +205,8 @@ int32_t SerialModule::runOnce()
|
||||
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
|
||||
}
|
||||
#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
|
||||
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
|
||||
|
||||
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
|
||||
!defined(MUZI_BASE)
|
||||
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
|
||||
#ifdef ARCH_RP2040
|
||||
Serial2.setFIFOSize(RX_BUFFER);
|
||||
@@ -265,8 +263,7 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
|
||||
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
|
||||
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
|
||||
processWXSerial();
|
||||
|
||||
@@ -542,10 +539,7 @@ void SerialModule::processWXSerial()
|
||||
{
|
||||
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
|
||||
!defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
|
||||
!defined(ELECROW_ThinkNode_M3) && \
|
||||
!defined(ELECROW_ThinkNode_M4) && \
|
||||
!defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
|
||||
|
||||
!defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
|
||||
static unsigned int lastAveraged = 0;
|
||||
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
|
||||
static double dir_sum_sin = 0;
|
||||
|
||||
@@ -13,8 +13,6 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
|
||||
{
|
||||
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
|
||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||
if (inputBroker)
|
||||
inputObserver.observe(inputBroker);
|
||||
}
|
||||
|
||||
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
@@ -62,12 +60,6 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
return 0;
|
||||
};
|
||||
|
||||
int StatusLEDModule::handleInputEvent(const InputEvent *event)
|
||||
{
|
||||
lastUserbuttonTime = millis();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t StatusLEDModule::runOnce()
|
||||
{
|
||||
my_interval = 1000;
|
||||
@@ -111,21 +103,6 @@ int32_t StatusLEDModule::runOnce()
|
||||
PAIRING_LED_state = LED_STATE_ON;
|
||||
}
|
||||
|
||||
bool chargeIndicatorLED1 = LED_STATE_OFF;
|
||||
bool chargeIndicatorLED2 = LED_STATE_OFF;
|
||||
bool chargeIndicatorLED3 = LED_STATE_OFF;
|
||||
bool chargeIndicatorLED4 = LED_STATE_OFF;
|
||||
if (lastUserbuttonTime + 10 * 1000 > millis() || CHARGE_LED_state == LED_STATE_ON) {
|
||||
// should this be off at very low percentages?
|
||||
chargeIndicatorLED1 = LED_STATE_ON;
|
||||
if (powerStatus && powerStatus->getBatteryChargePercent() >= 25)
|
||||
chargeIndicatorLED2 = LED_STATE_ON;
|
||||
if (powerStatus && powerStatus->getBatteryChargePercent() >= 50)
|
||||
chargeIndicatorLED3 = LED_STATE_ON;
|
||||
if (powerStatus && powerStatus->getBatteryChargePercent() >= 75)
|
||||
chargeIndicatorLED4 = LED_STATE_ON;
|
||||
}
|
||||
|
||||
#ifdef LED_CHARGE
|
||||
digitalWrite(LED_CHARGE, CHARGE_LED_state);
|
||||
#endif
|
||||
@@ -134,18 +111,5 @@ int32_t StatusLEDModule::runOnce()
|
||||
digitalWrite(LED_PAIRING, PAIRING_LED_state);
|
||||
#endif
|
||||
|
||||
#ifdef Battery_LED_1
|
||||
digitalWrite(Battery_LED_1, chargeIndicatorLED1);
|
||||
#endif
|
||||
#ifdef Battery_LED_2
|
||||
digitalWrite(Battery_LED_2, chargeIndicatorLED2);
|
||||
#endif
|
||||
#ifdef Battery_LED_3
|
||||
digitalWrite(Battery_LED_3, chargeIndicatorLED3);
|
||||
#endif
|
||||
#ifdef Battery_LED_4
|
||||
digitalWrite(Battery_LED_4, chargeIndicatorLED4);
|
||||
#endif
|
||||
|
||||
return (my_interval);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "PowerStatus.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
@@ -18,8 +17,6 @@ class StatusLEDModule : private concurrency::OSThread
|
||||
|
||||
int handleStatusUpdate(const meshtastic::Status *);
|
||||
|
||||
int handleInputEvent(const InputEvent *arg);
|
||||
|
||||
protected:
|
||||
unsigned int my_interval = 1000; // interval in millisconds
|
||||
virtual int32_t runOnce() override;
|
||||
@@ -28,15 +25,12 @@ class StatusLEDModule : private concurrency::OSThread
|
||||
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
|
||||
CallbackObserver<StatusLEDModule, const meshtastic::Status *> powerStatusObserver =
|
||||
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
|
||||
CallbackObserver<StatusLEDModule, const InputEvent *> inputObserver =
|
||||
CallbackObserver<StatusLEDModule, const InputEvent *>(this, &StatusLEDModule::handleInputEvent);
|
||||
|
||||
private:
|
||||
bool CHARGE_LED_state = LED_STATE_OFF;
|
||||
bool PAIRING_LED_state = LED_STATE_OFF;
|
||||
|
||||
uint32_t PAIRING_LED_starttime = 0;
|
||||
uint32_t lastUserbuttonTime = 0;
|
||||
uint32_t POWER_LED_starttime = 0;
|
||||
bool doing_fast_blink = false;
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||
|
||||
#include "StatusMessageModule.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include "MeshService.h"
|
||||
|
||||
|
||||
StatusMessageModule *statusMessageModule;
|
||||
|
||||
int32_t StatusMessageModule::runOnce()
|
||||
{
|
||||
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
|
||||
// create and send message with the status message set
|
||||
meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero;
|
||||
strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status));
|
||||
ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
p->decoded.payload.size =
|
||||
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), meshtastic_StatusMessage_fields, &ourStatus);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
p->channel = 0;
|
||||
service->sendToMesh(p);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 1000 * 12 * 60 * 60;
|
||||
}
|
||||
|
||||
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
meshtastic_StatusMessage incomingMessage;
|
||||
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
|
||||
&incomingMessage)) {
|
||||
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
|
||||
}
|
||||
|
||||
}
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||
#include "SinglePortModule.h"
|
||||
#include "configuration.h"
|
||||
|
||||
|
||||
|
||||
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
||||
{
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
StatusMessageModule() : SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage")
|
||||
{
|
||||
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
|
||||
this->setInterval(2 * 60 * 1000);
|
||||
}
|
||||
// TODO: If we have a string, set the initial delay (15 minutes maybe)
|
||||
}
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
*/
|
||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
extern StatusMessageModule *statusMessageModule;
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "AirQualityTelemetry.h"
|
||||
@@ -10,54 +10,27 @@
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "Sensor/AddI2CSensorTemplate.h"
|
||||
#include "UnitConversions.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/images.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
// Sensors
|
||||
#include "Sensor/PMSA003ISensor.h"
|
||||
|
||||
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
{
|
||||
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO("Air Quality Telemetry adding I2C devices...");
|
||||
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the module
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
Note: this was previously on runOnce, which didnt take effect
|
||||
as other modules already had already been initialized (screen)
|
||||
*/
|
||||
|
||||
// moduleConfig.telemetry.air_quality_enabled = 1;
|
||||
// moduleConfig.telemetry.air_quality_screen_enabled = 1;
|
||||
// moduleConfig.telemetry.air_quality_interval = 15;
|
||||
|
||||
// order by priority of metrics/values (low top, high bottom)
|
||||
addSensor<PMSA003ISensor>(i2cScanner, ScanI2C::DeviceType::PMSA003I);
|
||||
}
|
||||
#ifndef PMSA003I_WARMUP_MS
|
||||
// from the PMSA003I datasheet:
|
||||
// "Stable data should be got at least 30 seconds after the sensor wakeup
|
||||
// from the sleep mode because of the fan’s performance."
|
||||
#define PMSA003I_WARMUP_MS 30000
|
||||
#endif
|
||||
|
||||
int32_t AirQualityTelemetryModule::runOnce()
|
||||
{
|
||||
if (sleepOnNextExecution == true) {
|
||||
sleepOnNextExecution = false;
|
||||
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs);
|
||||
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs);
|
||||
doDeepSleep(nightyNightMs, true, false);
|
||||
}
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the module
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
*/
|
||||
|
||||
uint32_t result = UINT32_MAX;
|
||||
// moduleConfig.telemetry.air_quality_enabled = 1;
|
||||
|
||||
if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled ||
|
||||
AIR_QUALITY_TELEMETRY_MODULE_ENABLE)) {
|
||||
if (!(moduleConfig.telemetry.air_quality_enabled)) {
|
||||
// If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
|
||||
return disable();
|
||||
}
|
||||
@@ -69,152 +42,82 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
if (moduleConfig.telemetry.air_quality_enabled) {
|
||||
LOG_INFO("Air quality Telemetry: init");
|
||||
|
||||
// check if we have at least one sensor
|
||||
if (!sensors.empty()) {
|
||||
result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
}
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
// put the sensor to sleep on startup
|
||||
pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
|
||||
#endif /* PMSA003I_ENABLE_PIN */
|
||||
|
||||
// it's possible to have this module enabled, only for displaying values on the screen.
|
||||
// therefore, we should only enable the sensor loop if measurement is also enabled
|
||||
return result == UINT32_MAX ? disable() : setStartDelay();
|
||||
if (!aqi.begin_I2C()) {
|
||||
#ifndef I2C_NO_RESCAN
|
||||
LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan");
|
||||
// rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty.
|
||||
uint8_t i2caddr_scan[] = {PMSA0031_ADDR};
|
||||
uint8_t i2caddr_asize = 1;
|
||||
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
|
||||
#if defined(I2C_SDA1)
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
|
||||
#endif
|
||||
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
|
||||
auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031);
|
||||
if (found.type != ScanI2C::DeviceType::NONE) {
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
|
||||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
|
||||
i2cScanner->fetchI2CBus(found.address);
|
||||
return setStartDelay();
|
||||
}
|
||||
#endif
|
||||
return disable();
|
||||
}
|
||||
return setStartDelay();
|
||||
}
|
||||
return disable();
|
||||
} else {
|
||||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
|
||||
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
|
||||
if (!moduleConfig.telemetry.air_quality_enabled)
|
||||
return disable();
|
||||
|
||||
switch (state) {
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
case State::IDLE:
|
||||
// sensor is in standby; fire it up and sleep
|
||||
LOG_DEBUG("runOnce(): state = idle");
|
||||
digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
|
||||
state = State::ACTIVE;
|
||||
|
||||
return PMSA003I_WARMUP_MS;
|
||||
#endif /* PMSA003I_ENABLE_PIN */
|
||||
case State::ACTIVE:
|
||||
// sensor is already warmed up; grab telemetry and send it
|
||||
LOG_DEBUG("runOnce(): state = active");
|
||||
|
||||
if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
} else if (service->isToPhoneQueueEmpty()) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
sendTelemetry(NODENUM_BROADCAST, true);
|
||||
}
|
||||
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
// put sensor back to sleep
|
||||
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
|
||||
state = State::IDLE;
|
||||
#endif /* PMSA003I_ENABLE_PIN */
|
||||
|
||||
return sendToPhoneIntervalMs;
|
||||
default:
|
||||
return disable();
|
||||
}
|
||||
|
||||
// Wake up the sensors that need it
|
||||
LOG_INFO("Waking up sensors");
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
if (!sensor->isActive()) {
|
||||
return sensor->wakeUp();
|
||||
}
|
||||
}
|
||||
|
||||
if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
sendTelemetry(NODENUM_BROADCAST, true);
|
||||
lastSentToPhone = millis();
|
||||
}
|
||||
|
||||
// Send to sleep sensors that consume power
|
||||
LOG_INFO("Sending sensors to sleep");
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
sensor->sleep();
|
||||
}
|
||||
}
|
||||
return min(sendToPhoneIntervalMs, result);
|
||||
}
|
||||
|
||||
bool AirQualityTelemetryModule::wantUIFrame()
|
||||
{
|
||||
return moduleConfig.telemetry.air_quality_screen_enabled;
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// === Setup display ===
|
||||
display->clear();
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
int line = 1;
|
||||
|
||||
// === Set Title
|
||||
const char *titleStr = (graphics::currentResolution == graphics::ScreenResolution::High) ? "Air Quality" : "AQ.";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
// === Row spacing setup ===
|
||||
const int rowHeight = FONT_HEIGHT_SMALL - 4;
|
||||
int currentY = graphics::getTextPositions(display)[line++];
|
||||
|
||||
// === Show "No Telemetry" if no data available ===
|
||||
if (!lastMeasurementPacket) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the telemetry message from the latest received packet
|
||||
const meshtastic_Data &p = lastMeasurementPacket->decoded;
|
||||
meshtastic_Telemetry telemetry;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &telemetry)) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &m = telemetry.variant.air_quality_metrics;
|
||||
|
||||
// Check if any telemetry field has valid data
|
||||
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental ||
|
||||
m.has_pm25_environmental || m.has_pm100_environmental;
|
||||
|
||||
if (!hasAny) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// === First line: Show sender name + time since received (left), and first metric (right) ===
|
||||
const char *sender = getSenderShortName(*lastMeasurementPacket);
|
||||
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
|
||||
String agoStr = (agoSecs > 864000) ? "?"
|
||||
: (agoSecs > 3600) ? String(agoSecs / 3600) + "h"
|
||||
: (agoSecs > 60) ? String(agoSecs / 60) + "m"
|
||||
: String(agoSecs) + "s";
|
||||
|
||||
String leftStr = String(sender) + " (" + agoStr + ")";
|
||||
display->drawString(x, currentY, leftStr); // Left side: who and when
|
||||
|
||||
// === Collect sensor readings as label strings (no icons) ===
|
||||
std::vector<String> entries;
|
||||
|
||||
if (m.has_pm10_standard)
|
||||
entries.push_back("PM1: " + String(m.pm10_standard) + "ug/m3");
|
||||
if (m.has_pm25_standard)
|
||||
entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3");
|
||||
if (m.has_pm100_standard)
|
||||
entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
|
||||
|
||||
// === Show first available metric on top-right of first line ===
|
||||
if (!entries.empty()) {
|
||||
String valueStr = entries.front();
|
||||
int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr);
|
||||
display->drawString(rightX, currentY, valueStr);
|
||||
entries.erase(entries.begin()); // Remove from queue
|
||||
}
|
||||
|
||||
// === Advance to next line for remaining telemetry entries ===
|
||||
currentY += rowHeight;
|
||||
|
||||
// === Draw remaining entries in 2-column format (left and right) ===
|
||||
for (size_t i = 0; i < entries.size(); i += 2) {
|
||||
// Left column
|
||||
display->drawString(x, currentY, entries[i]);
|
||||
|
||||
// Right column if it exists
|
||||
if (i + 1 < entries.size()) {
|
||||
int rightX = SCREEN_WIDTH / 2;
|
||||
display->drawString(rightX, currentY, entries[i + 1]);
|
||||
}
|
||||
|
||||
currentY += rowHeight;
|
||||
}
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
|
||||
@@ -241,21 +144,35 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
|
||||
|
||||
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
bool valid = true;
|
||||
bool hasSensor = false;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
|
||||
|
||||
// TODO - Should we check for sensor state here?
|
||||
// If a sensor is sleeping, we should know and check to wake it up
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
LOG_INFO("Reading AQ sensors");
|
||||
valid = valid && sensor->getMetrics(m);
|
||||
hasSensor = true;
|
||||
if (!aqi.read(&data)) {
|
||||
LOG_WARN("Skip send measurements. Could not read AQIn");
|
||||
return false;
|
||||
}
|
||||
|
||||
return valid && hasSensor;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m->variant.air_quality_metrics.has_pm10_standard = true;
|
||||
m->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
|
||||
m->variant.air_quality_metrics.has_pm25_standard = true;
|
||||
m->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
|
||||
m->variant.air_quality_metrics.has_pm100_standard = true;
|
||||
m->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
|
||||
|
||||
m->variant.air_quality_metrics.has_pm10_environmental = true;
|
||||
m->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
|
||||
m->variant.air_quality_metrics.has_pm25_environmental = true;
|
||||
m->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
|
||||
m->variant.air_quality_metrics.has_pm100_environmental = true;
|
||||
m->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
|
||||
|
||||
LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", m->variant.air_quality_metrics.pm10_standard,
|
||||
m->variant.air_quality_metrics.pm25_standard, m->variant.air_quality_metrics.pm100_standard);
|
||||
|
||||
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
|
||||
m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental,
|
||||
m->variant.air_quality_metrics.pm100_environmental);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
|
||||
@@ -289,15 +206,7 @@ meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
|
||||
bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
{
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m.time = getTime();
|
||||
if (getAirQualityTelemetry(&m)) {
|
||||
LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, \
|
||||
pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u",
|
||||
m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,
|
||||
m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental,
|
||||
m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
p->decoded.want_response = false;
|
||||
@@ -312,44 +221,16 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
|
||||
lastMeasurementPacket = packetPool.allocCopy(*p);
|
||||
if (phoneOnly) {
|
||||
LOG_INFO("Sending packet to phone");
|
||||
LOG_INFO("Send packet to phone");
|
||||
service->sendToPhone(p);
|
||||
} else {
|
||||
LOG_INFO("Sending packet to mesh");
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
|
||||
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
|
||||
notification->level = meshtastic_LogRecord_Level_INFO;
|
||||
notification->time = getValidTime(RTCQualityFromNet);
|
||||
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs) /
|
||||
1000U);
|
||||
service->sendClientNotification(notification);
|
||||
sleepOnNextExecution = true;
|
||||
LOG_DEBUG("Start next execution in 5s, then sleep");
|
||||
setIntervalFromNow(FIVE_SECONDS_MS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response)
|
||||
{
|
||||
AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
|
||||
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
result = sensor->handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,23 +1,14 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE
|
||||
#define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0
|
||||
#endif
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "Adafruit_PM25AQI.h"
|
||||
#include "NodeDB.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include "detect/ScanI2CConsumer.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
class AirQualityTelemetryModule : private concurrency::OSThread,
|
||||
public ScanI2CConsumer,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *>(this,
|
||||
@@ -25,19 +16,22 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
|
||||
|
||||
public:
|
||||
AirQualityTelemetryModule()
|
||||
: concurrency::OSThread("AirQualityTelemetry"), ScanI2CConsumer(),
|
||||
: concurrency::OSThread("AirQualityTelemetry"),
|
||||
ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
|
||||
{
|
||||
lastMeasurementPacket = nullptr;
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(10 * 1000);
|
||||
}
|
||||
virtual bool wantUIFrame() override;
|
||||
#if !HAS_SCREEN
|
||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
aqi = Adafruit_PM25AQI();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
// the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking
|
||||
// a reading
|
||||
state = State::IDLE;
|
||||
#else
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
|
||||
state = State::ACTIVE;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
@@ -55,17 +49,19 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
|
||||
*/
|
||||
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response) override;
|
||||
void i2cScanFinished(ScanI2C *i2cScanner);
|
||||
|
||||
private:
|
||||
enum State {
|
||||
IDLE = 0,
|
||||
ACTIVE = 1,
|
||||
};
|
||||
|
||||
State state;
|
||||
Adafruit_PM25AQI aqi;
|
||||
PM25_AQI_Data data = {0};
|
||||
bool firstTime = true;
|
||||
meshtastic_MeshPacket *lastMeasurementPacket;
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -141,10 +141,37 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
|
||||
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
|
||||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
|
||||
|
||||
#include "Sensor/AddI2CSensorTemplate.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
#include <forward_list>
|
||||
|
||||
static std::forward_list<TelemetrySensor *> sensors;
|
||||
|
||||
template <typename T> void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
|
||||
{
|
||||
ScanI2C::FoundDevice dev = i2cScanner->find(type);
|
||||
if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
|
||||
TelemetrySensor *sensor = new T();
|
||||
#if WIRE_INTERFACES_COUNT > 1
|
||||
TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
|
||||
if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
|
||||
// This sensor only works on Wire (Wire1 is not supported)
|
||||
delete sensor;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
TwoWire *bus = &Wire;
|
||||
#endif
|
||||
if (sensor->initDevice(bus, &dev)) {
|
||||
sensors.push_front(sensor);
|
||||
return;
|
||||
}
|
||||
// destroy sensor
|
||||
delete sensor;
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
{
|
||||
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
|
||||
@@ -615,6 +642,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature,
|
||||
m.variant.environment_metrics.soil_moisture);
|
||||
|
||||
sensor_read_error_count = 0;
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
p->decoded.want_response = false;
|
||||
|
||||
@@ -67,6 +67,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
uint32_t sensor_read_error_count = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
|
||||
#include "TelemetrySensor.h"
|
||||
#include "detect/ScanI2C.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <Wire.h>
|
||||
#include <forward_list>
|
||||
|
||||
static std::forward_list<TelemetrySensor *> sensors;
|
||||
|
||||
template <typename T> void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
|
||||
{
|
||||
ScanI2C::FoundDevice dev = i2cScanner->find(type);
|
||||
if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
|
||||
TelemetrySensor *sensor = new T();
|
||||
#if WIRE_INTERFACES_COUNT > 1
|
||||
TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
|
||||
if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
|
||||
// This sensor only works on Wire (Wire1 is not supported)
|
||||
delete sensor;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
TwoWire *bus = &Wire;
|
||||
#endif
|
||||
if (sensor->initDevice(bus, &dev)) {
|
||||
sensors.push_front(sensor);
|
||||
return;
|
||||
}
|
||||
// destroy sensor
|
||||
delete sensor;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,158 +0,0 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
|
||||
#include "../detect/reClockI2C.h"
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "PMSA003ISensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {}
|
||||
|
||||
bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
{
|
||||
LOG_INFO("Init sensor: %s", sensorName);
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
|
||||
#endif
|
||||
|
||||
_bus = bus;
|
||||
_address = dev->address.address;
|
||||
|
||||
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
|
||||
if (!currentClock) {
|
||||
LOG_WARN("PMSA003I can't be used at this clock speed");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
_bus->beginTransmission(_address);
|
||||
if (_bus->endTransmission() != 0) {
|
||||
LOG_WARN("PMSA003I not found on I2C at 0x12");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus);
|
||||
#endif
|
||||
|
||||
status = 1;
|
||||
LOG_INFO("PMSA003I Enabled");
|
||||
|
||||
initI2CSensor();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
if (!isActive()) {
|
||||
LOG_WARN("PMSA003I is not active");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
|
||||
#endif
|
||||
|
||||
_bus->requestFrom(_address, PMSA003I_FRAME_LENGTH);
|
||||
if (_bus->available() < PMSA003I_FRAME_LENGTH) {
|
||||
LOG_WARN("PMSA003I read failed: incomplete data (%d bytes)", _bus->available());
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus);
|
||||
#endif
|
||||
|
||||
for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH; i++) {
|
||||
buffer[i] = _bus->read();
|
||||
}
|
||||
|
||||
if (buffer[0] != 0x42 || buffer[1] != 0x4D) {
|
||||
LOG_WARN("PMSA003I frame header invalid: 0x%02X 0x%02X", buffer[0], buffer[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; };
|
||||
|
||||
computedChecksum = 0;
|
||||
|
||||
for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH - 2; i++) {
|
||||
computedChecksum += buffer[i];
|
||||
}
|
||||
receivedChecksum = read16(buffer, PMSA003I_FRAME_LENGTH - 2);
|
||||
|
||||
if (computedChecksum != receivedChecksum) {
|
||||
LOG_WARN("PMSA003I checksum failed: computed 0x%04X, received 0x%04X", computedChecksum, receivedChecksum);
|
||||
return false;
|
||||
}
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm10_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm10_standard = read16(buffer, 4);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm25_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm25_standard = read16(buffer, 6);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm100_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm100_standard = read16(buffer, 8);
|
||||
|
||||
// TODO - Add admin command to remove environmental metrics to save protobuf space
|
||||
measurement->variant.air_quality_metrics.has_pm10_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm10_environmental = read16(buffer, 10);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm25_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm25_environmental = read16(buffer, 12);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm100_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm100_environmental = read16(buffer, 14);
|
||||
|
||||
// TODO - Add admin command to remove PN to save protobuf space
|
||||
measurement->variant.air_quality_metrics.has_particles_03um = true;
|
||||
measurement->variant.air_quality_metrics.particles_03um = read16(buffer, 16);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_particles_05um = true;
|
||||
measurement->variant.air_quality_metrics.particles_05um = read16(buffer, 18);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_particles_10um = true;
|
||||
measurement->variant.air_quality_metrics.particles_10um = read16(buffer, 20);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_particles_25um = true;
|
||||
measurement->variant.air_quality_metrics.particles_25um = read16(buffer, 22);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_particles_50um = true;
|
||||
measurement->variant.air_quality_metrics.particles_50um = read16(buffer, 24);
|
||||
|
||||
measurement->variant.air_quality_metrics.has_particles_100um = true;
|
||||
measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PMSA003ISensor::isActive()
|
||||
{
|
||||
return state == State::ACTIVE;
|
||||
}
|
||||
|
||||
void PMSA003ISensor::sleep()
|
||||
{
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
|
||||
state = State::IDLE;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t PMSA003ISensor::wakeUp()
|
||||
{
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
LOG_INFO("Waking up PMSA003I");
|
||||
digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
|
||||
state = State::ACTIVE;
|
||||
return PMSA003I_WARMUP_MS;
|
||||
#endif
|
||||
// No need to wait for warmup if already active
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
|
||||
#define PMSA003I_I2C_CLOCK_SPEED 100000
|
||||
#define PMSA003I_FRAME_LENGTH 32
|
||||
#define PMSA003I_WARMUP_MS 30000
|
||||
|
||||
class PMSA003ISensor : public TelemetrySensor
|
||||
{
|
||||
public:
|
||||
PMSA003ISensor();
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
|
||||
|
||||
virtual bool isActive() override;
|
||||
virtual void sleep() override;
|
||||
virtual uint32_t wakeUp() override;
|
||||
|
||||
private:
|
||||
enum class State { IDLE, ACTIVE };
|
||||
State state = State::ACTIVE;
|
||||
|
||||
uint16_t computedChecksum = 0;
|
||||
uint16_t receivedChecksum = 0;
|
||||
|
||||
uint8_t buffer[PMSA003I_FRAME_LENGTH]{};
|
||||
TwoWire *_bus{};
|
||||
uint8_t _address{};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
@@ -58,11 +58,6 @@ class TelemetrySensor
|
||||
|
||||
// TODO: delete after migration
|
||||
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
|
||||
// Functions to sleep / wakeup sensors that support it
|
||||
virtual void sleep(){};
|
||||
virtual uint32_t wakeUp() { return 0; }
|
||||
// Return active by default, override per sensor
|
||||
virtual bool isActive() { return true; }
|
||||
|
||||
#if WIRE_INTERFACES_COUNT > 1
|
||||
// Set to true if Implementation only works first I2C port (Wire)
|
||||
@@ -70,7 +65,6 @@ class TelemetrySensor
|
||||
#endif
|
||||
virtual int32_t runOnce() { return INT32_MAX; }
|
||||
virtual bool isInitialized() { return initialized; }
|
||||
// TODO: is this used?
|
||||
virtual bool isRunning() { return status > 0; }
|
||||
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
|
||||
|
||||
@@ -47,6 +47,7 @@ int32_t ICM20948Sensor::runOnce()
|
||||
int32_t ICM20948Sensor::runOnce()
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
|
||||
#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze
|
||||
if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
|
||||
if (!isAsleep) {
|
||||
LOG_DEBUG("sleeping IMU");
|
||||
@@ -59,6 +60,7 @@ int32_t ICM20948Sensor::runOnce()
|
||||
sensor->sleep(false);
|
||||
isAsleep = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
float magX = 0, magY = 0, magZ = 0;
|
||||
if (sensor->dataReady()) {
|
||||
|
||||
@@ -82,8 +82,8 @@ class ICM20948Sensor : public MotionSensor
|
||||
private:
|
||||
ICM20948Singleton *sensor = nullptr;
|
||||
bool showingScreen = false;
|
||||
bool isAsleep = false;
|
||||
#ifdef MUZI_BASE
|
||||
bool isAsleep = false;
|
||||
float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000,
|
||||
lowestZ = 98.000000;
|
||||
#else
|
||||
|
||||
46
src/platform/esp32/BleOta.cpp
Normal file
46
src/platform/esp32/BleOta.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "BleOta.h"
|
||||
#include "Arduino.h"
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
static const String MESHTASTIC_OTA_APP_PROJECT_NAME("Meshtastic-OTA");
|
||||
|
||||
const esp_partition_t *BleOta::findEspOtaAppPartition()
|
||||
{
|
||||
const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, nullptr);
|
||||
|
||||
esp_app_desc_t app_desc;
|
||||
esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
|
||||
|
||||
if (ret != ESP_OK || MESHTASTIC_OTA_APP_PROJECT_NAME != app_desc.project_name) {
|
||||
part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, nullptr);
|
||||
ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
|
||||
}
|
||||
|
||||
if (ret == ESP_OK && MESHTASTIC_OTA_APP_PROJECT_NAME == app_desc.project_name) {
|
||||
return part;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
String BleOta::getOtaAppVersion()
|
||||
{
|
||||
const esp_partition_t *part = findEspOtaAppPartition();
|
||||
esp_app_desc_t app_desc;
|
||||
esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
|
||||
String version;
|
||||
if (ret == ESP_OK) {
|
||||
version = app_desc.version;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
bool BleOta::switchToOtaApp()
|
||||
{
|
||||
bool success = false;
|
||||
const esp_partition_t *part = findEspOtaAppPartition();
|
||||
if (part) {
|
||||
success = (ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_set_boot_partition(part)) == ESP_OK);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
20
src/platform/esp32/BleOta.h
Normal file
20
src/platform/esp32/BleOta.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef BLEOTA_H
|
||||
#define BLEOTA_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
class BleOta
|
||||
{
|
||||
public:
|
||||
explicit BleOta(){};
|
||||
|
||||
static String getOtaAppVersion();
|
||||
static bool switchToOtaApp();
|
||||
|
||||
private:
|
||||
String mUserAgent;
|
||||
static const esp_partition_t *findEspOtaAppPartition();
|
||||
};
|
||||
|
||||
#endif // BLEOTA_H
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef MESHTASTICOTA_H
|
||||
#define MESHTASTICOTA_H
|
||||
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace MeshtasticOTA
|
||||
{
|
||||
void initialize();
|
||||
bool isUpdated();
|
||||
|
||||
void recoverConfig(meshtastic_Config_NetworkConfig *network);
|
||||
void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash);
|
||||
bool trySwitchToOTA();
|
||||
const char *getVersion();
|
||||
} // namespace MeshtasticOTA
|
||||
|
||||
#endif // MESHTASTICOTA_H
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "MeshtasticOTA.h"
|
||||
#include "WiFiOTA.h"
|
||||
#include "configuration.h"
|
||||
#include <Preferences.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
namespace MeshtasticOTA
|
||||
namespace WiFiOTA
|
||||
{
|
||||
|
||||
static const char *nvsNamespace = "MeshtasticOTA";
|
||||
static const char *appProjectName = "MeshtasticOTA";
|
||||
static const char *nvsNamespace = "ota-wifi";
|
||||
static const char *appProjectName = "OTA-WiFi";
|
||||
|
||||
static bool updated = false;
|
||||
|
||||
@@ -43,14 +43,12 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network)
|
||||
strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk));
|
||||
}
|
||||
|
||||
void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash)
|
||||
void saveConfig(meshtastic_Config_NetworkConfig *network)
|
||||
{
|
||||
LOG_INFO("Saving WiFi settings for upcoming OTA update");
|
||||
|
||||
Preferences prefs;
|
||||
prefs.begin(nvsNamespace);
|
||||
prefs.putUChar("method", method);
|
||||
prefs.putBytes("ota_hash", ota_hash, 32);
|
||||
prefs.putString("ssid", network->wifi_ssid);
|
||||
prefs.putString("psk", network->wifi_psk);
|
||||
prefs.putBool("updated", false);
|
||||
@@ -64,14 +62,10 @@ const esp_partition_t *getAppPartition()
|
||||
|
||||
bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc)
|
||||
{
|
||||
if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) {
|
||||
LOG_INFO("esp_ota_get_partition_description failed");
|
||||
if (esp_ota_get_partition_description(part, app_desc) != ESP_OK)
|
||||
return false;
|
||||
}
|
||||
if (strcmp(app_desc->project_name, appProjectName) != 0) {
|
||||
LOG_INFO("app_desc->project_name == 0");
|
||||
if (strcmp(app_desc->project_name, appProjectName) != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -95,4 +89,4 @@ const char *getVersion()
|
||||
return app_desc.version;
|
||||
}
|
||||
|
||||
} // namespace MeshtasticOTA
|
||||
} // namespace WiFiOTA
|
||||
18
src/platform/esp32/WiFiOTA.h
Normal file
18
src/platform/esp32/WiFiOTA.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef WIFIOTA_H
|
||||
#define WIFIOTA_H
|
||||
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace WiFiOTA
|
||||
{
|
||||
void initialize();
|
||||
bool isUpdated();
|
||||
|
||||
void recoverConfig(meshtastic_Config_NetworkConfig *network);
|
||||
void saveConfig(meshtastic_Config_NetworkConfig *network);
|
||||
bool trySwitchToOTA();
|
||||
const char *getVersion();
|
||||
} // namespace WiFiOTA
|
||||
|
||||
#endif // WIFIOTA_H
|
||||
@@ -195,8 +195,6 @@
|
||||
#define HW_VENDOR meshtastic_HardwareModel_LINK_32
|
||||
#elif defined(T_DECK_PRO)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
|
||||
#elif defined(T_BEAM_1W)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_TBEAM_1_WATT
|
||||
#elif defined(T_LORA_PAGER)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
|
||||
#elif defined(HELTEC_V4)
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
#include "main.h"
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
#include "BleOta.h"
|
||||
#include "nimble/NimbleBluetooth.h"
|
||||
#endif
|
||||
|
||||
#include <MeshtasticOTA.h>
|
||||
#include <WiFiOTA.h>
|
||||
|
||||
#if HAS_WIFI
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
@@ -143,14 +144,22 @@ void esp32Setup()
|
||||
preferences.putUInt("hwVendor", HW_VENDOR);
|
||||
preferences.end();
|
||||
LOG_DEBUG("Number of Device Reboots: %d", rebootCounter);
|
||||
#if !MESHTASTIC_EXCLUDE_WIFI
|
||||
String version = MeshtasticOTA::getVersion();
|
||||
if (version.isEmpty()) {
|
||||
LOG_INFO("MeshtasticOTA firmware not available");
|
||||
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
String BLEOTA = BleOta::getOtaAppVersion();
|
||||
if (BLEOTA.isEmpty()) {
|
||||
LOG_INFO("No BLE OTA firmware available");
|
||||
} else {
|
||||
LOG_INFO("MeshtasticOTA firmware version %s", version.c_str());
|
||||
LOG_INFO("BLE OTA firmware version %s", BLEOTA.c_str());
|
||||
}
|
||||
MeshtasticOTA::initialize();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_WIFI
|
||||
String version = WiFiOTA::getVersion();
|
||||
if (version.isEmpty()) {
|
||||
LOG_INFO("No WiFi OTA firmware available");
|
||||
} else {
|
||||
LOG_INFO("WiFi OTA firmware version %s", version.c_str());
|
||||
}
|
||||
WiFiOTA::initialize();
|
||||
#endif
|
||||
|
||||
// enableModemSleep();
|
||||
|
||||
@@ -74,8 +74,6 @@
|
||||
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M3
|
||||
#elif defined(ELECROW_ThinkNode_M6)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6
|
||||
#elif defined(ELECROW_ThinkNode_M4)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M4
|
||||
#elif defined(NANO_G2_ULTRA)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_NANO_G2_ULTRA
|
||||
#elif defined(CANARYONE)
|
||||
|
||||
@@ -55,7 +55,6 @@ void cpuDeepSleep(uint32_t msecs)
|
||||
void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel");
|
||||
|
||||
int TCPPort = SERVER_API_DEFAULT_PORT;
|
||||
bool checkConfigPort = true;
|
||||
|
||||
static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
@@ -64,7 +63,6 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
|
||||
if (sscanf(arg, "%d", &TCPPort) < 1)
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
else
|
||||
checkConfigPort = false;
|
||||
printf("Using config file %d\n", TCPPort);
|
||||
break;
|
||||
case 'c':
|
||||
@@ -847,7 +845,6 @@ bool loadConfig(const char *configPath)
|
||||
}
|
||||
|
||||
if (yamlConfig["Config"]) {
|
||||
portduino_config.has_config_overrides = true;
|
||||
if (yamlConfig["Config"]["DisplayMode"]) {
|
||||
portduino_config.has_configDisplayMode = true;
|
||||
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
|
||||
@@ -860,13 +857,6 @@ bool loadConfig(const char *configPath)
|
||||
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
|
||||
}
|
||||
}
|
||||
if (yamlConfig["Config"]["StatusMessage"]) {
|
||||
portduino_config.has_statusMessage = true;
|
||||
portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as<std::string>("");
|
||||
}
|
||||
if ((yamlConfig["Config"]["EnableUDP"]).as<bool>(false)) {
|
||||
portduino_config.enable_UDP = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (yamlConfig["General"]) {
|
||||
@@ -880,14 +870,6 @@ bool loadConfig(const char *configPath)
|
||||
std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (checkConfigPort) {
|
||||
portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1);
|
||||
if (portduino_config.api_port != -1 &&
|
||||
portduino_config.api_port > 1023 &&
|
||||
portduino_config.api_port < 65536) {
|
||||
TCPPort = (portduino_config.api_port);
|
||||
}
|
||||
}
|
||||
portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as<std::string>("");
|
||||
if (portduino_config.mac_address != "") {
|
||||
portduino_config.mac_address_explicit = true;
|
||||
|
||||
@@ -168,18 +168,13 @@ extern struct portduino_config_struct {
|
||||
int hostMetrics_channel = 0;
|
||||
|
||||
// config
|
||||
bool has_config_overrides = false;
|
||||
int configDisplayMode = 0;
|
||||
bool has_configDisplayMode = false;
|
||||
std::string statusMessage = "";
|
||||
bool has_statusMessage = false;
|
||||
bool enable_UDP = false;
|
||||
|
||||
// General
|
||||
std::string mac_address = "";
|
||||
bool mac_address_explicit = false;
|
||||
std::string mac_address_source = "";
|
||||
int api_port = -1;
|
||||
std::string config_directory = "";
|
||||
std::string available_directory = "/etc/meshtasticd/available.d/";
|
||||
int maxtophone = 100;
|
||||
@@ -489,32 +484,21 @@ extern struct portduino_config_struct {
|
||||
}
|
||||
|
||||
// config
|
||||
if (has_config_overrides) {
|
||||
if (has_configDisplayMode) {
|
||||
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
|
||||
if (has_configDisplayMode) {
|
||||
|
||||
switch (configDisplayMode) {
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_statusMessage) {
|
||||
out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage;
|
||||
|
||||
}
|
||||
if (enable_UDP) {
|
||||
out << YAML::Key << "EnableUDP" << YAML::Value << true;
|
||||
|
||||
switch (configDisplayMode) {
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
|
||||
break;
|
||||
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
|
||||
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
|
||||
break;
|
||||
}
|
||||
|
||||
out << YAML::EndMap; // Config
|
||||
@@ -524,8 +508,6 @@ extern struct portduino_config_struct {
|
||||
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
|
||||
if (config_directory != "")
|
||||
out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory;
|
||||
if (api_port != -1)
|
||||
out << YAML::Key << "TCPPort" << YAML::Value << api_port;
|
||||
if (mac_address_explicit)
|
||||
out << YAML::Key << "MACAddress" << YAML::Value << mac_address;
|
||||
if (mac_address_source != "")
|
||||
@@ -537,4 +519,4 @@ extern struct portduino_config_struct {
|
||||
out << YAML::EndMap; // General
|
||||
return out.c_str();
|
||||
}
|
||||
} portduino_config;
|
||||
} portduino_config;
|
||||
@@ -121,8 +121,6 @@ class Power : private concurrency::OSThread
|
||||
bool lipoChargerInit();
|
||||
/// Setup a meshSolar battery sensor
|
||||
bool meshSolarInit();
|
||||
/// Setup a serial battery sensor
|
||||
bool serialBatteryInit();
|
||||
|
||||
private:
|
||||
void shutdown();
|
||||
|
||||
@@ -49,7 +49,6 @@ build_flags =
|
||||
-DLIBPAX_BLE
|
||||
-DHAS_UDP_MULTICAST=1
|
||||
;-DDEBUG_HEAP
|
||||
-DCAN_RECLOCK_I2C
|
||||
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[env:heltec-wireless-bridge]
|
||||
;build_type = debug ; to make it possible to step through our jtag debugger
|
||||
;build_type = debug ; to make it possible to step through our jtag debugger
|
||||
extends = esp32_base
|
||||
board_level = extra
|
||||
board = heltec_wifi_lora_32
|
||||
build_flags =
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/esp32/heltec_wireless_bridge
|
||||
-D HELTEC_WIRELESS_BRIDGE
|
||||
@@ -13,7 +13,6 @@ build_flags =
|
||||
-D MESHTASTIC_EXCLUDE_CANNEDMESSAGES=1
|
||||
-D MESHTASTIC_EXCLUDE_DETECTIONSENSOR=1
|
||||
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
|
||||
-D MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
|
||||
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1
|
||||
-D MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1
|
||||
-D MESHTASTIC_EXCLUDE_GPS=1
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Need this file for ESP32-S3
|
||||
// No need to modify this file, changes to pins imported from variant.h
|
||||
// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h
|
||||
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <variant.h>
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x1001
|
||||
|
||||
// Serial
|
||||
static const uint8_t TX = UART_TX;
|
||||
static const uint8_t RX = UART_RX;
|
||||
|
||||
// Default SPI will be mapped to Radio
|
||||
static const uint8_t SS = LORA_CS;
|
||||
static const uint8_t SCK = LORA_SCK;
|
||||
static const uint8_t MOSI = LORA_MOSI;
|
||||
static const uint8_t MISO = LORA_MISO;
|
||||
|
||||
// The default Wire will be mapped to PMU and RTC
|
||||
static const uint8_t SCL = I2C_SCL;
|
||||
static const uint8_t SDA = I2C_SDA;
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
@@ -1,8 +0,0 @@
|
||||
[env:CDEBYTE_EoRa-Hub]
|
||||
extends = esp32s3_base
|
||||
board = CDEBYTE_EoRa-Hub
|
||||
board_level = extra
|
||||
build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-D PRIVATE_HW
|
||||
-I variants/esp32s3/CDEBYTE_EoRa-Hub
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "RadioLib.h"
|
||||
|
||||
// This is rewritten to match the requirements of the E80-900M2213S
|
||||
// The E80 does not conform to the reference Semtech switches(!) and therefore needs a custom matrix.
|
||||
// See footnote #3 in "https://www.cdebyte.com/products/E80-900M2213S/2#Pin"
|
||||
// RF Switch Matrix SubG RFO_HP_LF / RFO_LP_LF / RFI_[NP]_LF0
|
||||
// DIO5 -> RFSW0_V1
|
||||
// DIO6 -> RFSW1_V2
|
||||
// DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible.
|
||||
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6 DIO7
|
||||
{LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH, LOW}},
|
||||
{LR11x0::MODE_TX, {HIGH, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW, LOW}},
|
||||
{LR11x0::MODE_TX_HF, {LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH}},
|
||||
{LR11x0::MODE_WIFI, {LOW, LOW, LOW}}, END_OF_MODE_TABLE,
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
// EByte EoRA-Hub
|
||||
// Uses E80 (LR1121) LoRa module
|
||||
|
||||
#define LED_PIN 35
|
||||
|
||||
// Button - user interface
|
||||
#define BUTTON_PIN 0 // BOOT button
|
||||
|
||||
#define BATTERY_PIN 1
|
||||
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
|
||||
#define ADC_MULTIPLIER 103.0 // Calibrated value
|
||||
#define ADC_ATTENUATION ADC_ATTEN_DB_0
|
||||
#define ADC_CTRL 37
|
||||
#define ADC_CTRL_ENABLED LOW
|
||||
|
||||
// Display - OLED connected via I2C by the default hardware configuration
|
||||
#define HAS_SCREEN 1
|
||||
#define USE_SSD1306
|
||||
#define I2C_SCL 17
|
||||
#define I2C_SDA 18
|
||||
|
||||
// UART - The 1mm JST SH connector closest to the USB-C port
|
||||
#define UART_TX 43
|
||||
#define UART_RX 44
|
||||
|
||||
// Peripheral I2C - The 1mm JST SH connector furthest from the USB-C port which follows Adafruit connection standard. There are no
|
||||
// pull-up resistors on these lines, the downstream device needs to include them. TODO: test, currently untested
|
||||
#define I2C_SCL1 21
|
||||
#define I2C_SDA1 10
|
||||
|
||||
// Radio
|
||||
#define USE_LR1121
|
||||
|
||||
#define LORA_SCK 9
|
||||
#define LORA_MOSI 10
|
||||
#define LORA_MISO 11
|
||||
#define LORA_RESET 12
|
||||
#define LORA_CS 8
|
||||
#define LORA_DIO9 13
|
||||
|
||||
// LR1121
|
||||
#define LR1121_IRQ_PIN 14
|
||||
#define LR1121_NRESET_PIN LORA_RESET
|
||||
#define LR1121_BUSY_PIN LORA_DIO9
|
||||
#define LR1121_SPI_NSS_PIN LORA_CS
|
||||
#define LR1121_SPI_SCK_PIN LORA_SCK
|
||||
#define LR1121_SPI_MOSI_PIN LORA_MOSI
|
||||
#define LR1121_SPI_MISO_PIN LORA_MISO
|
||||
#define LR11X0_DIO3_TCXO_VOLTAGE 1.8
|
||||
#define LR11X0_DIO_AS_RF_SWITCH
|
||||
@@ -164,3 +164,6 @@ build_flags =
|
||||
${crowpanel_large_esp32s3_base.build_flags}
|
||||
-D VIEW_320x240
|
||||
-D DISPLAY_SIZE=800x480 ; landscape mode
|
||||
build_src_filter =
|
||||
${esp32s3_base.build_src_filter}
|
||||
+<../variants/esp32s3/elecrow_panel>
|
||||
|
||||
21
variants/esp32s3/elecrow_panel/variant.cpp
Normal file
21
variants/esp32s3/elecrow_panel/variant.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// meshtastic/firmware/variants/elecrow_panel/variant.cpp
|
||||
|
||||
#include "variant.h"
|
||||
#include "Arduino.h"
|
||||
#include "Wire.h"
|
||||
|
||||
bool elecrow_v2 = false; // false = v1, true = v2
|
||||
|
||||
extern "C" {
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
Wire.begin(I2C_SDA, I2C_SCL, 100000);
|
||||
delay(50);
|
||||
Wire.beginTransmission(0x30);
|
||||
if (Wire.endTransmission() == 0) {
|
||||
elecrow_v2 = true;
|
||||
}
|
||||
Wire.end();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#define I2C_SDA 15
|
||||
#define I2C_SCL 16
|
||||
|
||||
extern bool elecrow_v2; // false = v1, true = v2
|
||||
|
||||
#if CROW_SELECT == 1
|
||||
#define WAKE_ON_TOUCH
|
||||
#define SCREEN_TOUCH_INT 47
|
||||
@@ -17,7 +19,7 @@
|
||||
#define DAC_I2S_DOUT 12
|
||||
#define DAC_I2S_MCLK 8 // don't use GPIO0 because it's assigned to LoRa or button
|
||||
#else
|
||||
#define PIN_BUZZER 8
|
||||
#define PIN_BUZZER (elecrow_v2 ? 0 : 8)
|
||||
#endif
|
||||
|
||||
// GPS via UART1 connector
|
||||
@@ -72,7 +74,7 @@
|
||||
#define SENSOR_POWER_ON LOW
|
||||
#else
|
||||
// 4.3", 5.0", 7.0"
|
||||
#define LORA_CS 0
|
||||
#define LORA_CS (elecrow_v2 ? 8 : 0)
|
||||
#define LORA_SCK 5
|
||||
#define LORA_MISO 4
|
||||
#define LORA_MOSI 6
|
||||
|
||||
@@ -34,17 +34,6 @@ build_flags =
|
||||
-D I2C_SCL1=3
|
||||
|
||||
[env:heltec-v4-tft]
|
||||
custom_meshtastic_hw_model = 110
|
||||
custom_meshtastic_hw_model_slug = HELTEC_V4
|
||||
custom_meshtastic_architecture = esp32-s3
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = Heltec V4 TFT
|
||||
custom_meshtastic_images = heltec_v4.svg
|
||||
custom_meshtastic_tags = Heltec
|
||||
custom_meshtastic_requires_dfu = true
|
||||
custom_meshtastic_partition_scheme = 16MB
|
||||
|
||||
extends = heltec_v4_base
|
||||
build_flags =
|
||||
${heltec_v4_base.build_flags} ;-Os
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
; LilyGo T-Beam-1W (1 Watt LoRa with external PA)
|
||||
[env:t-beam-1w]
|
||||
custom_meshtastic_hw_model = 122
|
||||
custom_meshtastic_hw_model_slug = TBEAM_1_WATT
|
||||
custom_meshtastic_architecture = esp32s3
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = LILYGO T-Beam 1W
|
||||
custom_meshtastic_images = tbeam-1w.svg
|
||||
custom_meshtastic_tags = LilyGo
|
||||
|
||||
extends = esp32s3_base
|
||||
board = t-beam-1w
|
||||
board_build.partitions = default_8MB.csv
|
||||
|
||||
@@ -53,13 +53,10 @@
|
||||
#define HAS_BMA423 1
|
||||
#define BMA4XX_INT 14 // Interrupt for BMA_423 axis sensor
|
||||
|
||||
#define HAS_GPS 1
|
||||
#define GPS_DEFAULT_NOT_PRESENT 1
|
||||
#define GPS_BAUDRATE 38400
|
||||
#define GPS_RX_PIN 41
|
||||
#define GPS_TX_PIN 42
|
||||
|
||||
#define BUTTON_PIN 0 // only for Plus version
|
||||
#define GPS_RX_PIN 42
|
||||
#define GPS_TX_PIN 41
|
||||
|
||||
#define USE_SX1262
|
||||
#define USE_SX1268
|
||||
|
||||
@@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.3.3
|
||||
# renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
|
||||
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip
|
||||
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.1.3.zip
|
||||
# TODO renovate
|
||||
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
|
||||
# TODO renovate
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
; ThinkNode M4 - Powerbank nrf52840/LR1110 by Elecrow
|
||||
[env:thinknode_m4]
|
||||
extends = nrf52840_base
|
||||
board = ThinkNode-M4
|
||||
board_check = true
|
||||
debug_tool = jlink
|
||||
|
||||
build_flags = ${nrf52840_base.build_flags}
|
||||
-Ivariants/nrf52840/ELECROW-ThinkNode-M4
|
||||
-DELECROW_ThinkNode_M4
|
||||
|
||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M4>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "RadioLib.h"
|
||||
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6
|
||||
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
|
||||
{LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
|
||||
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
|
||||
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "variant.h"
|
||||
#include "nrf.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// P0 - pins 0 and 1 are hardwired for xtal and should never be enabled
|
||||
0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
|
||||
// P1
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
pinMode(PIN_LED2, OUTPUT);
|
||||
ledOff(PIN_LED2);
|
||||
|
||||
pinMode(LED_PAIRING, OUTPUT);
|
||||
ledOff(LED_PAIRING);
|
||||
|
||||
pinMode(Battery_LED_1, OUTPUT);
|
||||
ledOff(Battery_LED_1);
|
||||
pinMode(Battery_LED_2, OUTPUT);
|
||||
ledOff(Battery_LED_2);
|
||||
|
||||
pinMode(Battery_LED_3, OUTPUT);
|
||||
ledOff(Battery_LED_3);
|
||||
|
||||
pinMode(Battery_LED_4, OUTPUT);
|
||||
ledOff(Battery_LED_4);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _VARIANT_ELECROW_THINKNODE_M4_
|
||||
#define _VARIANT_ELECROW_THINKNODE_M4_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
#define PINS_COUNT (48)
|
||||
#define NUM_DIGITAL_PINS (48)
|
||||
#define NUM_ANALOG_INPUTS (1)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define LED_BUILTIN -1
|
||||
#define LED_BLUE -1
|
||||
#define PIN_LED2 (32 + 9)
|
||||
#define LED_PAIRING (13)
|
||||
|
||||
#define Battery_LED_1 (15)
|
||||
#define Battery_LED_2 (17)
|
||||
#define Battery_LED_3 (32 + 2)
|
||||
#define Battery_LED_4 (32 + 4)
|
||||
|
||||
#define LED_STATE_ON 1
|
||||
|
||||
// Button
|
||||
#define PIN_BUTTON1 (4)
|
||||
|
||||
// Battery ADC
|
||||
#define PIN_A0 (2)
|
||||
#define BATTERY_PIN PIN_A0
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#define ADC_RESOLUTION 14
|
||||
#define BATTERY_SENSE_RESOLUTION_BITS 12
|
||||
#define BATTERY_SENSE_RESOLUTION 4096.0
|
||||
#define ADC_MULTIPLIER (2.00F)
|
||||
#undef AREF_VOLTAGE
|
||||
#define AREF_VOLTAGE 3.0
|
||||
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||
|
||||
#define HAS_SERIAL_BATTERY_LEVEL 1
|
||||
#define SERIAL_BATTERY_RX 30
|
||||
#define SERIAL_BATTERY_TX 5
|
||||
|
||||
static const uint8_t A0 = PIN_A0;
|
||||
|
||||
#define PIN_NFC1 (9)
|
||||
#define PIN_NFC2 (10)
|
||||
|
||||
// I2C
|
||||
#define WIRE_INTERFACES_COUNT 1
|
||||
#define PIN_WIRE_SDA (23)
|
||||
#define PIN_WIRE_SCL (25)
|
||||
|
||||
// actually the LORA Radio
|
||||
#define PIN_POWER_EN (11)
|
||||
|
||||
// charger status
|
||||
#define EXT_CHRG_DETECT (32 + 6)
|
||||
#define EXT_CHRG_DETECT_VALUE HIGH
|
||||
|
||||
// SPI
|
||||
#define SPI_INTERFACES_COUNT 1
|
||||
#define PIN_SPI_MISO (8)
|
||||
#define PIN_SPI_MOSI (7)
|
||||
#define PIN_SPI_SCK (6)
|
||||
|
||||
#define LORA_RESET (32 + 8)
|
||||
#define LORA_DIO1 (12)
|
||||
#define LORA_DIO2 (26)
|
||||
#define LORA_SCK PIN_SPI_SCK
|
||||
#define LORA_MISO PIN_SPI_MISO
|
||||
#define LORA_MOSI PIN_SPI_MOSI
|
||||
#define LORA_CS (27)
|
||||
|
||||
#define USE_LR1110
|
||||
#define LR1110_IRQ_PIN LORA_DIO1
|
||||
#define LR1110_NRESET_PIN LORA_RESET
|
||||
#define LR1110_BUSY_PIN LORA_DIO2
|
||||
#define LR1110_SPI_NSS_PIN LORA_CS
|
||||
#define LR1110_SPI_SCK_PIN LORA_SCK
|
||||
#define LR1110_SPI_MOSI_PIN LORA_MOSI
|
||||
#define LR1110_SPI_MISO_PIN LORA_MISO
|
||||
|
||||
#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
|
||||
#define LR11X0_DIO_AS_RF_SWITCH
|
||||
|
||||
// Peripherals on I2C bus. Active Low
|
||||
#define VEXT_ENABLE (32)
|
||||
#define VEXT_ON_VALUE LOW
|
||||
|
||||
// GPS L76K
|
||||
#define HAS_GPS 1
|
||||
#define GPS_L76K
|
||||
#define GPS_BAUDRATE 9600
|
||||
#define PIN_GPS_EN (32 + 11)
|
||||
#define GPS_EN_ACTIVE LOW
|
||||
#define PIN_GPS_RESET (3)
|
||||
#define GPS_RESET_MODE HIGH
|
||||
#define PIN_GPS_STANDBY (28)
|
||||
#define GPS_STANDBY_ACTIVE HIGH
|
||||
#define GPS_TX_PIN (32 + 12)
|
||||
#define GPS_RX_PIN (32 + 14)
|
||||
#define GPS_THREAD_INTERVAL 50
|
||||
|
||||
#define PIN_SERIAL1_RX GPS_RX_PIN
|
||||
#define PIN_SERIAL1_TX GPS_TX_PIN
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -134,14 +134,12 @@ static const uint8_t A0 = PIN_A0;
|
||||
#define BATTERY_SENSE_RESOLUTION_BITS 12
|
||||
#define BATTERY_SENSE_RESOLUTION 4096.0
|
||||
#undef AREF_VOLTAGE
|
||||
#define AREF_VOLTAGE 2.4
|
||||
#define VBAT_AR_INTERNAL AR_INTERNAL_2_4
|
||||
#define AREF_VOLTAGE 3.0
|
||||
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||
#define ADC_MULTIPLIER (1.75F)
|
||||
|
||||
#define HAS_SOLAR
|
||||
|
||||
#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3450
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -13,7 +13,6 @@ build_flags =
|
||||
-DPIN_SERIAL1_RX=PB7
|
||||
-DPIN_SERIAL1_TX=PB6
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
|
||||
-DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
|
||||
-DMESHTASTIC_EXCLUDE_I2C=1
|
||||
-DMESHTASTIC_EXCLUDE_GPS=1
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ build_flags =
|
||||
-DPIN_WIRE_SDA=PA11
|
||||
-DPIN_WIRE_SCL=PA12
|
||||
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
|
||||
-DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
|
||||
-DMESHTASTIC_EXCLUDE_I2C=1
|
||||
-DMESHTASTIC_EXCLUDE_GPS=1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user