Merge upstream dev into keymindCascade
This commit is contained in:
commit
45fe38ff97
10 files changed
+213
-2
No files matched your search
@@ -1531,6 +1531,24 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
i += (len - 4);
|
||||
writePendingSerialFrame(out_frame, i);
|
||||
clearPendingReqs();
|
||||
} else {
|
||||
// Forward unsolicited responses, including pushed telemetry, to the app.
|
||||
// Pending requests above retain their captured reply route.
|
||||
// A matching tag with no response body is malformed, not an unsolicited
|
||||
// push, and must not escape through the app's generic response path.
|
||||
if (len == 4 && ((pending_telemetry && tag == pending_telemetry)
|
||||
|| (pending_req && tag == pending_req))) {
|
||||
clearPendingReqs();
|
||||
return;
|
||||
}
|
||||
if (_serial != NULL && _serial->isConnected() && len <= MAX_FRAME_SIZE - 2) {
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_BINARY_RESPONSE;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], data, len);
|
||||
i += len;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,16 @@ public:
|
||||
// is not open. Defaults to the stricter data-connection signal on boards
|
||||
// that cannot distinguish a computer from USB power.
|
||||
virtual bool isUsbHostConnected() { return isUsbDataConnected(); }
|
||||
// True while a board can identify an active battery-charging source.
|
||||
virtual bool isChargerActive() { return false; }
|
||||
|
||||
// Optional, source-specific power detection. Boards that can distinguish a USB
|
||||
// supply from a solar charger override these; defaults keep every other board
|
||||
// unaffected (the generic CLI prints "n/a" when a capability is absent).
|
||||
virtual bool hasUsbPowerDetect() const { return false; }
|
||||
virtual bool isUsbPowered() { return false; }
|
||||
virtual bool hasSolarChargerDetect() const { return false; }
|
||||
virtual bool isSolarChargerActive() { return false; }
|
||||
virtual uint16_t getBootVoltage() { return 0; }
|
||||
virtual bool getWakeLpcompSupported() const { return false; }
|
||||
virtual uint32_t getResetReason() const { return 0; }
|
||||
|
||||
@@ -2862,6 +2862,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else if (memcmp(command, "ota", 3) == 0 && (command[3] == 0 || command[3] == ' ')) {
|
||||
strcpy(reply, "LoRa OTA is not included in this build; tempradio cannot enable it. Use an OTA-enabled repeater firmware");
|
||||
#endif
|
||||
} else if (strcmp(command, "power") == 0) {
|
||||
const char* usb_state = _board->hasUsbPowerDetect()
|
||||
? (_board->isUsbPowered() ? "yes" : "no")
|
||||
: "n/a";
|
||||
const char* solar_state = _board->hasSolarChargerDetect()
|
||||
? (_board->isSolarChargerActive() ? "yes" : "no")
|
||||
: "n/a";
|
||||
snprintf(reply, 160, "batt:%u mV usb:%s solar_chg:%s ext:%s charger:%s",
|
||||
(unsigned)_board->getBattMilliVolts(),
|
||||
usb_state,
|
||||
solar_state,
|
||||
_board->isExternalPowered() ? "yes" : "no",
|
||||
_board->isChargerActive() ? "yes" : "no");
|
||||
} else if (memcmp(command, "sensor get ", 11) == 0) {
|
||||
const char* key = command + 11;
|
||||
const char* val = _sensors->getSettingByKey(key);
|
||||
@@ -2983,6 +2996,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
} else {
|
||||
strcpy(reply, "gps provider not found");
|
||||
}
|
||||
} else if (strcmp(command, "gps diag") == 0) {
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
const char* setting = _sensors->getSettingByKey("gps");
|
||||
bool requested = setting != NULL && strcmp(setting, "1") == 0;
|
||||
int prefix_len = snprintf(reply, 160, "req:%u ", requested ? 1U : 0U);
|
||||
if (prefix_len < 0 || prefix_len >= 160) {
|
||||
reply[159] = 0;
|
||||
} else {
|
||||
l->formatDiagnostics(reply + prefix_len, 160 - prefix_len);
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "gps provider not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps setloc", 10) == 0) {
|
||||
_prefs->node_lat = _sensors->node_lat;
|
||||
_prefs->node_lon = _sensors->node_lon;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef GPS_POWERSAVING_ON_DURATION_SECS
|
||||
#define GPS_POWERSAVING_ON_DURATION_SECS (10UL * 60UL)
|
||||
@@ -78,4 +80,14 @@ public:
|
||||
virtual bool isEnabled() = 0;
|
||||
virtual void setPinEn(int pin_en) { (void)pin_en; }
|
||||
virtual int getPinEn() { return -1; }
|
||||
|
||||
// Format compact diagnostics in the caller-provided buffer. Providers that
|
||||
// have more information (for example UART counters) can override this.
|
||||
virtual void formatDiagnostics(char* out, size_t out_size) {
|
||||
if (out_size == 0) return;
|
||||
snprintf(out, out_size, "en:%u sat:%ld fix:%u",
|
||||
isEnabled() ? 1U : 0U,
|
||||
satellitesCount(),
|
||||
isValid() ? 1U : 0U);
|
||||
}
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <RTClib.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/sensors/GpsTimeValidation.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef GPS_LOOP_MAX_BYTES
|
||||
#define GPS_LOOP_MAX_BYTES 96
|
||||
@@ -56,6 +57,42 @@ class MicroNMEALocationProvider : public LocationProvider {
|
||||
unsigned long _last_time_sync = 0;
|
||||
static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes
|
||||
|
||||
#ifdef GPS_L76K
|
||||
void configureL76K() {
|
||||
// Reapply the constellation mode at each provider start instead of
|
||||
// assuming it survives a receiver power cycle.
|
||||
// Mode 7 enables GPS + BeiDou + GLONASS.
|
||||
delay(250);
|
||||
MicroNMEA::sendSentence(*_gps_serial, "$PCAS04,7");
|
||||
delay(250);
|
||||
}
|
||||
#endif
|
||||
// Observation-only diagnostics. These fields do not change GPS power,
|
||||
// timing, reset behaviour, or parser input.
|
||||
uint32_t _uart_bytes = 0;
|
||||
uint32_t _nmea_checksum_ok = 0;
|
||||
uint32_t _nmea_checksum_bad = 0;
|
||||
uint32_t _begin_calls = 0;
|
||||
uint32_t _stop_calls = 0;
|
||||
unsigned long _last_uart_ms = 0;
|
||||
unsigned long _last_nmea_ms = 0;
|
||||
unsigned long _last_fix_ms = 0;
|
||||
bool _uart_seen = false;
|
||||
bool _nmea_seen = false;
|
||||
bool _fix_seen = false;
|
||||
|
||||
static uint32_t ageMs(unsigned long timestamp) {
|
||||
return (uint32_t)(millis() - timestamp);
|
||||
}
|
||||
|
||||
static void formatAge(char* out, size_t out_size, bool seen, unsigned long timestamp) {
|
||||
if (!seen) {
|
||||
snprintf(out, out_size, "never");
|
||||
} else {
|
||||
snprintf(out, out_size, "%lu", (unsigned long)ageMs(timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
public :
|
||||
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) :
|
||||
nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) {
|
||||
@@ -81,6 +118,7 @@ public :
|
||||
}
|
||||
|
||||
void begin() override {
|
||||
_begin_calls++;
|
||||
claim();
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, GPS_EN_ACTIVE);
|
||||
@@ -96,9 +134,15 @@ public :
|
||||
delay(10);
|
||||
digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
|
||||
}
|
||||
#ifdef GPS_L76K
|
||||
// EnvironmentSensorManager calls reset() immediately after begin().
|
||||
// Configure only after that reset so the setting is not discarded.
|
||||
configureL76K();
|
||||
#endif
|
||||
}
|
||||
|
||||
void stop() override {
|
||||
_stop_calls++;
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
|
||||
}
|
||||
@@ -163,11 +207,37 @@ public :
|
||||
size_t processed = 0;
|
||||
while (_gps_serial->available() && processed < GPS_LOOP_MAX_BYTES) {
|
||||
char c = _gps_serial->read();
|
||||
_uart_bytes++;
|
||||
_last_uart_ms = millis();
|
||||
_uart_seen = true;
|
||||
#ifdef GPS_NMEA_DEBUG
|
||||
if (mesh::isUsbLoggingEnabled()) mesh::usbLoggingPort().print(c);
|
||||
#endif
|
||||
nmea.process(c);
|
||||
bool parsed = nmea.process(c);
|
||||
processed++;
|
||||
|
||||
// MicroNMEA leaves the completed sentence in its buffer after the
|
||||
// first line terminator. CRLF therefore counts once: CR observes the
|
||||
// sentence, then LF clears the now-empty buffer.
|
||||
if ((c == '\0' || c == '\r' || c == '\n') && nmea.getSentence()[0] != '\0') {
|
||||
if (MicroNMEA::testChecksum(nmea.getSentence())) {
|
||||
_nmea_checksum_ok++;
|
||||
_last_nmea_ms = millis();
|
||||
_nmea_seen = true;
|
||||
|
||||
// Refresh fix age only when a newly parsed GGA/RMC sentence
|
||||
// carries a valid fix. Other valid NMEA sentences must not
|
||||
// make an old position look fresh.
|
||||
const char* message_id = nmea.getMessageID();
|
||||
if (parsed && isValid() &&
|
||||
(strcmp(message_id, "GGA") == 0 || strcmp(message_id, "RMC") == 0)) {
|
||||
_last_fix_ms = millis();
|
||||
_fix_seen = true;
|
||||
}
|
||||
} else {
|
||||
_nmea_checksum_bad++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid()) time_valid = 0;
|
||||
@@ -195,4 +265,31 @@ public :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void formatDiagnostics(char* out, size_t out_size) override {
|
||||
if (out_size == 0) return;
|
||||
|
||||
char uart_age[11];
|
||||
char nmea_age[11];
|
||||
char fix_age[11];
|
||||
formatAge(uart_age, sizeof(uart_age), _uart_seen, _last_uart_ms);
|
||||
formatAge(nmea_age, sizeof(nmea_age), _nmea_seen, _last_nmea_ms);
|
||||
formatAge(fix_age, sizeof(fix_age), _fix_seen, _last_fix_ms);
|
||||
|
||||
// Compact enough for CommonCLI's 160-byte reply buffer, including the
|
||||
// "req:" prefix added by CommonCLI.
|
||||
snprintf(out, out_size,
|
||||
"en:%u ub:%lu ua:%s ok:%lu bad:%lu na:%s sat:%ld fix:%u fa:%s bc:%lu sc:%lu",
|
||||
isEnabled() ? 1U : 0U,
|
||||
(unsigned long)_uart_bytes,
|
||||
uart_age,
|
||||
(unsigned long)_nmea_checksum_ok,
|
||||
(unsigned long)_nmea_checksum_bad,
|
||||
nmea_age,
|
||||
satellitesCount(),
|
||||
isValid() ? 1U : 0U,
|
||||
fix_age,
|
||||
(unsigned long)_begin_calls,
|
||||
(unsigned long)_stop_calls);
|
||||
}
|
||||
};
|
||||
@@ -88,7 +88,14 @@ int main() {
|
||||
for (unsigned n=0;n<256;++n) {
|
||||
MyMesh idle;uint8_t data[256]={};
|
||||
idle.onContactResponse(contact,data,n);
|
||||
assert(idle.serial.frames.empty() && idle.clears==0);idle.checkGuards();
|
||||
const bool unsolicited=n>=4 && n+2<=MAX_FRAME_SIZE;
|
||||
assert(idle.serial.frames.size()==(unsolicited ? 1U : 0U) && idle.clears==0);
|
||||
if (unsolicited) {
|
||||
const auto& frame=idle.serial.frames[0];
|
||||
assert(frame.size()==n+2 && frame[0]==PUSH_CODE_BINARY_RESPONSE);
|
||||
assert(frame[1]==0 && memcmp(frame.data()+2,data,n)==0);
|
||||
}
|
||||
idle.checkGuards();
|
||||
for (bool control:{false,true}) {
|
||||
MyMesh value;mesh::Packet packet;packet.payload_len=n;
|
||||
if (control) value.onControlDataRecv(&packet);else value.onRawDataRecv(&packet);
|
||||
|
||||
@@ -15,6 +15,9 @@ void ThinkNodeM6Board::begin() {
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#endif
|
||||
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
pinMode(EXT_CHRG_DETECT, INPUT);
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,35 @@ public:
|
||||
return "Elecrow ThinkNode M6";
|
||||
}
|
||||
|
||||
bool hasUsbPowerDetect() const override { return true; }
|
||||
|
||||
bool isUsbPowered() override {
|
||||
// P0.13 is the dedicated USB / external power-detect line on the M6 PCB
|
||||
// (same pin the Meshtastic M6 variant uses for USB detection).
|
||||
return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE;
|
||||
}
|
||||
|
||||
bool hasSolarChargerDetect() const override { return true; }
|
||||
|
||||
bool isSolarChargerActive() override {
|
||||
// P0.15 is the active-low solar-charger status line. It reports the
|
||||
// charger's state, not photovoltaic voltage or current.
|
||||
return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
|
||||
}
|
||||
|
||||
bool isExternalPowered() override {
|
||||
if (isUsbPowered() || isSolarChargerActive()) {
|
||||
return true;
|
||||
}
|
||||
return NRF52Board::isExternalPowered();
|
||||
}
|
||||
|
||||
bool isChargerActive() override {
|
||||
// Preserve a generic charging indication while also exposing both
|
||||
// source-specific states (USB / solar) separately through the CLI.
|
||||
return isSolarChargerActive() || isUsbPowered();
|
||||
}
|
||||
|
||||
void shutdownPeripherals() override {
|
||||
NRF52Board::shutdownPeripherals();
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
|
||||
// External power and charger status lines.
|
||||
#define EXT_PWR_DETECT (13)
|
||||
#define EXT_PWR_DETECT_VALUE HIGH
|
||||
#define EXT_CHRG_DETECT (15)
|
||||
#define EXT_CHRG_DETECT_VALUE LOW
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Number of pins
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ build_flags = ${WioTrackerL1-1W.build_flags}
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D UI_NO_HIBERNATE
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
@@ -96,6 +97,7 @@ build_flags = ${WioTrackerL1-1W.build_flags}
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D UI_NO_HIBERNATE
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
-D RADIO_FEM_TXGAIN=1
|
||||
|
||||
Reference in new issue
Block a user