mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-19 18:01:30 +10:00
Merge branch 'master' of https://github.com/meshtastic/Meshtastic-device into windev
# Conflicts: # geeksville-private/windows-build-instructions.md # src/graphics/Screen.h
This commit is contained in:
+1
-1
@@ -40,7 +40,7 @@ bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2
|
||||
tParam1 = param1;
|
||||
tParam2 = param2;
|
||||
|
||||
timerAlarmWrite(timer, delayMsec * 1000L, false); // Do not reload, we want it to be a single shot timer
|
||||
timerAlarmWrite(timer, delayMsec * 1000UL, false); // Do not reload, we want it to be a single shot timer
|
||||
timerRestart(timer);
|
||||
timerAlarmEnable(timer);
|
||||
return true;
|
||||
|
||||
+53
-10
@@ -5,12 +5,35 @@
|
||||
#include "sleep.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
// FIXME. nasty hack cleanup how we load axp192
|
||||
#undef AXP192_SLAVE_ADDRESS
|
||||
#include "axp20x.h"
|
||||
|
||||
#ifdef TBEAM_V10
|
||||
AXP20X_Class axp;
|
||||
#else
|
||||
// Copy of the base class defined in axp20x.h.
|
||||
// I'd rather not inlude axp20x.h as it brings Wire dependency.
|
||||
class HasBatteryLevel {
|
||||
public:
|
||||
/**
|
||||
* Battery state of charge, from 0 to 100 or -1 for unknown
|
||||
*/
|
||||
virtual int getBattPercentage() { return -1; }
|
||||
|
||||
/**
|
||||
* The raw voltage of the battery or NAN if unknown
|
||||
*/
|
||||
virtual float getBattVoltage() { return NAN; }
|
||||
|
||||
/**
|
||||
* return true if there is a battery installed in this unit
|
||||
*/
|
||||
virtual bool isBatteryConnect() { return false; }
|
||||
|
||||
virtual bool isVBUSPlug() { return false; }
|
||||
virtual bool isChargeing() { return false; }
|
||||
};
|
||||
#endif
|
||||
|
||||
bool pmu_irq = false;
|
||||
@@ -51,7 +74,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
*/
|
||||
virtual int getBattPercentage()
|
||||
{
|
||||
float v = getBattVoltage() / 1000;
|
||||
float v = getBattVoltage();
|
||||
|
||||
if (v < noBatVolt)
|
||||
return -1; // If voltage is super low assume no battery installed
|
||||
@@ -67,13 +90,27 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
*/
|
||||
virtual float getBattVoltage()
|
||||
{
|
||||
// Tested ttgo eink nrf52 board and the reported value is perfect
|
||||
// DEBUG_MSG("raw val %u", raw);
|
||||
return
|
||||
|
||||
#ifndef ADC_MULTIPLIER
|
||||
#define ADC_MULTIPLIER 2.0
|
||||
#endif
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
1000.0 * 2.0 * (AREF_VOLTAGE / 1024.0) * analogRead(BATTERY_PIN);
|
||||
// Do not call analogRead() often.
|
||||
const uint32_t min_read_interval = 5000;
|
||||
if (millis() - last_read_time_ms > min_read_interval) {
|
||||
last_read_time_ms = millis();
|
||||
uint32_t raw = analogRead(BATTERY_PIN);
|
||||
float scaled = 1000.0 * ADC_MULTIPLIER * (AREF_VOLTAGE / 1024.0) * raw;
|
||||
|
||||
// DEBUG_MSG("battery gpio %d raw val=%u scaled=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled));
|
||||
last_read_value = scaled;
|
||||
return scaled;
|
||||
} else {
|
||||
return last_read_value;
|
||||
}
|
||||
#else
|
||||
NAN;
|
||||
return NAN;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -84,7 +121,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
virtual bool isVBUSPlug() { return getBattVoltage() > 1000 * chargingVolt; }
|
||||
virtual bool isVBUSPlug() { return getBattVoltage() > chargingVolt; }
|
||||
|
||||
/// Assume charging if we have a battery and external power is connected.
|
||||
/// we can't be smart enough to say 'full'?
|
||||
@@ -93,7 +130,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
private:
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is pumping
|
||||
/// in power
|
||||
const float fullVolt = 4.2, emptyVolt = 3.27, chargingVolt = 4.3, noBatVolt = 2.1;
|
||||
|
||||
/// For heltecs with no battery connected, the measured voltage is 2204, so raising to 2230 from 2100
|
||||
const float fullVolt = 4200, emptyVolt = 3270, chargingVolt = 4210, noBatVolt = 2230;
|
||||
float last_read_value = 0.0;
|
||||
uint32_t last_read_time_ms = 0;
|
||||
} analogLevel;
|
||||
|
||||
Power::Power() : OSThread("Power") {}
|
||||
@@ -101,7 +142,7 @@ Power::Power() : OSThread("Power") {}
|
||||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef BATTERY_PIN
|
||||
DEBUG_MSG("Using analog input for battery level\n");
|
||||
DEBUG_MSG("Using analog input %d for battery level\n", BATTERY_PIN);
|
||||
|
||||
// disable any internal pullups
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
@@ -140,6 +181,8 @@ void Power::shutdown()
|
||||
#ifdef TBEAM_V10
|
||||
DEBUG_MSG("Shutting down\n");
|
||||
axp.shutdown();
|
||||
#elif NRF52_SERIES
|
||||
doDeepSleep(DELAY_FOREVER);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+74
-28
@@ -9,6 +9,23 @@
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
/// Should we behave as if we have AC power now?
|
||||
static bool isPowered()
|
||||
{
|
||||
bool isRouter = radioConfig.preferences.is_router;
|
||||
|
||||
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||
bool isLowPower = radioConfig.preferences.is_low_power || isRouter;
|
||||
|
||||
/* To determine if we're externally powered, assumptions
|
||||
1) If we're powered up and there's no battery, we must be getting power externally. (because we'd be dead otherwise)
|
||||
|
||||
2) If we detect USB power from the power management chip, we must be getting power externally.
|
||||
*/
|
||||
return !isLowPower && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB());
|
||||
}
|
||||
|
||||
static void sdsEnter()
|
||||
{
|
||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||
@@ -97,7 +114,8 @@ static void lsIdle()
|
||||
static void lsExit()
|
||||
{
|
||||
// setGPSPower(true); // restore GPS power
|
||||
gps->forceWake(true);
|
||||
if (gps)
|
||||
gps->forceWake(true);
|
||||
}
|
||||
|
||||
static void nbEnter()
|
||||
@@ -118,14 +136,34 @@ static void serialEnter()
|
||||
{
|
||||
setBluetoothEnable(false);
|
||||
screen->setOn(true);
|
||||
screen->print("Using API...\n");
|
||||
screen->print("Serial connected\n");
|
||||
}
|
||||
|
||||
static void serialExit()
|
||||
{
|
||||
screen->print("Serial disconnected\n");
|
||||
}
|
||||
|
||||
static void powerEnter()
|
||||
{
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
screen->print("Powered...\n");
|
||||
if (!isPowered()) {
|
||||
// If we got here, we are in the wrong state - we should be in powered, let that state ahndle things
|
||||
DEBUG_MSG("Loss of power in Powered\n");
|
||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||
} else {
|
||||
screen->setOn(true);
|
||||
setBluetoothEnable(true);
|
||||
screen->print("Powered...\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void powerIdle()
|
||||
{
|
||||
if (!isPowered()) {
|
||||
// If we got here, we are in the wrong state
|
||||
DEBUG_MSG("Loss of power in Powered\n");
|
||||
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
static void powerExit()
|
||||
@@ -152,6 +190,14 @@ static void onEnter()
|
||||
}
|
||||
}
|
||||
|
||||
static void onIdle()
|
||||
{
|
||||
if (isPowered()) {
|
||||
// If we got here, we are in the wrong state - we should be in powered, let that state ahndle things
|
||||
powerFSM.trigger(EVENT_POWER_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
static void screenPress()
|
||||
{
|
||||
screen->onPress();
|
||||
@@ -163,26 +209,16 @@ State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
||||
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
||||
State stateNB(nbEnter, NULL, NULL, "NB");
|
||||
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||
State stateSERIAL(serialEnter, NULL, NULL, "SERIAL");
|
||||
State stateSERIAL(serialEnter, NULL, serialExit, "SERIAL");
|
||||
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||
State stateON(onEnter, NULL, NULL, "ON");
|
||||
State statePOWER(powerEnter, NULL, powerExit, "POWER");
|
||||
State stateON(onEnter, onIdle, NULL, "ON");
|
||||
State statePOWER(powerEnter, powerIdle, powerExit, "POWER");
|
||||
Fsm powerFSM(&stateBOOT);
|
||||
|
||||
void PowerFSM_setup()
|
||||
{
|
||||
bool isRouter = radioConfig.preferences.is_router;
|
||||
|
||||
// If we are not a router and we already have AC power go to POWER state after init, otherwise go to ON
|
||||
// We assume routers might be powered all the time, but from a low current (solar) source
|
||||
bool isLowPower = radioConfig.preferences.is_low_power || isRouter;
|
||||
|
||||
/* To determine if we're externally powered, assumptions
|
||||
1) If we're powered up and there's no battery, we must be getting power externally. (because we'd be dead otherwise)
|
||||
|
||||
2) If we detect USB power from the power management chip, we must be getting power externally.
|
||||
*/
|
||||
bool hasPower = !isLowPower && powerStatus && (!powerStatus->getHasBattery() || powerStatus->getHasUSB());
|
||||
bool hasPower = isPowered();
|
||||
|
||||
DEBUG_MSG("PowerFSM init, USB power=%d\n", hasPower);
|
||||
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
|
||||
@@ -230,25 +266,33 @@ void PowerFSM_setup()
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_RECEIVED_TEXT_MSG, NULL, "Received text"); // restarts the sleep timer
|
||||
}
|
||||
|
||||
// If we are not in statePOWER but get a serial connection, suppress sleep (and keep the screen on) while connected
|
||||
powerFSM.add_transition(&stateLS, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateNB, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateDARK, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&stateON, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
powerFSM.add_transition(&statePOWER, &stateSERIAL, EVENT_SERIAL_CONNECTED, NULL, "serial API");
|
||||
|
||||
if (!isLowPower) {
|
||||
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
}
|
||||
// If we get power connected, go to the power connect state
|
||||
powerFSM.add_transition(&stateLS, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateNB, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateDARK, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
powerFSM.add_transition(&stateON, &statePOWER, EVENT_POWER_CONNECTED, NULL, "power connect");
|
||||
|
||||
powerFSM.add_transition(&statePOWER, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||
// powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_POWER_DISCONNECTED, NULL, "power disconnected");
|
||||
|
||||
powerFSM.add_transition(&stateSERIAL, &stateNB, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
||||
// the only way to leave state serial is for the client to disconnect (or we timeout and force disconnect them)
|
||||
// when we leave, go to ON (which might not be the correct state if we have power connected, we will fix that in onEnter)
|
||||
powerFSM.add_transition(&stateSERIAL, &stateON, EVENT_SERIAL_DISCONNECTED, NULL, "serial disconnect");
|
||||
|
||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||
|
||||
// each time we get a new update packet make sure we are staying in the ON state so the screen stays awake (also we don't
|
||||
// shutdown bluetooth if is_router)
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
|
||||
|
||||
powerFSM.add_transition(&stateNB, &stateDARK, EVENT_PACKET_FOR_PHONE, NULL, "Packet for phone");
|
||||
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK, getPref_screen_on_secs() * 1000, NULL, "Screen-on timeout");
|
||||
@@ -259,7 +303,9 @@ void PowerFSM_setup()
|
||||
#ifndef NRF52_SERIES
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||
// I don't think this transition is correct, turning off for now - @geeksville
|
||||
// powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout");
|
||||
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout");
|
||||
#else
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define EVENT_SERIAL_DISCONNECTED 12
|
||||
#define EVENT_POWER_CONNECTED 13
|
||||
#define EVENT_POWER_DISCONNECTED 14
|
||||
#define EVENT_FIRMWARE_UPDATE 15 // We just received a new firmware update packet from the phone
|
||||
|
||||
extern Fsm powerFSM;
|
||||
extern State statePOWER, stateSERIAL;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "RedirectablePrint.h"
|
||||
#include "RTC.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <assert.h>
|
||||
@@ -27,7 +28,8 @@ size_t RedirectablePrint::write(uint8_t c)
|
||||
#endif
|
||||
|
||||
dest->write(c);
|
||||
return 1; // We always claim one was written, rather than trusting what the serial port said (which could be zero)
|
||||
return 1; // We always claim one was written, rather than trusting what the
|
||||
// serial port said (which could be zero)
|
||||
}
|
||||
|
||||
size_t RedirectablePrint::vprintf(const char *format, va_list arg)
|
||||
@@ -52,10 +54,6 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
|
||||
return len;
|
||||
}
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
#define SEC_PER_HOUR 3600
|
||||
#define SEC_PER_MIN 60
|
||||
|
||||
size_t RedirectablePrint::logDebug(const char *format, ...)
|
||||
{
|
||||
size_t r = 0;
|
||||
@@ -71,9 +69,9 @@ size_t RedirectablePrint::logDebug(const char *format, ...)
|
||||
|
||||
// If we are the first message on a report, include the header
|
||||
if (!isContinuationMessage) {
|
||||
struct timeval tv;
|
||||
if (!gettimeofday(&tv, NULL)) {
|
||||
long hms = tv.tv_sec % SEC_PER_DAY;
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityFromNet);
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
|
||||
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
|
||||
|
||||
+19
-21
@@ -6,52 +6,50 @@
|
||||
|
||||
#define Port Serial
|
||||
|
||||
SerialConsole console;
|
||||
SerialConsole *console;
|
||||
|
||||
void consoleInit()
|
||||
{
|
||||
new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
|
||||
}
|
||||
|
||||
void consolePrintf(const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
console.vprintf(format, arg);
|
||||
console->vprintf(format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
|
||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
|
||||
{
|
||||
assert(!console);
|
||||
console = this;
|
||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
|
||||
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
|
||||
|
||||
Port.begin(SERIAL_BAUD);
|
||||
emitRebooted();
|
||||
}
|
||||
|
||||
/// Do late init that can't happen at constructor time
|
||||
void SerialConsole::init()
|
||||
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
|
||||
bool SerialConsole::checkIsConnected()
|
||||
{
|
||||
Port.begin(SERIAL_BAUD);
|
||||
StreamAPI::init();
|
||||
emitRebooted();
|
||||
uint32_t now = millis();
|
||||
return (now - lastContactMsec) < getPref_phone_timeout_secs() * 1000UL;
|
||||
}
|
||||
|
||||
/**
|
||||
* we override this to notice when we've received a protobuf over the serial
|
||||
* stream. Then we shunt off debug serial output.
|
||||
*/
|
||||
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
|
||||
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
|
||||
{
|
||||
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
|
||||
if (!radioConfig.preferences.debug_log_enabled)
|
||||
setDestination(&noopPrint);
|
||||
canWrite = true;
|
||||
|
||||
StreamAPI::handleToRadio(buf, len);
|
||||
return StreamAPI::handleToRadio(buf, len);
|
||||
}
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
void SerialConsole::onConnectionChanged(bool connected)
|
||||
{
|
||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
// FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
|
||||
// received a packet in a while
|
||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -11,14 +11,11 @@ class SerialConsole : public StreamAPI, public RedirectablePrint
|
||||
public:
|
||||
SerialConsole();
|
||||
|
||||
/// Do late init that can't happen at constructor time
|
||||
virtual void init();
|
||||
|
||||
/**
|
||||
* we override this to notice when we've received a protobuf over the serial stream. Then we shunt off
|
||||
* debug serial output.
|
||||
*/
|
||||
virtual void handleToRadio(const uint8_t *buf, size_t len);
|
||||
virtual bool handleToRadio(const uint8_t *buf, size_t len);
|
||||
|
||||
virtual size_t write(uint8_t c)
|
||||
{
|
||||
@@ -28,11 +25,13 @@ class SerialConsole : public StreamAPI, public RedirectablePrint
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Hookable to find out when connection changes
|
||||
virtual void onConnectionChanged(bool connected);
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected();
|
||||
};
|
||||
|
||||
// A simple wrapper to allow non class aware code write to the console
|
||||
void consolePrintf(const char *format, ...);
|
||||
void consoleInit();
|
||||
|
||||
extern SerialConsole console;
|
||||
extern SerialConsole *console;
|
||||
|
||||
+1
-2
@@ -47,8 +47,7 @@ class AirTime : private concurrency::OSThread
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms);
|
||||
|
||||
protected:
|
||||
|
||||
virtual int32_t runOnce();
|
||||
virtual int32_t runOnce() override;
|
||||
};
|
||||
|
||||
extern AirTime *airTime;
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "buzz.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef NRF52_SERIES
|
||||
#include "variant.h"
|
||||
#endif
|
||||
|
||||
#ifndef PIN_BUZZER
|
||||
|
||||
// Noop methods for boards w/o buzzer
|
||||
void playBeep(){};
|
||||
void playStartMelody(){};
|
||||
void playShutdownMelody(){};
|
||||
|
||||
#else
|
||||
#include "Tone.h"
|
||||
|
||||
extern "C" void delay(uint32_t dwMs);
|
||||
|
||||
struct ToneDuration {
|
||||
int frequency_khz;
|
||||
int duration_ms;
|
||||
};
|
||||
|
||||
// Some common frequencies.
|
||||
#define NOTE_C3 131
|
||||
#define NOTE_CS3 139
|
||||
#define NOTE_D3 147
|
||||
#define NOTE_DS3 156
|
||||
#define NOTE_E3 165
|
||||
#define NOTE_F3 175
|
||||
#define NOTE_FS3 185
|
||||
#define NOTE_G3 196
|
||||
#define NOTE_GS3 208
|
||||
#define NOTE_A3 220
|
||||
#define NOTE_AS3 233
|
||||
#define NOTE_B3 247
|
||||
|
||||
const int DURATION_1_8 = 125; // 1/8 note
|
||||
const int DURATION_1_4 = 250; // 1/4 note
|
||||
|
||||
void playTones(const ToneDuration *tone_durations, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
const auto &tone_duration = tone_durations[i];
|
||||
tone(PIN_BUZZER, tone_duration.frequency_khz, tone_duration.duration_ms);
|
||||
// to distinguish the notes, set a minimum time between them.
|
||||
delay(1.3 * tone_duration.duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void playBeep() { tone(PIN_BUZZER, NOTE_B3, DURATION_1_4); }
|
||||
|
||||
void playStartMelody() {
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_4},
|
||||
{NOTE_B3, DURATION_1_8},
|
||||
{NOTE_B3, DURATION_1_8}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
void playShutdownMelody() {
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_4},
|
||||
{NOTE_G3, DURATION_1_8},
|
||||
{NOTE_D3, DURATION_1_8}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
void playBeep();
|
||||
void playStartMelody();
|
||||
void playShutdownMelody();
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "../freertosinc.h"
|
||||
|
||||
namespace concurrency
|
||||
@@ -28,4 +27,4 @@ class BinarySemaphoreFreeRTOS
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
} // namespace concurrency
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "../freertosinc.h"
|
||||
|
||||
namespace concurrency
|
||||
@@ -28,4 +27,4 @@ class BinarySemaphorePosix
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
} // namespace concurrency
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "NotifiedWorkerThread.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <assert.h>
|
||||
|
||||
namespace concurrency
|
||||
@@ -28,6 +29,7 @@ IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
|
||||
if (overwrite || notification == 0) {
|
||||
enabled = true;
|
||||
setInterval(0); // Run ASAP
|
||||
runASAP = true;
|
||||
|
||||
notification = v;
|
||||
if (debugNotification)
|
||||
|
||||
+51
-13
@@ -227,8 +227,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define GPS_TX_PIN 15
|
||||
|
||||
#elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V2)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_HELTEC
|
||||
|
||||
// the default ESP32 Pin of 15 is the Oled SCL, set to 36 and 37 and works fine.
|
||||
// Tested on Neo6m module.
|
||||
@@ -256,6 +254,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define LORA_DIO1 35 // Not really used
|
||||
#define LORA_DIO2 34 // Not really used
|
||||
|
||||
// ratio of voltage divider = 3.20 (R1=100k, R2=220k)
|
||||
#define ADC_MULTIPLIER 3.2
|
||||
|
||||
#ifdef HELTEC_V2_0
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_HELTEC_V2_0
|
||||
|
||||
#define BATTERY_PIN 13 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HELTEC_V2_1
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_HELTEC_V2_1
|
||||
|
||||
#define BATTERY_PIN 37 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined(TLORA_V1)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_TLORA_V1
|
||||
@@ -311,7 +328,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#elif defined(TLORA_V1_3)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_TLORA_V1p3
|
||||
#define HW_VENDOR HardwareModel_TLORA_V1_1p3
|
||||
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
@@ -338,11 +355,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#elif defined(TLORA_V2_1_16)
|
||||
// This string must exactly match the case used in release file names or the android updater won't work
|
||||
#define HW_VENDOR HardwareModel_TLORA_V2_1p6_
|
||||
#define HW_VENDOR HardwareModel_TLORA_V2_1_1p6
|
||||
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
#define GPS_RX_PIN 36
|
||||
#define GPS_RX_PIN 15 // per @der_bear on the forum, 36 is incorrect for this board type and 15 is a better pick
|
||||
#define GPS_TX_PIN 13
|
||||
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
@@ -382,8 +399,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define LED_PIN 12 // If defined we will blink this LED
|
||||
//#define BUTTON_PIN 36 // If defined, this will be used for user button presses (ToDo problem on that line on debug screen -->
|
||||
//Long press start!) #define BUTTON_NEED_PULLUP //GPIOs 34 to 39 are GPIs – input only pins. These pins don’t have internal
|
||||
//pull-ups or pull-down resistors.
|
||||
// Long press start!) #define BUTTON_NEED_PULLUP //GPIOs 34 to 39 are GPIs – input only pins. These pins don’t have internal
|
||||
// pull-ups or pull-down resistors.
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 38 // a No connect on the SX1262 module
|
||||
@@ -409,6 +426,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define HW_VENDOR HardwareModel_PPR
|
||||
|
||||
#elif defined(RAK4630)
|
||||
|
||||
#define HW_VENDOR HardwareModel_RAK4631
|
||||
|
||||
#elif defined(TTGO_T_ECHO)
|
||||
|
||||
#define HW_VENDOR HardwareModel_T_ECHO
|
||||
|
||||
#elif NRF52_SERIES
|
||||
|
||||
#define HW_VENDOR HardwareModel_NRF52_UNKNOWN
|
||||
@@ -419,11 +444,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#define USE_SIM_RADIO
|
||||
|
||||
#define USE_RF95
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET RADIOLIB_NC
|
||||
#define LORA_DIO1 33 // Not really used
|
||||
#define LORA_DIO2 32 // Not really used
|
||||
// Pine64 uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||
// not found then probe for SX1262. Currently the RF95 code is disabled because I think the RF95 module won't need to ship.
|
||||
// #define USE_RF95
|
||||
#define USE_SX1262
|
||||
|
||||
// Fake SPI device selections
|
||||
#define RF95_SCK 5
|
||||
@@ -431,6 +455,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define RF95_MOSI 27
|
||||
#define RF95_NSS RADIOLIB_NC // the ch341f spi controller does CS for us
|
||||
|
||||
#define LORA_DIO0 26 // a No connect on the SX1262 module
|
||||
#define LORA_RESET 14
|
||||
#define LORA_DIO1 33 // SX1262 IRQ, called DIO0 on pinelora schematic, pin 7 on ch341f "ack" - FIXME, enable hwints in linux
|
||||
#define LORA_DIO2 32 // SX1262 BUSY, actually connected to "DIO5" on pinelora schematic, pin 8 on ch341f "slct"
|
||||
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
|
||||
|
||||
#ifdef USE_SX1262
|
||||
#define SX1262_CS 20 // CS0 on pinelora schematic, hooked to gpio D0 on ch341f
|
||||
#define SX1262_DIO1 LORA_DIO1
|
||||
#define SX1262_BUSY LORA_DIO2
|
||||
#define SX1262_RESET LORA_RESET
|
||||
// HOPE RFM90 does not have a TCXO therefore not SX1262_E22
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// DEBUG LED
|
||||
@@ -457,7 +495,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "SerialConsole.h"
|
||||
|
||||
#define DEBUG_PORT console // Serial debug port
|
||||
#define DEBUG_PORT (*console) // Serial debug port
|
||||
|
||||
// What platforms should use SEGGER?
|
||||
#ifdef NRF52_SERIES
|
||||
|
||||
+4
-1
@@ -4,5 +4,8 @@
|
||||
|
||||
#include "mesh/generated/mesh.pb.h" // For CriticalErrorCode
|
||||
|
||||
/// A macro that include filename and line
|
||||
#define RECORD_CRITICALERROR(code) recordCriticalError(code, __LINE__, __FILE__)
|
||||
|
||||
/// Record an error that should be reported via analytics
|
||||
void recordCriticalError(CriticalErrorCode code = CriticalErrorCode_Unspecified, uint32_t address = 0);
|
||||
void recordCriticalError(CriticalErrorCode code = CriticalErrorCode_Unspecified, uint32_t address = 0, const char *filename = NULL);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "../concurrency/LockGuard.h"
|
||||
#include "../graphics/Screen.h"
|
||||
#include "../main.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "configuration.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#include "NodeDB.h"
|
||||
#include "../graphics/Screen.h"
|
||||
#include "../main.h"
|
||||
|
||||
#include <CRC32.h>
|
||||
#include <Update.h>
|
||||
@@ -16,7 +16,6 @@
|
||||
int16_t updateResultHandle = -1;
|
||||
|
||||
static CRC32 crc;
|
||||
static uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
|
||||
static uint32_t updateExpectedSize, updateActualSize;
|
||||
static uint8_t update_result;
|
||||
@@ -51,8 +50,8 @@ int update_size_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
|
||||
screen->startFirmwareUpdateScreen();
|
||||
if (RadioLibInterface::instance)
|
||||
RadioLibInterface::instance->disable(); // FIXME, nasty hack - the RF95 ISR/SPI code on ESP32 can fail while we are
|
||||
// writing flash - shut the radio off during updates
|
||||
RadioLibInterface::instance->disable(); // FIXME, nasty hack - the RF95 ISR/SPI code on ESP32 can fail while we
|
||||
// are writing flash - shut the radio off during updates
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ int update_data_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
updateActualSize += len;
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
powerFSM.trigger(EVENT_FIRMWARE_UPDATE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -107,8 +106,7 @@ int update_crc32_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble
|
||||
if (update_region == U_SPIFFS) {
|
||||
DEBUG_MSG("SPIFFS updated!\n");
|
||||
nodeDB.saveToDisk(); // Since we just wiped spiffs, we need to save our current state
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
DEBUG_MSG("Appload updated, rebooting in 5 seconds!\n");
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
@@ -140,14 +138,6 @@ int update_region_callback(uint16_t conn_handle, uint16_t attr_handle, struct bl
|
||||
return chr_readwrite8(&update_region, sizeof(update_region), ctxt);
|
||||
}
|
||||
|
||||
void bluetoothRebootCheck()
|
||||
{
|
||||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||
DEBUG_MSG("Rebooting for update\n");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
See bluetooth-api.md
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
void reinitUpdateService();
|
||||
|
||||
void bluetoothRebootCheck();
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
#include <Preferences.h>
|
||||
#include <driver/rtc_io.h>
|
||||
#include <nvs.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
@@ -45,6 +46,18 @@ void esp32Setup()
|
||||
DEBUG_MSG("NVS: UsedEntries %d, FreeEntries %d, AllEntries %d\n", nvs_stats.used_entries, nvs_stats.free_entries,
|
||||
nvs_stats.total_entries);
|
||||
|
||||
DEBUG_MSG("Setup Preferences in Flash Storage\n");
|
||||
|
||||
// Create object to store our persistant data
|
||||
Preferences preferences;
|
||||
preferences.begin("meshtastic", false);
|
||||
|
||||
uint32_t rebootCounter = preferences.getUInt("rebootCounter", 0);
|
||||
rebootCounter++;
|
||||
preferences.putUInt("rebootCounter", rebootCounter);
|
||||
preferences.end();
|
||||
DEBUG_MSG("Number of Device Reboots: %d\n", rebootCounter);
|
||||
|
||||
// enableModemSleep();
|
||||
|
||||
// Since we are turning on watchdogs rather late in the release schedule, we really don't want to catch any
|
||||
@@ -84,7 +97,6 @@ void esp32Loop()
|
||||
{
|
||||
esp_task_wdt_reset(); // service our app level watchdog
|
||||
loopBLE();
|
||||
bluetoothRebootCheck();
|
||||
|
||||
// for debug printing
|
||||
// radio.radioIf.canSleep();
|
||||
|
||||
+49
-2
@@ -7,7 +7,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
// If we have a serial GPS port it will not be null
|
||||
#ifdef GPS_RX_PIN
|
||||
#ifdef GPS_SERIAL_NUM
|
||||
HardwareSerial _serial_gps_real(GPS_SERIAL_NUM);
|
||||
HardwareSerial *GPS::_serial_gps = &_serial_gps_real;
|
||||
#elif defined(NRF52840_XXAA) || defined(NRF52833_XXAA)
|
||||
@@ -34,7 +34,8 @@ bool GPS::setupGPS()
|
||||
if (_serial_gps && !didSerialInit) {
|
||||
didSerialInit = true;
|
||||
|
||||
#ifdef GPS_RX_PIN
|
||||
// ESP32 has a special set of parameters vs other arduino ports
|
||||
#if defined(GPS_RX_PIN) && !defined(NO_ESP32)
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
@@ -308,3 +309,49 @@ int GPS::prepareDeepSleep(void *unused)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef GPS_TX_PIN
|
||||
#include "UBloxGPS.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_AIR530_GPS
|
||||
#include "Air530GPS.h"
|
||||
#elif !defined(NO_GPS)
|
||||
#include "NMEAGPS.h"
|
||||
#endif
|
||||
|
||||
GPS *createGps()
|
||||
{
|
||||
|
||||
#ifdef NO_GPS
|
||||
return nullptr;
|
||||
#else
|
||||
// If we don't have bidirectional comms, we can't even try talking to UBLOX
|
||||
#ifdef GPS_TX_PIN
|
||||
// Init GPS - first try ublox
|
||||
UBloxGPS *ublox = new UBloxGPS();
|
||||
|
||||
if (!ublox->setup()) {
|
||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||
delete ublox;
|
||||
ublox = NULL;
|
||||
} else {
|
||||
return ublox;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GPS::_serial_gps) {
|
||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||
// assume NMEA at 9600 baud.
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
#ifdef HAS_AIR530_GPS
|
||||
GPS *new_gps = new Air530GPS();
|
||||
#else
|
||||
GPS *new_gps = new NMEAGPS();
|
||||
#endif
|
||||
new_gps->setup();
|
||||
return new_gps;
|
||||
}
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
@@ -71,6 +71,9 @@ class GPS : private concurrency::OSThread
|
||||
* */
|
||||
void forceWake(bool on);
|
||||
|
||||
// Some GPS modules (ublock) require factory reset
|
||||
virtual bool factoryReset() { return true; }
|
||||
|
||||
protected:
|
||||
/// Do gps chipset specific init, return true for success
|
||||
virtual bool setupGPS();
|
||||
@@ -145,4 +148,8 @@ class GPS : private concurrency::OSThread
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
// Creates an instance of the GPS class.
|
||||
// Returns the new instance or null if the GPS is not present.
|
||||
GPS* createGps();
|
||||
|
||||
extern GPS *gps;
|
||||
|
||||
+14
-5
@@ -39,22 +39,31 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
|
||||
currentQuality = q;
|
||||
shouldSet = true;
|
||||
DEBUG_MSG("Upgrading time to RTC %ld secs (quality %d)\n", tv->tv_sec, q);
|
||||
} else if(q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000L)) {
|
||||
} else if(q == RTCQualityGPS && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
|
||||
// Every 12 hrs we will slam in a new GPS time, to correct for local RTC clock drift
|
||||
shouldSet = true;
|
||||
DEBUG_MSG("Reapplying GPS time to correct clock drift %ld secs\n", tv->tv_sec);
|
||||
DEBUG_MSG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec);
|
||||
}
|
||||
else
|
||||
shouldSet = false;
|
||||
|
||||
if (shouldSet) {
|
||||
lastSetMsec = now;
|
||||
|
||||
// This delta value works on all platforms
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv->tv_sec;
|
||||
|
||||
// If this platform has a setable RTC, set it
|
||||
#ifndef NO_ESP32
|
||||
settimeofday(tv, NULL);
|
||||
#else
|
||||
DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n");
|
||||
#endif
|
||||
|
||||
// nrf52 doesn't have a readable RTC (yet - software not written)
|
||||
#if defined(PORTDUINO) || !defined(NO_ESP32)
|
||||
readFromRTC();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -83,7 +92,7 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
|
||||
uint32_t getTime()
|
||||
{
|
||||
return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
return (((uint32_t) millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
|
||||
}
|
||||
|
||||
uint32_t getValidTime(RTCQuality minQuality)
|
||||
|
||||
+5
-1
@@ -27,4 +27,8 @@ uint32_t getTime();
|
||||
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
|
||||
uint32_t getValidTime(RTCQuality minQuality);
|
||||
|
||||
void readFromRTC();
|
||||
void readFromRTC();
|
||||
|
||||
#define SEC_PER_DAY 86400
|
||||
#define SEC_PER_HOUR 3600
|
||||
#define SEC_PER_MIN 60
|
||||
@@ -43,7 +43,7 @@ bool UBloxGPS::setupGPS()
|
||||
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
|
||||
|
||||
if (!setUBXMode())
|
||||
recordCriticalError(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
||||
+51
-24
@@ -28,11 +28,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "Screen.h"
|
||||
#include "configuration.h"
|
||||
#include "fonts.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "plugins/TextMessagePlugin.h"
|
||||
#include "mesh/Channels.h"
|
||||
#include "plugins/TextMessagePlugin.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -129,7 +130,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
|
||||
// Draw version in upper right
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%s",
|
||||
xstr(APP_VERSION)); // Note: we don't bother printing region or now, it makes the string too long
|
||||
xstr(APP_VERSION_SHORT)); // Note: we don't bother printing region or now, it makes the string too long
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(buf), y + 0, buf);
|
||||
screen->forceDisplay();
|
||||
|
||||
@@ -155,24 +156,22 @@ static void drawPluginFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
||||
{
|
||||
uint8_t plugin_frame;
|
||||
// there's a little but in the UI transition code
|
||||
// where it invokes the function at the correct offset
|
||||
// where it invokes the function at the correct offset
|
||||
// in the array of "drawScreen" functions; however,
|
||||
// the passed-state doesn't quite reflect the "current"
|
||||
// the passed-state doesn't quite reflect the "current"
|
||||
// screen, so we have to detect it.
|
||||
if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) {
|
||||
// if we're transitioning from the end of the frame list back around to the first
|
||||
// if we're transitioning from the end of the frame list back around to the first
|
||||
// frame, then we want this to be `0`
|
||||
plugin_frame = state->transitionFrameTarget;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// otherwise, just display the plugin frame that's aligned with the current frame
|
||||
plugin_frame = state->currentFrame;
|
||||
//DEBUG_MSG("Screen is not in transition. Frame: %d\n\n", plugin_frame);
|
||||
// DEBUG_MSG("Screen is not in transition. Frame: %d\n\n", plugin_frame);
|
||||
}
|
||||
//DEBUG_MSG("Drawing Plugin Frame %d\n\n", plugin_frame);
|
||||
// DEBUG_MSG("Drawing Plugin Frame %d\n\n", plugin_frame);
|
||||
MeshPlugin &pi = *pluginFrames.at(plugin_frame);
|
||||
pi.drawFrame(display,state,x,y);
|
||||
|
||||
pi.drawFrame(display, state, x, y);
|
||||
}
|
||||
|
||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -204,11 +203,10 @@ static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait...");
|
||||
|
||||
//display->setFont(FONT_LARGE);
|
||||
//display->drawString(64 + x, 26 + y, btPIN);
|
||||
// display->setFont(FONT_LARGE);
|
||||
// display->drawString(64 + x, 26 + y, btPIN);
|
||||
}
|
||||
|
||||
|
||||
/// Draw the last text message we received
|
||||
static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
@@ -719,6 +717,7 @@ void Screen::handleSetOn(bool on)
|
||||
dispdev.displayOn();
|
||||
enabled = true;
|
||||
setInterval(0); // Draw ASAP
|
||||
runASAP = true;
|
||||
} else {
|
||||
DEBUG_MSG("Turning off screen\n");
|
||||
dispdev.displayOff();
|
||||
@@ -793,7 +792,7 @@ void Screen::setup()
|
||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
if(textMessagePlugin)
|
||||
if (textMessagePlugin)
|
||||
textMessageObserver.observe(textMessagePlugin);
|
||||
}
|
||||
|
||||
@@ -842,7 +841,7 @@ int32_t Screen::runOnce()
|
||||
break;
|
||||
case Cmd::START_FIRMWARE_UPDATE_SCREEN:
|
||||
handleStartFirmwareUpdateScreen();
|
||||
break;
|
||||
break;
|
||||
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
||||
case Cmd::STOP_BOOT_SCREEN:
|
||||
setFrames();
|
||||
@@ -936,7 +935,7 @@ void Screen::setFrames()
|
||||
for (auto i = pluginFrames.begin(); i != pluginFrames.end(); ++i) {
|
||||
normalFrames[numframes++] = drawPluginFrame;
|
||||
}
|
||||
|
||||
|
||||
DEBUG_MSG("Added plugins. numframes: %d\n", numframes);
|
||||
|
||||
// If we have a critical fault, show it first
|
||||
@@ -1055,6 +1054,7 @@ void Screen::setFastFramerate()
|
||||
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
|
||||
ui.setTargetFPS(targetFramerate);
|
||||
setInterval(0); // redraw ASAP
|
||||
runASAP = true;
|
||||
}
|
||||
|
||||
void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -1286,14 +1286,41 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
uint32_t minutes = seconds / 60;
|
||||
uint32_t hours = minutes / 60;
|
||||
uint32_t days = hours / 24;
|
||||
currentMillis %= 1000;
|
||||
seconds %= 60;
|
||||
minutes %= 60;
|
||||
hours %= 24;
|
||||
// currentMillis %= 1000;
|
||||
// seconds %= 60;
|
||||
// minutes %= 60;
|
||||
// hours %= 24;
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
|
||||
String(days) + "d " + (hours < 10 ? "0" : "") + String(hours) + ":" + (minutes < 10 ? "0" : "") +
|
||||
String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds));
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
String uptime;
|
||||
if (days >= 2)
|
||||
uptime += String(days) + "d ";
|
||||
else if (hours >= 2)
|
||||
uptime += String(hours) + "h ";
|
||||
else if (minutes >= 1)
|
||||
uptime += String(minutes) + "m ";
|
||||
else
|
||||
uptime += String(seconds) + "s ";
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityFromNet);
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
// hms += tz.tz_dsttime * SEC_PER_HOUR;
|
||||
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
|
||||
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
|
||||
// Tear apart hms into h:m:s
|
||||
int hour = hms / SEC_PER_HOUR;
|
||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||
|
||||
char timebuf[9];
|
||||
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", hour, min, sec);
|
||||
uptime += timebuf;
|
||||
}
|
||||
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, uptime);
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Show CPU Frequency.
|
||||
|
||||
+19
-1
@@ -1,6 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#ifdef NO_SCREEN
|
||||
namespace graphics
|
||||
{
|
||||
// Noop class for boards without screen.
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
Screen(char){}
|
||||
void onPress() {}
|
||||
void setup() {}
|
||||
void setOn(bool) {}
|
||||
void print(const char*){}
|
||||
void adjustBrightness(){}
|
||||
void doDeepSleep() {}
|
||||
};
|
||||
}
|
||||
|
||||
#else
|
||||
#include <cstring>
|
||||
|
||||
#include <OLEDDisplayUi.h>
|
||||
@@ -281,3 +298,4 @@ class Screen : public concurrency::OSThread
|
||||
};
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
+79
-55
@@ -1,11 +1,11 @@
|
||||
|
||||
#include "Air530GPS.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "UBloxGPS.h"
|
||||
#include "airtime.h"
|
||||
#include "buzz.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "power.h"
|
||||
@@ -32,8 +32,9 @@
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#endif
|
||||
|
||||
#ifdef PORTDUINO
|
||||
#if defined(HAS_WIFI) || defined(PORTDUINO)
|
||||
#include "mesh/wifi/WiFiServerAPI.h"
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
#include "RF95Interface.h"
|
||||
@@ -67,7 +68,7 @@ Router *router = NULL; // Users of router don't care what sort of subclass imple
|
||||
// -----------------------------------------------------------------------------
|
||||
// Application
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#ifndef NO_WIRE
|
||||
void scanI2Cdevice(void)
|
||||
{
|
||||
byte err, addr;
|
||||
@@ -104,6 +105,9 @@ void scanI2Cdevice(void)
|
||||
else
|
||||
DEBUG_MSG("done\n");
|
||||
}
|
||||
#else
|
||||
void scanI2Cdevice(void) {}
|
||||
#endif
|
||||
|
||||
const char *getDeviceName()
|
||||
{
|
||||
@@ -173,6 +177,7 @@ class ButtonThread : public OSThread
|
||||
#ifdef BUTTON_PIN_ALT
|
||||
OneButton userButtonAlt;
|
||||
#endif
|
||||
static bool shutdown_on_long_stop;
|
||||
|
||||
public:
|
||||
static uint32_t longPressTime;
|
||||
@@ -239,13 +244,23 @@ class ButtonThread : public OSThread
|
||||
// DEBUG_MSG("Long press!\n");
|
||||
screen->adjustBrightness();
|
||||
|
||||
// If user button is held down for 10 seconds, shutdown the device.
|
||||
if (millis() - longPressTime > 10 * 1000) {
|
||||
// If user button is held down for 5 seconds, shutdown the device.
|
||||
if (millis() - longPressTime > 5 * 1000) {
|
||||
#ifdef TBEAM_V10
|
||||
if (axp192_found == true) {
|
||||
setLed(false);
|
||||
power->shutdown();
|
||||
}
|
||||
#elif NRF52_SERIES
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediatedly.
|
||||
if (!shutdown_on_long_stop) {
|
||||
DEBUG_MSG("Shutdown from long press");
|
||||
playBeep();
|
||||
ledOff(PIN_LED1);
|
||||
ledOff(PIN_LED2);
|
||||
shutdown_on_long_stop = true;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// DEBUG_MSG("Long press %u\n", (millis() - longPressTime));
|
||||
@@ -269,15 +284,28 @@ class ButtonThread : public OSThread
|
||||
{
|
||||
DEBUG_MSG("Long press stop!\n");
|
||||
longPressTime = 0;
|
||||
if (shutdown_on_long_stop) {
|
||||
playShutdownMelody();
|
||||
power->shutdown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool ButtonThread::shutdown_on_long_stop = false;
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread, *buttonThread;
|
||||
uint32_t ButtonThread::longPressTime = 0;
|
||||
|
||||
RadioInterface *rIf = NULL;
|
||||
|
||||
/**
|
||||
* Some platforms (nrf52) might provide an alterate version that supresses calling delay from sleep.
|
||||
*/
|
||||
__attribute__ ((weak, noinline)) bool loopCanSleep() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
concurrency::hasBeenSetup = true;
|
||||
@@ -292,13 +320,10 @@ void setup()
|
||||
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, buflen, mode);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SEGGER
|
||||
SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
|
||||
#endif
|
||||
|
||||
// Debug
|
||||
#ifdef DEBUG_PORT
|
||||
DEBUG_PORT.init(); // Set serial baud rate and init our mesh console
|
||||
if (!radioConfig.preferences.serial_disabled) {
|
||||
consoleInit(); // Set serial baud rate and init our mesh console
|
||||
}
|
||||
#endif
|
||||
|
||||
initDeepSleep();
|
||||
@@ -345,7 +370,7 @@ void setup()
|
||||
|
||||
#ifdef I2C_SDA
|
||||
Wire.begin(I2C_SDA, I2C_SCL);
|
||||
#else
|
||||
#elif !defined(NO_WIRE)
|
||||
Wire.begin();
|
||||
#endif
|
||||
|
||||
@@ -382,7 +407,7 @@ void setup()
|
||||
#ifdef NRF52_SERIES
|
||||
nrf52Setup();
|
||||
#endif
|
||||
|
||||
playStartMelody();
|
||||
// We do this as early as possible because this loads preferences from flash
|
||||
// but we need to do this after main cpu iniot (esp32setup), because we need the random seed set
|
||||
nodeDB.init();
|
||||
@@ -409,8 +434,12 @@ void setup()
|
||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||
|
||||
#ifdef GENIEBLOCKS
|
||||
// gps setup
|
||||
pinMode(GPS_RESET_N, OUTPUT);
|
||||
Im intentionally breaking your build so you see this note.Feel free to revert if not correct.I think you can
|
||||
remove this GPS_RESET_N code by instead defining PIN_GPS_RESET and
|
||||
use the shared code in GPS.cpp instead.- geeksville
|
||||
|
||||
// gps setup
|
||||
pinMode(GPS_RESET_N, OUTPUT);
|
||||
pinMode(GPS_EXTINT, OUTPUT);
|
||||
digitalWrite(GPS_RESET_N, HIGH);
|
||||
digitalWrite(GPS_EXTINT, LOW);
|
||||
@@ -420,34 +449,7 @@ void setup()
|
||||
pinMode(BATTERY_EN_PIN, OUTPUT);
|
||||
digitalWrite(BATTERY_EN_PIN, LOW);
|
||||
#endif
|
||||
|
||||
// If we don't have bidirectional comms, we can't even try talking to UBLOX
|
||||
UBloxGPS *ublox = NULL;
|
||||
#ifdef GPS_TX_PIN
|
||||
// Init GPS - first try ublox
|
||||
ublox = new UBloxGPS();
|
||||
gps = ublox;
|
||||
if (!gps->setup()) {
|
||||
DEBUG_MSG("ERROR: No UBLOX GPS found\n");
|
||||
|
||||
delete ublox;
|
||||
gps = ublox = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gps && GPS::_serial_gps) {
|
||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||
// assume NMEA at 9600 baud.
|
||||
// dumb NMEA access only work for serial GPSes)
|
||||
DEBUG_MSG("Hoping that NMEA might work\n");
|
||||
|
||||
#ifdef HAS_AIR530_GPS
|
||||
gps = new Air530GPS();
|
||||
#else
|
||||
gps = new NMEAGPS();
|
||||
#endif
|
||||
gps->setup();
|
||||
}
|
||||
gps = createGps();
|
||||
|
||||
if (gps)
|
||||
gpsStatus->observe(&gps->newStatus);
|
||||
@@ -464,7 +466,7 @@ void setup()
|
||||
// Do this after service.init (because that clears error_code)
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
if (!axp192_found)
|
||||
recordCriticalError(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
|
||||
#endif
|
||||
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
@@ -481,8 +483,8 @@ void setup()
|
||||
// We have now loaded our saved preferences from flash
|
||||
|
||||
// ONCE we will factory reset the GPS for bug #327
|
||||
if (ublox && !devicestate.did_gps_reset) {
|
||||
if (ublox->factoryReset()) { // If we don't succeed try again next time
|
||||
if (gps && !devicestate.did_gps_reset) {
|
||||
if (gps->factoryReset()) { // If we don't succeed try again next time
|
||||
devicestate.did_gps_reset = true;
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
@@ -504,7 +506,7 @@ void setup()
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
DEBUG_MSG("Radio init succeeded, using RF95 radio\n");
|
||||
DEBUG_MSG("RF95 Radio init succeeded, using RF95 radio\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -517,7 +519,7 @@ void setup()
|
||||
delete rIf;
|
||||
rIf = NULL;
|
||||
} else {
|
||||
DEBUG_MSG("Radio init succeeded, using SX1262 radio\n");
|
||||
DEBUG_MSG("SX1262 Radio init succeeded, using SX1262 radio\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -547,11 +549,15 @@ void setup()
|
||||
initApiServer();
|
||||
#endif
|
||||
|
||||
#if defined(PORTDUINO) || defined(HAS_WIFI)
|
||||
mqttInit();
|
||||
#endif
|
||||
|
||||
// Start airtime logger thread.
|
||||
airTime = new AirTime();
|
||||
|
||||
if (!rIf)
|
||||
recordCriticalError(CriticalErrorCode_NoRadio);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_NoRadio);
|
||||
else
|
||||
router->addInterface(rIf);
|
||||
|
||||
@@ -584,13 +590,29 @@ Periodic axpDebugOutput(axpDebugRead);
|
||||
axpDebugOutput.setup();
|
||||
#endif
|
||||
|
||||
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
|
||||
void rebootCheck()
|
||||
{
|
||||
if (rebootAtMsec && millis() > rebootAtMsec) {
|
||||
#ifndef NO_ESP32
|
||||
DEBUG_MSG("Rebooting for update\n");
|
||||
ESP.restart();
|
||||
#else
|
||||
DEBUG_MSG("FIXME implement reboot for this platform");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
|
||||
// This will supress the current delay and instead try to run ASAP.
|
||||
bool runASAP;
|
||||
|
||||
void loop()
|
||||
{
|
||||
// axpDebugOutput.loop();
|
||||
runASAP = false;
|
||||
|
||||
#ifdef DEBUG_PORT
|
||||
DEBUG_PORT.loop(); // Send/receive protobufs over the serial port
|
||||
#endif
|
||||
// axpDebugOutput.loop();
|
||||
|
||||
// heap_caps_check_integrity_all(true); // FIXME - disable this expensive check
|
||||
|
||||
@@ -600,6 +622,7 @@ void loop()
|
||||
#ifdef NRF52_SERIES
|
||||
nrf52Loop();
|
||||
#endif
|
||||
rebootCheck();
|
||||
|
||||
// For debugging
|
||||
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
|
||||
@@ -624,6 +647,7 @@ void loop()
|
||||
mainController.nextThread->tillRun(millis())); */
|
||||
|
||||
// We want to sleep as long as possible here - because it saves power
|
||||
mainDelay.delay(delayMsec);
|
||||
if (!runASAP && loopCanSleep())
|
||||
mainDelay.delay(delayMsec);
|
||||
// if (didWake) DEBUG_MSG("wake!\n");
|
||||
}
|
||||
|
||||
@@ -20,4 +20,10 @@ extern graphics::Screen *screen;
|
||||
// Return a human readable string of the form "Meshtastic_ab13"
|
||||
const char *getDeviceName();
|
||||
|
||||
extern uint32_t rebootAtMsec;
|
||||
|
||||
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
|
||||
// This will supress the current delay and instead try to run ASAP.
|
||||
extern bool runASAP;
|
||||
|
||||
void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop();
|
||||
|
||||
@@ -33,7 +33,7 @@ int16_t Channels::generateHash(ChannelIndex channelNum)
|
||||
return -1; // invalid
|
||||
else {
|
||||
const char *name = getName(channelNum);
|
||||
uint8_t h = xorHash((const uint8_t *) name, strlen(name));
|
||||
uint8_t h = xorHash((const uint8_t *)name, strlen(name));
|
||||
|
||||
h ^= xorHash(k.bytes, k.length);
|
||||
|
||||
@@ -184,7 +184,7 @@ void Channels::onConfigChanged()
|
||||
|
||||
Channel &Channels::getByIndex(ChannelIndex chIndex)
|
||||
{
|
||||
assert(chIndex < channelFile.channels_count);
|
||||
assert(chIndex < channelFile.channels_count); // This should be equal to MAX_NUM_CHANNELS
|
||||
Channel *ch = channelFile.channels + chIndex;
|
||||
return *ch;
|
||||
}
|
||||
@@ -278,11 +278,11 @@ const char *Channels::getPrimaryName()
|
||||
*/
|
||||
bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
|
||||
{
|
||||
if(chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
|
||||
// DEBUG_MSG("Skipping channel %d (hash %x) due to invalid hash/index, want=%x\n", chIndex, getHash(chIndex), channelHash);
|
||||
if (chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
|
||||
// DEBUG_MSG("Skipping channel %d (hash %x) due to invalid hash/index, want=%x\n", chIndex, getHash(chIndex),
|
||||
// channelHash);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
DEBUG_MSG("Using channel %d (hash 0x%x)\n", chIndex, channelHash);
|
||||
setCrypto(chIndex);
|
||||
return true;
|
||||
|
||||
+7
-1
@@ -29,7 +29,6 @@ class Channels
|
||||
int16_t hashes[MAX_NUM_CHANNELS];
|
||||
|
||||
public:
|
||||
|
||||
/// Well known channel names
|
||||
static const char *adminChannel, *gpioChannel, *serialChannel;
|
||||
|
||||
@@ -43,8 +42,15 @@ class Channels
|
||||
*/
|
||||
void setChannel(const Channel &c);
|
||||
|
||||
/** Return a human friendly name for this channel (and expand any short strings as needed)
|
||||
*/
|
||||
const char *getName(size_t chIndex);
|
||||
|
||||
/**
|
||||
* Return a globally unique channel ID usable with MQTT.
|
||||
*/
|
||||
const char *getGlobalId(size_t chIndex) { return getName(chIndex); } // FIXME, not correct
|
||||
|
||||
/** The index of the primary channel */
|
||||
ChannelIndex getPrimaryIndex() const { return primaryIndex; }
|
||||
|
||||
|
||||
+105
-29
@@ -31,12 +31,47 @@ MeshPlugin::~MeshPlugin()
|
||||
assert(0); // FIXME - remove from list of plugins once someone needs this feature
|
||||
}
|
||||
|
||||
MeshPacket *MeshPlugin::allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
|
||||
{
|
||||
Routing c = Routing_init_default;
|
||||
|
||||
c.error_reason = err;
|
||||
c.which_variant = Routing_error_reason_tag;
|
||||
|
||||
// Now that we have moded sendAckNak up one level into the class heirarchy we can no longer assume we are a RoutingPlugin
|
||||
// So we manually call pb_encode_to_bytes and specify routing port number
|
||||
// auto p = allocDataProtobuf(c);
|
||||
MeshPacket *p = router->allocForSending();
|
||||
p->decoded.portnum = PortNum_ROUTING_APP;
|
||||
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Routing_fields, &c);
|
||||
|
||||
p->priority = MeshPacket_Priority_ACK;
|
||||
|
||||
p->hop_limit = 0; // Assume just immediate neighbors for now
|
||||
p->to = to;
|
||||
p->decoded.request_id = idFrom;
|
||||
p->channel = chIndex;
|
||||
DEBUG_MSG("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *p)
|
||||
{
|
||||
auto r = allocAckNak(err, getFrom(p), p->id, p->channel);
|
||||
|
||||
setReplyTo(r, *p);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
{
|
||||
// DEBUG_MSG("In call plugins\n");
|
||||
bool pluginFound = false;
|
||||
|
||||
assert(mp.which_payloadVariant == MeshPacket_decoded_tag); // I think we are guarnteed the packet is decoded by this point?
|
||||
// We now allow **encrypted** packets to pass through the plugins
|
||||
bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag;
|
||||
|
||||
currentReply = NULL; // No reply yet
|
||||
|
||||
@@ -48,37 +83,66 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
|
||||
pi.currentRequest = ∓
|
||||
|
||||
/// received channel
|
||||
auto ch = channels.getByIndex(mp.channel);
|
||||
assert(ch.has_settings);
|
||||
|
||||
/// Is the channel this packet arrived on acceptable? (security check)
|
||||
bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (strcmp(ch.settings.name, pi.boundChannel) == 0);
|
||||
|
||||
/// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious)
|
||||
bool wantsPacket = rxChannelOk && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
|
||||
// DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
|
||||
bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
|
||||
|
||||
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
|
||||
|
||||
if (wantsPacket) {
|
||||
DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
|
||||
|
||||
pluginFound = true;
|
||||
|
||||
bool handled = pi.handleReceived(mp);
|
||||
/// received channel (or NULL if not decoded)
|
||||
Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL;
|
||||
|
||||
// Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious sniffing)
|
||||
// also: we only let the one plugin send a reply, once that happens, remaining plugins are not considered
|
||||
/// Is the channel this packet arrived on acceptable? (security check)
|
||||
/// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins
|
||||
|
||||
// NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary because
|
||||
// currently when the phone sends things, it sends things using the local node ID as the from address. A better
|
||||
// solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like any other
|
||||
// node.
|
||||
if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) {
|
||||
pi.sendResponse(mp);
|
||||
DEBUG_MSG("Plugin %s sent a response\n", pi.name);
|
||||
/// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and it needs to
|
||||
/// to be able to fetch the initial admin packets without yet knowing any channels.
|
||||
|
||||
bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && (strcmp(ch->settings.name, pi.boundChannel) == 0));
|
||||
|
||||
if (!rxChannelOk) {
|
||||
// no one should have already replied!
|
||||
assert(!currentReply);
|
||||
|
||||
if (mp.decoded.want_response) {
|
||||
printPacket("packet on wrong channel, returning error", &mp);
|
||||
currentReply = pi.allocErrorResponse(Routing_Error_NOT_AUTHORIZED, &mp);
|
||||
} else
|
||||
printPacket("packet on wrong channel, but can't respond", &mp);
|
||||
} else {
|
||||
DEBUG_MSG("Plugin %s considered\n", pi.name);
|
||||
}
|
||||
if (handled) {
|
||||
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
|
||||
break;
|
||||
|
||||
bool handled = pi.handleReceived(mp);
|
||||
|
||||
// Possibly send replies (but only if the message was directed to us specifically, i.e. not for promiscious
|
||||
// sniffing) also: we only let the one plugin send a reply, once that happens, remaining plugins are not
|
||||
// considered
|
||||
|
||||
// NOTE: we send a reply *even if the (non broadcast) request was from us* which is unfortunate but necessary
|
||||
// because currently when the phone sends things, it sends things using the local node ID as the from address. A
|
||||
// better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like
|
||||
// any other node.
|
||||
if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) {
|
||||
pi.sendResponse(mp);
|
||||
DEBUG_MSG("Plugin %s sent a response\n", pi.name);
|
||||
} else {
|
||||
DEBUG_MSG("Plugin %s considered\n", pi.name);
|
||||
}
|
||||
|
||||
// If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks
|
||||
if (pi.myReply) {
|
||||
DEBUG_MSG("Discarding an unneeded response\n");
|
||||
packetPool.release(pi.myReply);
|
||||
pi.myReply = NULL;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +151,18 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
|
||||
if (mp.decoded.want_response && toUs) {
|
||||
if (currentReply) {
|
||||
DEBUG_MSG("Sending response\n");
|
||||
printPacket("Sending response", currentReply);
|
||||
service.sendToMesh(currentReply);
|
||||
currentReply = NULL;
|
||||
}
|
||||
else {
|
||||
} else if(mp.from != ourNodeNum) {
|
||||
// Note: if the message started with the local node we don't want to send a no response reply
|
||||
|
||||
// No one wanted to reply to this requst, tell the requster that happened
|
||||
DEBUG_MSG("No one responded, send a nak\n");
|
||||
|
||||
// SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded)
|
||||
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
|
||||
// bad.
|
||||
routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel);
|
||||
}
|
||||
}
|
||||
@@ -102,6 +171,13 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
|
||||
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
|
||||
}
|
||||
|
||||
MeshPacket *MeshPlugin::allocReply()
|
||||
{
|
||||
auto r = myReply;
|
||||
myReply = NULL; // Only use each reply once
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method
|
||||
* is optional
|
||||
@@ -124,7 +200,7 @@ void MeshPlugin::sendResponse(const MeshPacket &req)
|
||||
void setReplyTo(MeshPacket *p, const MeshPacket &to)
|
||||
{
|
||||
assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now
|
||||
p->to = getFrom(&to);
|
||||
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
|
||||
p->channel = to.channel; // Use the same channel that the request came in on
|
||||
|
||||
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
|
||||
|
||||
+30
-6
@@ -1,9 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "mesh/Channels.h"
|
||||
#include "mesh/MeshTypes.h"
|
||||
#include <vector>
|
||||
|
||||
#ifndef NO_SCREEN
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
/** A baseclass for any mesh "plugin".
|
||||
*
|
||||
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
|
||||
@@ -31,18 +36,22 @@ class MeshPlugin
|
||||
static void callPlugins(const MeshPacket &mp);
|
||||
|
||||
static std::vector<MeshPlugin *> GetMeshPluginsWithUIFrames();
|
||||
|
||||
#ifndef NO_SCREEN
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
|
||||
|
||||
#endif
|
||||
protected:
|
||||
const char *name;
|
||||
|
||||
/* Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific
|
||||
/** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific
|
||||
recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those
|
||||
plugins can set this to true and their handleReceived() will be called for every packet.
|
||||
*/
|
||||
bool isPromiscuous = false;
|
||||
|
||||
/** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this
|
||||
* flag */
|
||||
bool encryptedOk = false;
|
||||
|
||||
/** If a bound channel name is set, we will only accept received packets that come in on that channel.
|
||||
* A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface
|
||||
* are allowed on any channel (this lets the local user do anything).
|
||||
@@ -60,6 +69,11 @@ class MeshPlugin
|
||||
*/
|
||||
static const MeshPacket *currentRequest;
|
||||
|
||||
/**
|
||||
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
|
||||
*/
|
||||
MeshPacket *myReply = NULL;
|
||||
|
||||
/**
|
||||
* Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have
|
||||
* been initialized
|
||||
@@ -78,20 +92,30 @@ class MeshPlugin
|
||||
virtual bool handleReceived(const MeshPacket &mp) { return false; }
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. */
|
||||
virtual MeshPacket *allocReply() { return NULL; }
|
||||
* so that subclasses can (optionally) send a response back to the original sender.
|
||||
*
|
||||
* Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set
|
||||
* the protected reply field in this instance.
|
||||
* */
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
/***
|
||||
* @return true if you want to be alloced a UI screen frame
|
||||
*/
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
|
||||
MeshPacket *allocAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||
|
||||
/// Send an error response for the specified packet.
|
||||
MeshPacket *allocErrorResponse(Routing_Error err, const MeshPacket *p);
|
||||
|
||||
private:
|
||||
/**
|
||||
* If any of the current chain of plugins has already sent a reply, it will be here. This is useful to allow
|
||||
* the RoutingPlugin to avoid sending redundant acks
|
||||
*/
|
||||
static MeshPacket *currentReply;
|
||||
|
||||
friend class ReliableRouter;
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
|
||||
@@ -114,7 +114,7 @@ bool MeshService::reloadConfig()
|
||||
void MeshService::reloadOwner()
|
||||
{
|
||||
assert(nodeInfoPlugin);
|
||||
if(nodeInfoPlugin)
|
||||
if (nodeInfoPlugin)
|
||||
nodeInfoPlugin->sendOurNodeInfo();
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
@@ -172,13 +172,12 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||
assert(node);
|
||||
|
||||
if (node->has_position) {
|
||||
if(positionPlugin) {
|
||||
if (positionPlugin) {
|
||||
DEBUG_MSG("Sending position ping to 0x%x, wantReplies=%d\n", dest, wantReplies);
|
||||
positionPlugin->sendOurPosition(dest, wantReplies);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(nodeInfoPlugin) {
|
||||
} else {
|
||||
if (nodeInfoPlugin) {
|
||||
DEBUG_MSG("Sending nodeinfo ping to 0x%x, wantReplies=%d\n", dest, wantReplies);
|
||||
nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies);
|
||||
}
|
||||
@@ -198,10 +197,13 @@ NodeInfo *MeshService::refreshMyNodeInfo()
|
||||
|
||||
Position &position = node->position;
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
position.time =
|
||||
// Update our local node info with our time (even if we don't decide to update anyone else)
|
||||
node->last_heard =
|
||||
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
|
||||
|
||||
// For the time in the position field, only set that if we have a real GPS clock
|
||||
position.time = getValidTime(RTCQualityGPS);
|
||||
|
||||
position.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(position.battery_level);
|
||||
|
||||
|
||||
@@ -84,13 +84,12 @@ class MeshService
|
||||
NodeInfo *refreshMyNodeInfo();
|
||||
|
||||
private:
|
||||
|
||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||
/// returns 0 to allow futher processing
|
||||
int onGPSChanged(const meshtastic::GPSStatus *arg);
|
||||
|
||||
/// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it needs
|
||||
/// to keep the packet around it makes a copy
|
||||
/// Handle a packet that just arrived from the radio. This method does _ReliableRouternot_ free the provided packet. If it
|
||||
/// needs to keep the packet around it makes a copy
|
||||
int handleFromRadio(const MeshPacket *p);
|
||||
friend class RoutingPlugin;
|
||||
};
|
||||
|
||||
@@ -12,10 +12,8 @@ typedef uint32_t PacketId; // A packet sequence number
|
||||
#define NODENUM_BROADCAST UINT32_MAX
|
||||
#define ERRNO_OK 0
|
||||
#define ERRNO_NO_INTERFACES 33
|
||||
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
|
||||
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
|
||||
#define ERRNO_DISABLED 34 // the itnerface is disabled
|
||||
#define ERRNO_TOO_LARGE 35
|
||||
#define ERRNO_NO_CHANNEL 36
|
||||
|
||||
/**
|
||||
* the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket.
|
||||
@@ -35,7 +33,7 @@ typedef int ErrorCode;
|
||||
extern Allocator<MeshPacket> &packetPool;
|
||||
|
||||
/**
|
||||
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on the local node.
|
||||
* If from is zero this function returns our node number instead
|
||||
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on
|
||||
* the local node. If from is zero this function returns our node number instead
|
||||
*/
|
||||
NodeNum getFrom(const MeshPacket *p);
|
||||
+41
-19
@@ -23,6 +23,8 @@
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "plugins/esp32/StoreForwardPlugin.h"
|
||||
#include <Preferences.h>
|
||||
#endif
|
||||
|
||||
NodeDB nodeDB;
|
||||
@@ -82,16 +84,17 @@ bool NodeDB::resetRadioConfig()
|
||||
|
||||
radioGeneration++;
|
||||
|
||||
radioConfig.has_preferences = true;
|
||||
if (radioConfig.preferences.factory_reset) {
|
||||
DEBUG_MSG("Performing factory reset!\n");
|
||||
installDefaultDeviceState();
|
||||
didFactoryReset = true;
|
||||
} else if (channelFile.channels_count == 0) {
|
||||
}
|
||||
|
||||
if (channelFile.channels_count != MAX_NUM_CHANNELS) {
|
||||
DEBUG_MSG("Setting default channel and radio preferences!\n");
|
||||
|
||||
channels.initDefaults();
|
||||
|
||||
radioConfig.has_preferences = true;
|
||||
}
|
||||
|
||||
channels.onConfigChanged();
|
||||
@@ -206,12 +209,13 @@ void NodeDB::init()
|
||||
// removed from 1.2 (though we do use old values if found)
|
||||
// We set these _after_ loading from disk - because they come from the build and are more trusted than
|
||||
// what is stored in flash
|
||||
//if (xstr(HW_VERSION)[0])
|
||||
// if (xstr(HW_VERSION)[0])
|
||||
// strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region));
|
||||
// else DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build flag
|
||||
// else DEBUG_MSG("This build does not specify a HW_VERSION\n"); // Eventually new builds will no longer include this build
|
||||
// flag
|
||||
|
||||
// DEBUG_MSG("legacy region %d\n", devicestate.legacyRadio.preferences.region);
|
||||
if(radioConfig.preferences.region == RegionCode_Unset)
|
||||
if (radioConfig.preferences.region == RegionCode_Unset)
|
||||
radioConfig.preferences.region = devicestate.legacyRadio.preferences.region;
|
||||
|
||||
// Check for the old style of region code strings, if found, convert to the new enum.
|
||||
@@ -226,11 +230,20 @@ void NodeDB::init()
|
||||
}
|
||||
|
||||
strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version));
|
||||
|
||||
|
||||
// hw_model is no longer stored in myNodeInfo (as of 1.2.11) - we now store it as an enum in nodeinfo
|
||||
myNodeInfo.hw_model_deprecated[0] = '\0';
|
||||
// strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model));
|
||||
|
||||
#ifndef NO_ESP32
|
||||
Preferences preferences;
|
||||
preferences.begin("meshtastic", false);
|
||||
myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
|
||||
preferences.end();
|
||||
DEBUG_MSG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
|
||||
|
||||
#endif
|
||||
|
||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
|
||||
DEBUG_MSG("region=%d, NODENUM=0x%x, dbsize=%d\n", radioConfig.preferences.region, myNodeInfo.my_node_num, *numNodes);
|
||||
@@ -373,7 +386,7 @@ void NodeDB::saveChannelsToDisk()
|
||||
FS.mkdir("/prefs");
|
||||
#endif
|
||||
saveProto(channelfile, ChannelFile_size, sizeof(ChannelFile), ChannelFile_fields, &channelFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeDB::saveToDisk()
|
||||
@@ -406,8 +419,7 @@ uint32_t sinceLastSeen(const NodeInfo *n)
|
||||
{
|
||||
uint32_t now = getTime();
|
||||
|
||||
uint32_t last_seen = n->position.time;
|
||||
int delta = (int)(now - last_seen);
|
||||
int delta = (int)(now - n->last_heard);
|
||||
if (delta < 0) // our clock must be slightly off still - not set from GPS yet
|
||||
delta = 0;
|
||||
|
||||
@@ -441,7 +453,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
|
||||
// Be careful to only update fields that have been set by the sender
|
||||
// A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
|
||||
// recorded based on the packet rxTime
|
||||
if (!info->position.time && p.time)
|
||||
if (p.time)
|
||||
info->position.time = p.time;
|
||||
if (p.battery_level)
|
||||
info->position.battery_level = p.battery_level;
|
||||
@@ -449,6 +461,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
|
||||
info->position.latitude_i = p.latitude_i;
|
||||
info->position.longitude_i = p.longitude_i;
|
||||
}
|
||||
if (p.altitude)
|
||||
info->position.altitude = p.altitude;
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
@@ -484,17 +498,16 @@ void NodeDB::updateUser(uint32_t nodeId, const User &p)
|
||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||
void NodeDB::updateFrom(const MeshPacket &mp)
|
||||
{
|
||||
if (mp.which_payloadVariant == MeshPacket_decoded_tag) {
|
||||
if (mp.which_payloadVariant == MeshPacket_decoded_tag && mp.from) {
|
||||
DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
|
||||
|
||||
NodeInfo *info = getOrCreateNode(getFrom(&mp));
|
||||
|
||||
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
||||
info->has_position = true; // at least the time is valid
|
||||
info->position.time = mp.rx_time;
|
||||
}
|
||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
||||
info->last_heard = mp.rx_time;
|
||||
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,15 +541,24 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
|
||||
}
|
||||
|
||||
/// Record an error that should be reported via analytics
|
||||
void recordCriticalError(CriticalErrorCode code, uint32_t address)
|
||||
void recordCriticalError(CriticalErrorCode code, uint32_t address, const char *filename)
|
||||
{
|
||||
// Print error to screen and serial port
|
||||
String lcd = String("Critical error ") + code + "!\n";
|
||||
screen->print(lcd.c_str());
|
||||
DEBUG_MSG("NOTE! Recording critical error %d, address=%lx\n", code, address);
|
||||
if(filename)
|
||||
DEBUG_MSG("NOTE! Recording critical error %d at %s:%lx\n", code, filename, address);
|
||||
else
|
||||
DEBUG_MSG("NOTE! Recording critical error %d, address=%lx\n", code, address);
|
||||
|
||||
// Record error to DB
|
||||
myNodeInfo.error_code = code;
|
||||
myNodeInfo.error_address = address;
|
||||
myNodeInfo.error_count++;
|
||||
|
||||
// Currently portuino is mostly used for simulation. Make sue the user notices something really bad happend
|
||||
#ifdef PORTDUINO
|
||||
DEBUG_MSG("A critical failure occurred, portduino is exiting...");
|
||||
exit(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate)
|
||||
recentPackets.erase(recentPackets.begin() + i); // delete old record
|
||||
} else {
|
||||
if (r.id == p->id && r.sender == getFrom(p)) {
|
||||
DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id);
|
||||
DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x\n", p->from, p->to, p->id);
|
||||
|
||||
// Update the time on this record to now
|
||||
if (withUpdate)
|
||||
|
||||
+72
-45
@@ -1,10 +1,10 @@
|
||||
#include "PhoneAPI.h"
|
||||
#include "Channels.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Channels.h"
|
||||
#include <assert.h>
|
||||
|
||||
#if FromRadio_size > MAX_TO_FROM_RADIO_SIZE
|
||||
@@ -15,33 +15,52 @@
|
||||
#error ToRadio is too big
|
||||
#endif
|
||||
|
||||
PhoneAPI::PhoneAPI() {}
|
||||
|
||||
void PhoneAPI::init()
|
||||
PhoneAPI::PhoneAPI()
|
||||
{
|
||||
observe(&service.fromNumChanged);
|
||||
lastContactMsec = millis();
|
||||
}
|
||||
|
||||
PhoneAPI::~PhoneAPI() {
|
||||
PhoneAPI::~PhoneAPI()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void PhoneAPI::close() {
|
||||
unobserve();
|
||||
state = STATE_SEND_NOTHING;
|
||||
bool oldConnected = isConnected;
|
||||
isConnected = false;
|
||||
if(oldConnected != isConnected)
|
||||
onConnectionChanged(isConnected);
|
||||
void PhoneAPI::handleStartConfig()
|
||||
{
|
||||
// Must be before setting state (because state is how we know !connected)
|
||||
if (!isConnected()) {
|
||||
onConnectionChanged(true);
|
||||
observe(&service.fromNumChanged);
|
||||
}
|
||||
|
||||
// even if we were already connected - restart our state machine
|
||||
state = STATE_SEND_MY_INFO;
|
||||
|
||||
DEBUG_MSG("Starting API client config\n");
|
||||
nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos
|
||||
nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because
|
||||
// this will break once we have multiple instances of PhoneAPI running independently
|
||||
}
|
||||
|
||||
void PhoneAPI::close()
|
||||
{
|
||||
if (state != STATE_SEND_NOTHING) {
|
||||
state = STATE_SEND_NOTHING;
|
||||
|
||||
unobserve();
|
||||
releasePhonePacket(); // Don't leak phone packets on shutdown
|
||||
|
||||
onConnectionChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PhoneAPI::checkConnectionTimeout()
|
||||
{
|
||||
if (isConnected) {
|
||||
bool newConnected = (millis() - lastContactMsec < getPref_phone_timeout_secs() * 1000L);
|
||||
if (!newConnected) {
|
||||
isConnected = false;
|
||||
onConnectionChanged(isConnected);
|
||||
if (isConnected()) {
|
||||
bool newContact = checkIsConnected();
|
||||
if (!newContact) {
|
||||
DEBUG_MSG("Lost phone connection\n");
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,43 +68,40 @@ void PhoneAPI::checkConnectionTimeout()
|
||||
/**
|
||||
* Handle a ToRadio protobuf
|
||||
*/
|
||||
void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
|
||||
{
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
|
||||
lastContactMsec = millis();
|
||||
if (!isConnected) {
|
||||
isConnected = true;
|
||||
onConnectionChanged(isConnected);
|
||||
}
|
||||
|
||||
// return (lastContactMsec != 0) &&
|
||||
|
||||
memset(&toRadioScratch, 0, sizeof(toRadioScratch));
|
||||
if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) {
|
||||
switch (toRadioScratch.which_payloadVariant) {
|
||||
case ToRadio_packet_tag: {
|
||||
MeshPacket &p = toRadioScratch.packet;
|
||||
printPacket("PACKET FROM PHONE", &p);
|
||||
service.handleToRadio(p);
|
||||
break;
|
||||
}
|
||||
case ToRadio_packet_tag:
|
||||
return handleToRadioPacket(toRadioScratch.packet);
|
||||
|
||||
case ToRadio_want_config_id_tag:
|
||||
config_nonce = toRadioScratch.want_config_id;
|
||||
DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce);
|
||||
state = STATE_SEND_MY_INFO;
|
||||
|
||||
DEBUG_MSG("Reset nodeinfo read pointer\n");
|
||||
nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos
|
||||
nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because
|
||||
// this will break once we have multiple instances of PhoneAPI running independently
|
||||
handleStartConfig();
|
||||
break;
|
||||
|
||||
case ToRadio_disconnect_tag:
|
||||
close();
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_MSG("Error: unexpected ToRadio variant\n");
|
||||
// Ignore nop messages
|
||||
// DEBUG_MSG("Error: unexpected ToRadio variant\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("Error: ignoring malformed toradio\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,14 +136,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
case STATE_SEND_MY_INFO:
|
||||
// If the user has specified they don't want our node to share its location, make sure to tell the phone
|
||||
// app not to send locations on our behalf.
|
||||
myNodeInfo.has_gps = (radioConfig.preferences.location_share == LocationSharing_LocDisabled)
|
||||
? true
|
||||
: (gps && gps->isConnected()); // Update with latest GPS connect info
|
||||
myNodeInfo.has_gps = gps && gps->isConnected(); // Update with latest GPS connect info
|
||||
fromRadioScratch.which_payloadVariant = FromRadio_my_info_tag;
|
||||
fromRadioScratch.my_info = myNodeInfo;
|
||||
state = STATE_SEND_NODEINFO;
|
||||
|
||||
service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon.
|
||||
service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon.
|
||||
break;
|
||||
|
||||
case STATE_SEND_NODEINFO: {
|
||||
@@ -135,7 +149,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time
|
||||
|
||||
if (info) {
|
||||
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id,
|
||||
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->last_heard, info->user.id,
|
||||
info->user.long_name);
|
||||
fromRadioScratch.which_payloadVariant = FromRadio_node_info_tag;
|
||||
fromRadioScratch.node_info = *info;
|
||||
@@ -159,16 +173,13 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
case STATE_SEND_PACKETS:
|
||||
// Do we have a message from the mesh?
|
||||
if (packetForPhone) {
|
||||
|
||||
printPacket("phone downloaded packet", packetForPhone);
|
||||
|
||||
// Encapsulate as a FromRadio packet
|
||||
fromRadioScratch.which_payloadVariant = FromRadio_packet_tag;
|
||||
fromRadioScratch.packet = *packetForPhone;
|
||||
|
||||
service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore
|
||||
packetForPhone = NULL;
|
||||
}
|
||||
releasePhonePacket();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -187,6 +198,16 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PhoneAPI::handleDisconnect() {}
|
||||
|
||||
void PhoneAPI::releasePhonePacket()
|
||||
{
|
||||
if (packetForPhone) {
|
||||
service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore
|
||||
packetForPhone = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we have data available to send to the phone
|
||||
*/
|
||||
@@ -226,7 +247,13 @@ bool PhoneAPI::available()
|
||||
/**
|
||||
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
|
||||
*/
|
||||
void PhoneAPI::handleToRadioPacket(MeshPacket *p) {}
|
||||
bool PhoneAPI::handleToRadioPacket(MeshPacket &p)
|
||||
{
|
||||
printPacket("PACKET FROM PHONE", &p);
|
||||
service.handleToRadio(p);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// If the mesh service tells us fromNum has changed, tell the phone
|
||||
int PhoneAPI::onNotify(uint32_t newValue)
|
||||
|
||||
+26
-14
@@ -22,6 +22,7 @@ class PhoneAPI
|
||||
enum State {
|
||||
STATE_UNUSED, // (no longer used) old default state - until Android apps are all updated, uses the old BLE API
|
||||
STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config
|
||||
// (disconnected)
|
||||
STATE_SEND_MY_INFO, // send our my info record
|
||||
// STATE_SEND_RADIO, // in 1.2 we now send this as a regular mesh packet
|
||||
// STATE_SEND_OWNER, no need to send Owner specially, it is just part of the nodedb
|
||||
@@ -49,26 +50,21 @@ class PhoneAPI
|
||||
/// Use to ensure that clients don't get confused about old messages from the radio
|
||||
uint32_t config_nonce = 0;
|
||||
|
||||
/** the last msec we heard from the client on the other side of this link */
|
||||
uint32_t lastContactMsec = 0;
|
||||
|
||||
public:
|
||||
PhoneAPI();
|
||||
|
||||
/// Destructor - calls close()
|
||||
virtual ~PhoneAPI();
|
||||
|
||||
/// Do late init that can't happen at constructor time
|
||||
virtual void init();
|
||||
|
||||
// Call this when the client drops the connection, resets the state to STATE_SEND_NOTHING
|
||||
// Unregisters our observer. A closed connection **can** be reopened by calling init again.
|
||||
virtual void close();
|
||||
|
||||
/**
|
||||
* Handle a ToRadio protobuf
|
||||
* @return true true if a packet was queued for sending (so that caller can yield)
|
||||
*/
|
||||
virtual void handleToRadio(const uint8_t *buf, size_t len);
|
||||
virtual bool handleToRadio(const uint8_t *buf, size_t len);
|
||||
|
||||
/**
|
||||
* Get the next packet we want to send to the phone
|
||||
@@ -83,29 +79,45 @@ class PhoneAPI
|
||||
*/
|
||||
bool available();
|
||||
|
||||
protected:
|
||||
/// Are we currently connected to a client?
|
||||
bool isConnected = false;
|
||||
bool isConnected() { return state != STATE_SEND_NOTHING; }
|
||||
|
||||
protected:
|
||||
/// Our fromradio packet while it is being assembled
|
||||
FromRadio fromRadioScratch;
|
||||
|
||||
/** the last msec we heard from the client on the other side of this link */
|
||||
uint32_t lastContactMsec = 0;
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
virtual void onConnectionChanged(bool connected) {}
|
||||
|
||||
/// If we haven't heard from the other side in a while then say not connected
|
||||
/// If we haven't heard from the other side in a while then say not connected
|
||||
void checkConnectionTimeout();
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() = 0;
|
||||
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum) {}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
|
||||
* Subclasses can use this to find out when a client drops the link
|
||||
*/
|
||||
void handleToRadioPacket(MeshPacket *p);
|
||||
virtual void handleDisconnect();
|
||||
|
||||
private:
|
||||
void releasePhonePacket();
|
||||
|
||||
/// begin a new connection
|
||||
void handleStartConfig();
|
||||
|
||||
/**
|
||||
* Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it
|
||||
* @return true true if a packet was queued for sending
|
||||
*/
|
||||
bool handleToRadioPacket(MeshPacket &p);
|
||||
|
||||
/// If the mesh service tells us fromNum has changed, tell the phone
|
||||
virtual int onNotify(uint32_t newValue);
|
||||
|
||||
@@ -22,10 +22,9 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Handle a received message, the data field in the message is already decoded and is provided
|
||||
*
|
||||
*
|
||||
* In general decoded will always be !NULL. But in some special applications (where you have handling packets
|
||||
* for multiple port numbers, decoding will ONLY be attempted for packets where the portnum matches our expected ourPortNum.
|
||||
*/
|
||||
@@ -58,11 +57,12 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
|
||||
// it would be better to update even if the message was destined to others.
|
||||
|
||||
auto &p = mp.decoded;
|
||||
DEBUG_MSG("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum, p.payload.size);
|
||||
DEBUG_MSG("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum,
|
||||
p.payload.size);
|
||||
|
||||
T scratch;
|
||||
T *decoded = NULL;
|
||||
if(mp.decoded.portnum == ourPortNum) {
|
||||
T *decoded = NULL;
|
||||
if (mp.which_payloadVariant == MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) {
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch))
|
||||
decoded = &scratch;
|
||||
|
||||
+11
-10
@@ -36,8 +36,6 @@ bool RF95Interface::init()
|
||||
{
|
||||
RadioLibInterface::init();
|
||||
|
||||
applyModemConfig();
|
||||
|
||||
if (power == 0)
|
||||
power = POWER_DEFAULT;
|
||||
|
||||
@@ -86,24 +84,25 @@ void INTERRUPT_ATTR RF95Interface::disableInterrupt()
|
||||
lora->clearDio0Action();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool RF95Interface::reconfigure()
|
||||
{
|
||||
applyModemConfig();
|
||||
RadioLibInterface::reconfigure();
|
||||
|
||||
// set mode to standby
|
||||
setStandby();
|
||||
|
||||
// configure publicly accessible settings
|
||||
int err = lora->setSpreadingFactor(sf);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
err = lora->setBandwidth(bw);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
err = lora->setCodingRate(cr);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
err = lora->setSyncWord(syncWord);
|
||||
assert(err == ERR_NONE);
|
||||
@@ -115,12 +114,14 @@ bool RF95Interface::reconfigure()
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora->setFrequency(freq);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
if (power > MAX_POWER) // This chip has lower power limits than some
|
||||
power = MAX_POWER;
|
||||
err = lora->setOutputPower(power);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ class RF95Interface : public RadioLibInterface
|
||||
public:
|
||||
RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi);
|
||||
|
||||
/// Some boards (Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
||||
bool isIRQPending() { return lora->getPendingIRQ(); }
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
|
||||
+17
-11
@@ -1,12 +1,12 @@
|
||||
|
||||
#include "configuration.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Channels.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "assert.h"
|
||||
#include "Router.h"
|
||||
#include "assert.h"
|
||||
#include "configuration.h"
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <pb_decode.h>
|
||||
@@ -31,8 +31,8 @@ const RegionInfo regions[] = {
|
||||
|
||||
/* Notes about the RU bandplan (from @denis-d in https://meshtastic.discourse.group/t/russian-band-plan-proposal/2786/2):
|
||||
|
||||
According to Annex 12 to GKRCh (National Radio Frequency Commission) decision № 18-46-03-1 (September 11th 2018) https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf 1
|
||||
We have 3 options for 868 MHz:
|
||||
According to Annex 12 to GKRCh (National Radio Frequency Commission) decision № 18-46-03-1 (September 11th 2018)
|
||||
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf 1 We have 3 options for 868 MHz:
|
||||
|
||||
864,0 - 865,0 MHz ERP 25mW, Duty Cycle 0.1% (3.6 sec in hour) or LBT (Listen Before Talk), prohibited in airports.
|
||||
866,0 - 868,0 MHz ERP 25mW, Duty Cycle 1% or LBT, PSD (Power Spectrum Density) 1000mW/MHz, prohibited in airports
|
||||
@@ -112,6 +112,8 @@ uint32_t RadioInterface::getPacketTime(MeshPacket *p)
|
||||
/** The delay to use for retransmitting dropped packets */
|
||||
uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
|
||||
{
|
||||
assert(shortPacketMsec); // Better be non zero
|
||||
|
||||
// was 20 and 22 secs respectively, but now with shortPacketMsec as 2269, this should give the same range
|
||||
return random(9 * shortPacketMsec, 10 * shortPacketMsec);
|
||||
}
|
||||
@@ -153,7 +155,7 @@ void printPacket(const char *prefix, const MeshPacket *p)
|
||||
if (s.dest != 0)
|
||||
DEBUG_MSG(" dest=%08x", s.dest);
|
||||
|
||||
if(s.request_id)
|
||||
if (s.request_id)
|
||||
DEBUG_MSG(" requestId=%0x", s.request_id);
|
||||
|
||||
/* now inside Data and therefore kinda opaque
|
||||
@@ -185,6 +187,12 @@ RadioInterface::RadioInterface()
|
||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||
}
|
||||
|
||||
bool RadioInterface::reconfigure()
|
||||
{
|
||||
applyModemConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioInterface::init()
|
||||
{
|
||||
DEBUG_MSG("Starting meshradio init...\n");
|
||||
@@ -197,6 +205,8 @@ bool RadioInterface::init()
|
||||
// radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor
|
||||
// time.
|
||||
|
||||
applyModemConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -309,7 +319,7 @@ void RadioInterface::applyModemConfig()
|
||||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
int channel_num = channelSettings.channel_num ? channelSettings.channel_num - 1 : hash(channelName) % myRegion->numChannels;
|
||||
freq = myRegion->freq + myRegion->spacing * channel_num;
|
||||
freq = myRegion->freq + radioConfig.preferences.frequency_offset + myRegion->spacing * channel_num;
|
||||
|
||||
DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelName, channelSettings.modem_config, channel_num, power);
|
||||
DEBUG_MSG("Radio myRegion->freq: %f\n", myRegion->freq);
|
||||
@@ -351,12 +361,8 @@ ErrorCode SimRadio::send(MeshPacket *p)
|
||||
|
||||
void RadioInterface::deliverToReceiver(MeshPacket *p)
|
||||
{
|
||||
assert(rxDest);
|
||||
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
|
||||
|
||||
// Nasty hack because our threading is primitive. interfaces shouldn't need to know about routers FIXME
|
||||
if (router)
|
||||
router->setReceivedMessage();
|
||||
router->enqueueReceivedMessage(p);
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
+16
-28
@@ -42,7 +42,6 @@ typedef struct {
|
||||
class RadioInterface
|
||||
{
|
||||
friend class MeshRadio; // for debugging we let that class touch pool
|
||||
PointerQueue<MeshPacket> *rxDest = NULL;
|
||||
|
||||
CallbackObserver<RadioInterface, void *> configChangedObserver =
|
||||
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::reloadConfig);
|
||||
@@ -82,17 +81,11 @@ class RadioInterface
|
||||
float freq = 915.0;
|
||||
|
||||
/** pool is the pool we will alloc our rx packets from
|
||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||
*/
|
||||
RadioInterface();
|
||||
|
||||
virtual ~RadioInterface() {}
|
||||
|
||||
/**
|
||||
* Set where to deliver received packets. This method should only be used by the Router class
|
||||
*/
|
||||
void setReceiver(PointerQueue<MeshPacket> *_rxDest) { rxDest = _rxDest; }
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
*
|
||||
@@ -104,7 +97,11 @@ class RadioInterface
|
||||
virtual bool sleep() { return true; }
|
||||
|
||||
/// Disable this interface (while disabled, no packets can be sent or received)
|
||||
void disable() { disabled = true; sleep(); }
|
||||
void disable()
|
||||
{
|
||||
disabled = true;
|
||||
sleep();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet (possibly by enquing in a private fifo). This routine will
|
||||
@@ -126,7 +123,7 @@ class RadioInterface
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool reconfigure() = 0;
|
||||
virtual bool reconfigure();
|
||||
|
||||
/** The delay to use for retransmitting dropped packets */
|
||||
uint32_t getRetransmissionMsec(const MeshPacket *p);
|
||||
@@ -154,6 +151,9 @@ class RadioInterface
|
||||
*/
|
||||
float getFreq();
|
||||
|
||||
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
||||
virtual bool isIRQPending() { return false; }
|
||||
|
||||
protected:
|
||||
int8_t power = 17; // Set by applyModemConfig()
|
||||
|
||||
@@ -174,13 +174,6 @@ class RadioInterface
|
||||
*/
|
||||
void limitPower();
|
||||
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*
|
||||
* These paramaters will be pull from the channelSettings global
|
||||
*/
|
||||
virtual void applyModemConfig();
|
||||
|
||||
/**
|
||||
* Save the frequency we selected for later reuse.
|
||||
*/
|
||||
@@ -192,6 +185,13 @@ class RadioInterface
|
||||
virtual void saveChannelNum(uint32_t savedChannelNum);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Convert our modemConfig enum into wf, sf, etc...
|
||||
*
|
||||
* These paramaters will be pull from the channelSettings global
|
||||
*/
|
||||
void applyModemConfig();
|
||||
|
||||
/// Return 0 if sleep is okay
|
||||
int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; }
|
||||
|
||||
@@ -208,18 +208,6 @@ class SimRadio : public RadioInterface
|
||||
{
|
||||
public:
|
||||
virtual ErrorCode send(MeshPacket *p);
|
||||
|
||||
// methods from radiohead
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init() { return true; }
|
||||
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool reconfigure() { return true; }
|
||||
};
|
||||
|
||||
/// Debug printing for packets
|
||||
|
||||
@@ -75,7 +75,7 @@ bool RadioLibInterface::canSendImmediately()
|
||||
// TX IRQ from the radio, the radio is probably broken.
|
||||
if (busyTx && (millis() - lastTxStart > 60000)) {
|
||||
DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n");
|
||||
recordCriticalError(CriticalErrorCode_TransmitFailed);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_TransmitFailed);
|
||||
#ifndef NO_ESP32
|
||||
if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot
|
||||
ESP.restart();
|
||||
@@ -312,7 +312,13 @@ void RadioLibInterface::startSend(MeshPacket *txp)
|
||||
size_t numbytes = beginSending(txp);
|
||||
|
||||
int res = iface->startTransmit(radiobuf, numbytes);
|
||||
assert(res == ERR_NONE);
|
||||
if(res != ERR_NONE) {
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_RadioSpiBug);
|
||||
|
||||
// This send failed, but make sure to 'complete' it properly
|
||||
completeSending();
|
||||
startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode)
|
||||
}
|
||||
|
||||
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
||||
enableInterrupt(isrTxLevel0);
|
||||
|
||||
@@ -49,6 +49,9 @@ bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
|
||||
|
||||
stopRetransmission(key);
|
||||
}
|
||||
else {
|
||||
DEBUG_MSG("didn't find pending packet\n");
|
||||
}
|
||||
}
|
||||
|
||||
return FloodingRouter::shouldFilterReceived(p);
|
||||
|
||||
@@ -79,9 +79,10 @@ class ReliableRouter : public FloodingRouter
|
||||
/** Do our retransmission handling */
|
||||
virtual int32_t runOnce()
|
||||
{
|
||||
auto d = FloodingRouter::runOnce();
|
||||
// Note: We must doRetransmissions FIRST, because it might queue up work for the base class runOnce implementation
|
||||
auto d = doRetransmissions();
|
||||
|
||||
int32_t r = doRetransmissions();
|
||||
int32_t r = FloodingRouter::runOnce();
|
||||
|
||||
return min(d, r);
|
||||
}
|
||||
@@ -109,7 +110,6 @@ class ReliableRouter : public FloodingRouter
|
||||
PendingPacket *startRetransmission(MeshPacket *p);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
||||
*
|
||||
|
||||
+92
-46
@@ -4,9 +4,14 @@
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "plugins/RoutingPlugin.h"
|
||||
|
||||
#if defined(HAS_WIFI) || defined(PORTDUINO)
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Router todo
|
||||
*
|
||||
@@ -55,12 +60,30 @@ int32_t Router::runOnce()
|
||||
{
|
||||
MeshPacket *mp;
|
||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
||||
// printPacket("handle fromRadioQ", mp);
|
||||
perhapsHandleReceived(mp);
|
||||
}
|
||||
|
||||
// DEBUG_MSG("sleeping forever!\n");
|
||||
return INT32_MAX; // Wait a long time - until we get woken for the message queue
|
||||
}
|
||||
|
||||
/**
|
||||
* RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for
|
||||
* freeing the packet
|
||||
*/
|
||||
void Router::enqueueReceivedMessage(MeshPacket *p)
|
||||
{
|
||||
if (fromRadioQueue.enqueue(p, 0)) { // NOWAIT - fixme, if queue is full, delete older messages
|
||||
|
||||
// Nasty hack because our threading is primitive. interfaces shouldn't need to know about routers FIXME
|
||||
setReceivedMessage();
|
||||
} else {
|
||||
printPacket("BUG! fromRadioQueue is full! Discarding!", p);
|
||||
packetPool.release(p);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a unique packet id
|
||||
// FIXME, move this someplace better
|
||||
PacketId generatePacketId()
|
||||
@@ -115,17 +138,19 @@ void Router::abortSendAndNak(Routing_Error err, MeshPacket *p)
|
||||
packetPool.release(p);
|
||||
}
|
||||
|
||||
void Router::setReceivedMessage() {
|
||||
void Router::setReceivedMessage()
|
||||
{
|
||||
// DEBUG_MSG("set interval to ASAP\n");
|
||||
setInterval(0); // Run ASAP, so we can figure out our correct sleep time
|
||||
runASAP = true;
|
||||
}
|
||||
|
||||
ErrorCode Router::sendLocal(MeshPacket *p)
|
||||
{
|
||||
// No need to deliver externally if the destination is the local node
|
||||
if (p->to == nodeDB.getNodeNum()) {
|
||||
printPacket("Enqueuing local", p);
|
||||
fromRadioQueue.enqueue(p);
|
||||
setReceivedMessage();
|
||||
printPacket("Enqueued local", p);
|
||||
enqueueReceivedMessage(p);
|
||||
return ERRNO_OK;
|
||||
} else if (!iface) {
|
||||
// We must be sending to remote nodes also, fail if no interface found
|
||||
@@ -143,9 +168,10 @@ ErrorCode Router::sendLocal(MeshPacket *p)
|
||||
}
|
||||
}
|
||||
|
||||
void printBytes(const char *label, const uint8_t *p, size_t numbytes) {
|
||||
void printBytes(const char *label, const uint8_t *p, size_t numbytes)
|
||||
{
|
||||
DEBUG_MSG("%s: ", label);
|
||||
for(size_t i = 0; i < numbytes; i++)
|
||||
for (size_t i = 0; i < numbytes; i++)
|
||||
DEBUG_MSG("%02x ", p[i]);
|
||||
DEBUG_MSG("\n");
|
||||
}
|
||||
@@ -176,36 +202,20 @@ ErrorCode Router::send(MeshPacket *p)
|
||||
assert(p->which_payloadVariant == MeshPacket_encrypted_tag ||
|
||||
p->which_payloadVariant == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now
|
||||
|
||||
// First convert from protobufs to raw bytes
|
||||
// If the packet is not yet encrypted, do so now
|
||||
if (p->which_payloadVariant == MeshPacket_decoded_tag) {
|
||||
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
|
||||
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||
|
||||
// printPacket("pre encrypt", p); // portnum valid here
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
|
||||
|
||||
if (numbytes > MAX_RHPACKETLEN) {
|
||||
abortSendAndNak(Routing_Error_TOO_LARGE, p);
|
||||
return ERRNO_TOO_LARGE;
|
||||
auto encodeResult = perhapsEncode(p);
|
||||
if (encodeResult != Routing_Error_NONE) {
|
||||
abortSendAndNak(encodeResult, p);
|
||||
return encodeResult; // FIXME - this isn't a valid ErrorCode
|
||||
}
|
||||
|
||||
//printBytes("plaintext", bytes, numbytes);
|
||||
|
||||
auto hash = channels.setActiveByIndex(p->channel);
|
||||
if (hash < 0) {
|
||||
// No suitable channel could be found for sending
|
||||
abortSendAndNak(Routing_Error_NO_CHANNEL, p);
|
||||
return ERRNO_NO_CHANNEL;
|
||||
}
|
||||
|
||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||
p->channel = hash;
|
||||
crypto->encrypt(getFrom(p), p->id, numbytes, bytes);
|
||||
|
||||
// Copy back into the packet and set the variant type
|
||||
memcpy(p->encrypted.bytes, bytes, numbytes);
|
||||
p->encrypted.size = numbytes;
|
||||
p->which_payloadVariant = MeshPacket_encrypted_tag;
|
||||
#if defined(HAS_WIFI) || defined(PORTDUINO)
|
||||
if (mqtt)
|
||||
mqtt->onSend(*p, chIndex);
|
||||
#endif
|
||||
}
|
||||
|
||||
assert(iface); // This should have been detected already in sendLocal (or we just received a packet from outside)
|
||||
@@ -228,7 +238,7 @@ void Router::sniffReceived(const MeshPacket *p, const Routing *c)
|
||||
// FIXME, update nodedb here for any packet that passes through us
|
||||
}
|
||||
|
||||
bool Router::perhapsDecode(MeshPacket *p)
|
||||
bool perhapsDecode(MeshPacket *p)
|
||||
{
|
||||
if (p->which_payloadVariant == MeshPacket_decoded_tag)
|
||||
return true; // If packet was already decoded just return
|
||||
@@ -247,19 +257,18 @@ bool Router::perhapsDecode(MeshPacket *p)
|
||||
rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
|
||||
crypto->decrypt(p->from, p->id, rawSize, bytes);
|
||||
|
||||
//printBytes("plaintext", bytes, p->encrypted.size);
|
||||
// printBytes("plaintext", bytes, p->encrypted.size);
|
||||
|
||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||
memset(&p->decoded, 0, sizeof(p->decoded));
|
||||
memset(&p->decoded, 0, sizeof(p->decoded));
|
||||
if (!pb_decode_from_bytes(bytes, rawSize, Data_fields, &p->decoded)) {
|
||||
DEBUG_MSG("Invalid protobufs in received mesh packet (bad psk?)!\n");
|
||||
} else if(p->decoded.portnum == PortNum_UNKNOWN_APP) {
|
||||
} else if (p->decoded.portnum == PortNum_UNKNOWN_APP) {
|
||||
DEBUG_MSG("Invalid portnum (bad psk?)!\n");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// parsing was successful
|
||||
p->which_payloadVariant = MeshPacket_decoded_tag; // change type to decoded
|
||||
p->channel = chIndex; // change to store the index instead of the hash
|
||||
p->channel = chIndex; // change to store the index instead of the hash
|
||||
printPacket("decoded message", p);
|
||||
return true;
|
||||
}
|
||||
@@ -270,6 +279,42 @@ bool Router::perhapsDecode(MeshPacket *p)
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return 0 for success or a Routing_Errror code for failure
|
||||
*/
|
||||
Routing_Error perhapsEncode(MeshPacket *p)
|
||||
{
|
||||
// If the packet is not yet encrypted, do so now
|
||||
if (p->which_payloadVariant == MeshPacket_decoded_tag) {
|
||||
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
|
||||
|
||||
// printPacket("pre encrypt", p); // portnum valid here
|
||||
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
|
||||
|
||||
if (numbytes > MAX_RHPACKETLEN)
|
||||
return Routing_Error_TOO_LARGE;
|
||||
|
||||
// printBytes("plaintext", bytes, numbytes);
|
||||
|
||||
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||
auto hash = channels.setActiveByIndex(chIndex);
|
||||
if (hash < 0)
|
||||
// No suitable channel could be found for sending
|
||||
return Routing_Error_NO_CHANNEL;
|
||||
|
||||
// Now that we are encrypting the packet channel should be the hash (no longer the index)
|
||||
p->channel = hash;
|
||||
crypto->encrypt(getFrom(p), p->id, numbytes, bytes);
|
||||
|
||||
// Copy back into the packet and set the variant type
|
||||
memcpy(p->encrypted.bytes, bytes, numbytes);
|
||||
p->encrypted.size = numbytes;
|
||||
p->which_payloadVariant = MeshPacket_encrypted_tag;
|
||||
}
|
||||
|
||||
return Routing_Error_NONE;
|
||||
}
|
||||
|
||||
NodeNum Router::getNodeNum()
|
||||
{
|
||||
return nodeDB.getNodeNum();
|
||||
@@ -285,26 +330,27 @@ void Router::handleReceived(MeshPacket *p)
|
||||
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
|
||||
|
||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||
bool decoded = perhapsDecode(p);
|
||||
bool decoded = perhapsDecode(p);
|
||||
if (decoded) {
|
||||
// parsing was successful, queue for our recipient
|
||||
printPacket("handleReceived", p);
|
||||
|
||||
// call any promiscious plugins here, make a (non promisiocous) plugin for forwarding messages to phone api
|
||||
// sniffReceived(p);
|
||||
MeshPlugin::callPlugins(*p);
|
||||
} else {
|
||||
printPacket("packet decoding failed (no PSK?)", p);
|
||||
}
|
||||
|
||||
// call plugins here
|
||||
MeshPlugin::callPlugins(*p);
|
||||
}
|
||||
|
||||
void Router::perhapsHandleReceived(MeshPacket *p)
|
||||
{
|
||||
assert(radioConfig.has_preferences);
|
||||
bool ignore = is_in_repeated(radioConfig.preferences.ignore_incoming, getFrom(p));
|
||||
bool ignore = is_in_repeated(radioConfig.preferences.ignore_incoming, p->from);
|
||||
|
||||
if (ignore)
|
||||
DEBUG_MSG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from);
|
||||
else if (ignore |= shouldFilterReceived(p)) {
|
||||
// DEBUG_MSG("Incoming message was filtered 0x%x\n", p->from);
|
||||
DEBUG_MSG("Incoming message was filtered 0x%x\n", p->from);
|
||||
}
|
||||
|
||||
// Note: we avoid calling shouldFilterReceived if we are supposed to ignore certain nodes - because some overrides might
|
||||
|
||||
+22
-17
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Channels.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
#include "PointerQueue.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "Channels.h"
|
||||
|
||||
/**
|
||||
* A mesh aware router that supports multiple interfaces.
|
||||
@@ -22,7 +22,6 @@ class Router : protected concurrency::OSThread
|
||||
RadioInterface *iface = NULL;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -32,11 +31,7 @@ class Router : protected concurrency::OSThread
|
||||
/**
|
||||
* Currently we only allow one interface, that may change in the future
|
||||
*/
|
||||
void addInterface(RadioInterface *_iface)
|
||||
{
|
||||
iface = _iface;
|
||||
iface->setReceiver(&fromRadioQueue);
|
||||
}
|
||||
void addInterface(RadioInterface *_iface) { iface = _iface; }
|
||||
|
||||
/**
|
||||
* do idle processing
|
||||
@@ -53,7 +48,7 @@ class Router : protected concurrency::OSThread
|
||||
ErrorCode sendLocal(MeshPacket *p);
|
||||
|
||||
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
|
||||
bool cancelSending(NodeNum from, PacketId id);
|
||||
bool cancelSending(NodeNum from, PacketId id);
|
||||
|
||||
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
||||
* The returned packet is guaranteed to have a unique packet ID already assigned
|
||||
@@ -69,6 +64,12 @@ class Router : protected concurrency::OSThread
|
||||
*/
|
||||
void setReceivedMessage();
|
||||
|
||||
/**
|
||||
* RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for
|
||||
* freeing the packet
|
||||
*/
|
||||
void enqueueReceivedMessage(MeshPacket *p);
|
||||
|
||||
protected:
|
||||
friend class RoutingPlugin;
|
||||
|
||||
@@ -83,7 +84,7 @@ class Router : protected concurrency::OSThread
|
||||
|
||||
/**
|
||||
* Should this incoming filter be dropped?
|
||||
*
|
||||
*
|
||||
* FIXME, move this into the new RoutingPlugin and do the filtering there using the regular plugin logic
|
||||
*
|
||||
* Called immedately on receiption, before any further processing.
|
||||
@@ -97,18 +98,11 @@ class Router : protected concurrency::OSThread
|
||||
*/
|
||||
virtual void sniffReceived(const MeshPacket *p, const Routing *c);
|
||||
|
||||
/**
|
||||
* Remove any encryption and decode the protobufs inside this packet (if necessary).
|
||||
*
|
||||
* @return true for success, false for corrupt packet.
|
||||
*/
|
||||
bool perhapsDecode(MeshPacket *p);
|
||||
|
||||
/**
|
||||
* Send an ack or a nak packet back towards whoever sent idFrom
|
||||
*/
|
||||
void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
|
||||
|
||||
|
||||
private:
|
||||
/**
|
||||
* Called from loop()
|
||||
@@ -134,6 +128,17 @@ class Router : protected concurrency::OSThread
|
||||
void abortSendAndNak(Routing_Error err, MeshPacket *p);
|
||||
};
|
||||
|
||||
/** FIXME - move this into a mesh packet class
|
||||
* Remove any encryption and decode the protobufs inside this packet (if necessary).
|
||||
*
|
||||
* @return true for success, false for corrupt packet.
|
||||
*/
|
||||
bool perhapsDecode(MeshPacket *p);
|
||||
|
||||
/** Return 0 for success or a Routing_Errror code for failure
|
||||
*/
|
||||
Routing_Error perhapsEncode(MeshPacket *p);
|
||||
|
||||
extern Router *router;
|
||||
|
||||
/// Generate a unique packet id
|
||||
|
||||
@@ -23,8 +23,6 @@ bool SX1262Interface::init()
|
||||
pinMode(SX1262_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
RadioLibInterface::init();
|
||||
|
||||
#ifdef SX1262_RXEN // set not rx or tx mode
|
||||
digitalWrite(SX1262_RXEN, LOW); // Set low before becoming an output
|
||||
pinMode(SX1262_RXEN, OUTPUT);
|
||||
@@ -37,12 +35,12 @@ bool SX1262Interface::init()
|
||||
#ifndef SX1262_E22
|
||||
float tcxoVoltage = 0; // None - we use an XTAL
|
||||
#else
|
||||
float tcxoVoltage =
|
||||
1.8; // E22 uses DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575
|
||||
// Use DIO3 to power tcxo per https://github.com/jgromes/RadioLib/issues/12#issuecomment-520695575
|
||||
float tcxoVoltage = 1.8;
|
||||
#endif
|
||||
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
||||
|
||||
applyModemConfig();
|
||||
RadioLibInterface::init();
|
||||
|
||||
if (power == 0)
|
||||
power = SX1262_MAX_POWER;
|
||||
@@ -61,6 +59,30 @@ bool SX1262Interface::init()
|
||||
res = lora.setDio2AsRfSwitch(false);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
||||
uint8_t crcLSB = 0;
|
||||
int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
//if(crcLSB != 0x0f)
|
||||
// RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
crcLSB = 0x5a;
|
||||
err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
if(crcLSB != 0x5a)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
// If we got this far register accesses (and therefore SPI comms) are good
|
||||
#endif
|
||||
|
||||
if (res == ERR_NONE)
|
||||
res = lora.setCRC(SX126X_LORA_CRC_ON);
|
||||
|
||||
@@ -72,20 +94,23 @@ bool SX1262Interface::init()
|
||||
|
||||
bool SX1262Interface::reconfigure()
|
||||
{
|
||||
applyModemConfig();
|
||||
RadioLibInterface::reconfigure();
|
||||
|
||||
// set mode to standby
|
||||
setStandby();
|
||||
|
||||
// configure publicly accessible settings
|
||||
int err = lora.setSpreadingFactor(sf);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
err = lora.setBandwidth(bw);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
err = lora.setCodingRate(cr);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
// Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
|
||||
err = lora.setRxGain(true);
|
||||
@@ -101,7 +126,8 @@ bool SX1262Interface::reconfigure()
|
||||
assert(err == ERR_NONE);
|
||||
|
||||
err = lora.setFrequency(freq);
|
||||
if(err != ERR_NONE) recordCriticalError(CriticalErrorCode_InvalidRadioSetting);
|
||||
if (err != ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
|
||||
|
||||
if (power > 22) // This chip has lower power limits than some
|
||||
power = 22;
|
||||
@@ -142,6 +168,7 @@ void SX1262Interface::addReceiveMetadata(MeshPacket *mp)
|
||||
{
|
||||
// DEBUG_MSG("PacketStatus %x\n", lora.getPacketStatus());
|
||||
mp->rx_snr = lora.getSNR();
|
||||
mp->rx_rssi = lround(lora.getRSSI());
|
||||
}
|
||||
|
||||
/** We override to turn on transmitter power as needed.
|
||||
|
||||
@@ -25,6 +25,8 @@ class SX1262Interface : public RadioLibInterface
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep();
|
||||
|
||||
bool isIRQPending() { return lora.getIrqStatus() != 0; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Glue functions called from ISR land
|
||||
|
||||
+62
-28
@@ -1,52 +1,72 @@
|
||||
#include "StreamAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#define START1 0x94
|
||||
#define START2 0xc3
|
||||
#define HEADER_LEN 4
|
||||
|
||||
void StreamAPI::loop()
|
||||
int32_t StreamAPI::runOnce()
|
||||
{
|
||||
auto result = readStream();
|
||||
writeStream();
|
||||
readStream();
|
||||
checkConnectionTimeout();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read any rx chars from the link and call handleToRadio
|
||||
*/
|
||||
void StreamAPI::readStream()
|
||||
int32_t StreamAPI::readStream()
|
||||
{
|
||||
while (stream->available()) { // Currently we never want to block
|
||||
uint8_t c = stream->read();
|
||||
uint32_t now = millis();
|
||||
if (!stream->available()) {
|
||||
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
|
||||
bool recentRx = (now - lastRxMsec) < 2000;
|
||||
return recentRx ? 5 : 250;
|
||||
} else {
|
||||
while (stream->available()) { // Currently we never want to block
|
||||
uint8_t c = stream->read();
|
||||
|
||||
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
|
||||
size_t ptr = rxPtr++; // assume we will probably advance the rxPtr
|
||||
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
|
||||
size_t ptr = rxPtr;
|
||||
|
||||
rxBuf[ptr] = c; // store all bytes (including framing)
|
||||
rxPtr++; // assume we will probably advance the rxPtr
|
||||
rxBuf[ptr] = c; // store all bytes (including framing)
|
||||
|
||||
if (ptr == 0) { // looking for START1
|
||||
if (c != START1)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr == 1) { // looking for START2
|
||||
if (c != START2)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing
|
||||
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
|
||||
// console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
|
||||
|
||||
if (ptr == HEADER_LEN) {
|
||||
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
|
||||
// protobuf also)
|
||||
if (len > MAX_TO_FROM_RADIO_SIZE)
|
||||
rxPtr = 0; // length is bogus, restart search for framing
|
||||
}
|
||||
if (ptr == 0) { // looking for START1
|
||||
if (c != START1)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr == 1) { // looking for START2
|
||||
if (c != START2)
|
||||
rxPtr = 0; // failed to find framing
|
||||
} else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
|
||||
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
|
||||
|
||||
if (rxPtr != 0 && ptr + 1 == len + HEADER_LEN) {
|
||||
// If we didn't just fail the packet and we now have the right # of bytes, parse it
|
||||
handleToRadio(rxBuf + HEADER_LEN, len);
|
||||
rxPtr = 0; // start over again
|
||||
// console->printf("len %d\n", len);
|
||||
|
||||
if (ptr == HEADER_LEN - 1) {
|
||||
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
|
||||
// protobuf also)
|
||||
if (len > MAX_TO_FROM_RADIO_SIZE)
|
||||
rxPtr = 0; // length is bogus, restart search for framing
|
||||
}
|
||||
|
||||
if (rxPtr != 0) // Is packet still considered 'good'?
|
||||
if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
|
||||
rxPtr = 0; // start over again on the next packet
|
||||
|
||||
// If we didn't just fail the packet and we now have the right # of bytes, parse it
|
||||
handleToRadio(rxBuf + HEADER_LEN, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we had bytes available this time, so assume we might have them next time also
|
||||
lastRxMsec = now;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +91,7 @@ void StreamAPI::writeStream()
|
||||
void StreamAPI::emitTxBuffer(size_t len)
|
||||
{
|
||||
if (len != 0) {
|
||||
DEBUG_MSG("emit tx %d\n", len);
|
||||
// DEBUG_MSG("emit tx %d\n", len);
|
||||
txBuf[0] = START1;
|
||||
txBuf[1] = START2;
|
||||
txBuf[2] = (len >> 8) & 0xff;
|
||||
@@ -93,6 +113,20 @@ void StreamAPI::emitRebooted()
|
||||
fromRadioScratch.which_payloadVariant = FromRadio_rebooted_tag;
|
||||
fromRadioScratch.rebooted = true;
|
||||
|
||||
DEBUG_MSG("Emitting reboot packet for serial shell\n");
|
||||
// DEBUG_MSG("Emitting reboot packet for serial shell\n");
|
||||
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch));
|
||||
}
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
void StreamAPI::onConnectionChanged(bool connected)
|
||||
{
|
||||
// FIXME do reference counting instead
|
||||
|
||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
// FIXME, we get no notice of serial going away, we should instead automatically generate this event if we haven't
|
||||
// received a packet in a while
|
||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "PhoneAPI.h"
|
||||
#include "Stream.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
// A To/FromRadio packet + our 32 bit header
|
||||
#define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t))
|
||||
@@ -27,7 +28,7 @@ valid utf8 encoding. This makes it a bit easier to start a device outputting reg
|
||||
after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding.
|
||||
|
||||
*/
|
||||
class StreamAPI : public PhoneAPI
|
||||
class StreamAPI : public PhoneAPI, protected concurrency::OSThread
|
||||
{
|
||||
/**
|
||||
* The stream we read/write from
|
||||
@@ -37,21 +38,23 @@ class StreamAPI : public PhoneAPI
|
||||
uint8_t rxBuf[MAX_STREAM_BUF_SIZE];
|
||||
size_t rxPtr = 0;
|
||||
|
||||
/// time of last rx, used, to slow down our polling if we haven't heard from anyone
|
||||
uint32_t lastRxMsec = 0;
|
||||
|
||||
public:
|
||||
StreamAPI(Stream *_stream) : stream(_stream) {}
|
||||
StreamAPI(Stream *_stream) : concurrency::OSThread("StreamAPI"), stream(_stream) {}
|
||||
|
||||
/**
|
||||
* Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the
|
||||
* phone.
|
||||
* FIXME, to support better power behavior instead move to a thread and block on serial reads.
|
||||
*/
|
||||
void loop();
|
||||
virtual int32_t runOnce();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Read any rx chars from the link and call handleToRadio
|
||||
*/
|
||||
void readStream();
|
||||
int32_t readStream();
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
@@ -63,7 +66,12 @@ class StreamAPI : public PhoneAPI
|
||||
* Send a FromRadio.rebooted = true packet to the phone
|
||||
*/
|
||||
void emitRebooted();
|
||||
|
||||
virtual void onConnectionChanged(bool connected);
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() = 0;
|
||||
|
||||
/**
|
||||
* Send the current txBuffer over our stream
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,9 @@ template <class T> class TypedQueue
|
||||
|
||||
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
||||
|
||||
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
||||
/** euqueue a packet. Also, maxWait used to default to portMAX_DELAY, but we now want to callers to THINK about what blocking
|
||||
* they want */
|
||||
bool enqueue(T x, TickType_t maxWait)
|
||||
{
|
||||
if (reader) {
|
||||
reader->setInterval(0);
|
||||
|
||||
@@ -25,6 +25,8 @@ typedef struct _AdminMessage {
|
||||
Channel get_channel_response;
|
||||
bool confirm_set_channel;
|
||||
bool confirm_set_radio;
|
||||
bool exit_simulator;
|
||||
int32_t reboot_seconds;
|
||||
};
|
||||
} AdminMessage;
|
||||
|
||||
@@ -47,6 +49,8 @@ extern "C" {
|
||||
#define AdminMessage_get_channel_response_tag 7
|
||||
#define AdminMessage_confirm_set_channel_tag 32
|
||||
#define AdminMessage_confirm_set_radio_tag 33
|
||||
#define AdminMessage_exit_simulator_tag 34
|
||||
#define AdminMessage_reboot_seconds_tag 35
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define AdminMessage_FIELDLIST(X, a) \
|
||||
@@ -58,7 +62,9 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,get_radio_response,get_radio_respons
|
||||
X(a, STATIC, ONEOF, UINT32, (variant,get_channel_request,get_channel_request), 6) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,get_channel_response,get_channel_response), 7) \
|
||||
X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_channel,confirm_set_channel), 32) \
|
||||
X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_radio,confirm_set_radio), 33)
|
||||
X(a, STATIC, ONEOF, BOOL, (variant,confirm_set_radio,confirm_set_radio), 33) \
|
||||
X(a, STATIC, ONEOF, BOOL, (variant,exit_simulator,exit_simulator), 34) \
|
||||
X(a, STATIC, ONEOF, INT32, (variant,reboot_seconds,reboot_seconds), 35)
|
||||
#define AdminMessage_CALLBACK NULL
|
||||
#define AdminMessage_DEFAULT NULL
|
||||
#define AdminMessage_variant_set_radio_MSGTYPE RadioConfig
|
||||
@@ -73,7 +79,7 @@ extern const pb_msgdesc_t AdminMessage_msg;
|
||||
#define AdminMessage_fields &AdminMessage_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define AdminMessage_size 351
|
||||
#define AdminMessage_size 397
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
PB_BIND(ServiceEnvelope, ServiceEnvelope, 2)
|
||||
|
||||
|
||||
PB_BIND(ChannelSet, ChannelSet, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#ifndef PB_APPONLY_PB_H_INCLUDED
|
||||
#define PB_APPONLY_PB_H_INCLUDED
|
||||
#include <pb.h>
|
||||
#include "mesh.pb.h"
|
||||
#include "channel.pb.h"
|
||||
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
@@ -16,54 +15,31 @@ typedef struct _ChannelSet {
|
||||
pb_callback_t settings;
|
||||
} ChannelSet;
|
||||
|
||||
typedef struct _ServiceEnvelope {
|
||||
bool has_packet;
|
||||
MeshPacket packet;
|
||||
pb_callback_t channel_id;
|
||||
pb_callback_t gateway_id;
|
||||
} ServiceEnvelope;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define ServiceEnvelope_init_default {false, MeshPacket_init_default, {{NULL}, NULL}, {{NULL}, NULL}}
|
||||
#define ChannelSet_init_default {{{NULL}, NULL}}
|
||||
#define ServiceEnvelope_init_zero {false, MeshPacket_init_zero, {{NULL}, NULL}, {{NULL}, NULL}}
|
||||
#define ChannelSet_init_zero {{{NULL}, NULL}}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define ChannelSet_settings_tag 1
|
||||
#define ServiceEnvelope_packet_tag 1
|
||||
#define ServiceEnvelope_channel_id_tag 2
|
||||
#define ServiceEnvelope_gateway_id_tag 3
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define ServiceEnvelope_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, packet, 1) \
|
||||
X(a, CALLBACK, SINGULAR, STRING, channel_id, 2) \
|
||||
X(a, CALLBACK, SINGULAR, STRING, gateway_id, 3)
|
||||
#define ServiceEnvelope_CALLBACK pb_default_field_callback
|
||||
#define ServiceEnvelope_DEFAULT NULL
|
||||
#define ServiceEnvelope_packet_MSGTYPE MeshPacket
|
||||
|
||||
#define ChannelSet_FIELDLIST(X, a) \
|
||||
X(a, CALLBACK, REPEATED, MESSAGE, settings, 1)
|
||||
#define ChannelSet_CALLBACK pb_default_field_callback
|
||||
#define ChannelSet_DEFAULT NULL
|
||||
#define ChannelSet_settings_MSGTYPE ChannelSettings
|
||||
|
||||
extern const pb_msgdesc_t ServiceEnvelope_msg;
|
||||
extern const pb_msgdesc_t ChannelSet_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define ServiceEnvelope_fields &ServiceEnvelope_msg
|
||||
#define ChannelSet_fields &ChannelSet_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* ServiceEnvelope_size depends on runtime parameters */
|
||||
/* ChannelSet_size depends on runtime parameters */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -125,7 +125,7 @@ extern const pb_msgdesc_t ChannelFile_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define LegacyRadioConfig_size 4
|
||||
#define LegacyRadioConfig_LegacyPreferences_size 2
|
||||
#define DeviceState_size 4898
|
||||
#define DeviceState_size 5190
|
||||
#define ChannelFile_size 832
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -39,6 +39,9 @@ PB_BIND(FromRadio, FromRadio, 2)
|
||||
PB_BIND(ToRadio, ToRadio, 2)
|
||||
|
||||
|
||||
PB_BIND(ToRadio_PeerInfo, ToRadio_PeerInfo, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ typedef enum _HardwareModel {
|
||||
HardwareModel_TLORA_V1 = 2,
|
||||
HardwareModel_TLORA_V2_1_1p6 = 3,
|
||||
HardwareModel_TBEAM = 4,
|
||||
HardwareModel_HELTEC = 5,
|
||||
HardwareModel_HELTEC_V2_0 = 5,
|
||||
HardwareModel_TBEAM0p7 = 6,
|
||||
HardwareModel_T_ECHO = 7,
|
||||
HardwareModel_TLORA_V1_1p3 = 8,
|
||||
HardwareModel_RAK4631 = 9,
|
||||
HardwareModel_HELTEC_V2_1 = 10,
|
||||
HardwareModel_LORA_RELAY_V1 = 32,
|
||||
HardwareModel_NRF52840DK = 33,
|
||||
HardwareModel_PPR = 34,
|
||||
@@ -45,7 +47,9 @@ typedef enum _CriticalErrorCode {
|
||||
CriticalErrorCode_NoAXP192 = 6,
|
||||
CriticalErrorCode_InvalidRadioSetting = 7,
|
||||
CriticalErrorCode_TransmitFailed = 8,
|
||||
CriticalErrorCode_Brownout = 9
|
||||
CriticalErrorCode_Brownout = 9,
|
||||
CriticalErrorCode_SX1262Failure = 10,
|
||||
CriticalErrorCode_RadioSpiBug = 11
|
||||
} CriticalErrorCode;
|
||||
|
||||
typedef enum _Routing_Error {
|
||||
@@ -57,7 +61,9 @@ typedef enum _Routing_Error {
|
||||
Routing_Error_MAX_RETRANSMIT = 5,
|
||||
Routing_Error_NO_CHANNEL = 6,
|
||||
Routing_Error_TOO_LARGE = 7,
|
||||
Routing_Error_NO_RESPONSE = 8
|
||||
Routing_Error_NO_RESPONSE = 8,
|
||||
Routing_Error_BAD_REQUEST = 32,
|
||||
Routing_Error_NOT_AUTHORIZED = 33
|
||||
} Routing_Error;
|
||||
|
||||
typedef enum _MeshPacket_Priority {
|
||||
@@ -104,10 +110,11 @@ typedef struct _MyNodeInfo {
|
||||
uint32_t num_bands;
|
||||
char region[12];
|
||||
char hw_model_deprecated[16];
|
||||
char firmware_version[12];
|
||||
char firmware_version[18];
|
||||
CriticalErrorCode error_code;
|
||||
uint32_t error_address;
|
||||
uint32_t error_count;
|
||||
uint32_t reboot_count;
|
||||
uint32_t message_timeout_msec;
|
||||
uint32_t min_app_version;
|
||||
uint32_t max_channels;
|
||||
@@ -126,12 +133,18 @@ typedef struct _RouteDiscovery {
|
||||
uint32_t route[8];
|
||||
} RouteDiscovery;
|
||||
|
||||
typedef struct _ToRadio_PeerInfo {
|
||||
uint32_t app_version;
|
||||
bool mqtt_gateway;
|
||||
} ToRadio_PeerInfo;
|
||||
|
||||
typedef struct _User {
|
||||
char id[16];
|
||||
char long_name[40];
|
||||
char short_name[5];
|
||||
pb_byte_t macaddr[6];
|
||||
HardwareModel hw_model;
|
||||
bool is_licensed;
|
||||
} User;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t;
|
||||
@@ -150,6 +163,7 @@ typedef struct _MeshPacket {
|
||||
uint8_t hop_limit;
|
||||
bool want_ack;
|
||||
MeshPacket_Priority priority;
|
||||
int32_t rx_rssi;
|
||||
} MeshPacket;
|
||||
|
||||
typedef struct _NodeInfo {
|
||||
@@ -158,6 +172,7 @@ typedef struct _NodeInfo {
|
||||
User user;
|
||||
bool has_position;
|
||||
Position position;
|
||||
uint32_t last_heard;
|
||||
float snr;
|
||||
} NodeInfo;
|
||||
|
||||
@@ -187,7 +202,9 @@ typedef struct _ToRadio {
|
||||
pb_size_t which_payloadVariant;
|
||||
union {
|
||||
MeshPacket packet;
|
||||
ToRadio_PeerInfo peer_info;
|
||||
uint32_t want_config_id;
|
||||
bool disconnect;
|
||||
};
|
||||
} ToRadio;
|
||||
|
||||
@@ -202,12 +219,12 @@ typedef struct _ToRadio {
|
||||
#define _Constants_ARRAYSIZE ((Constants)(Constants_DATA_PAYLOAD_LEN+1))
|
||||
|
||||
#define _CriticalErrorCode_MIN CriticalErrorCode_None
|
||||
#define _CriticalErrorCode_MAX CriticalErrorCode_Brownout
|
||||
#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_Brownout+1))
|
||||
#define _CriticalErrorCode_MAX CriticalErrorCode_RadioSpiBug
|
||||
#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_RadioSpiBug+1))
|
||||
|
||||
#define _Routing_Error_MIN Routing_Error_NONE
|
||||
#define _Routing_Error_MAX Routing_Error_NO_RESPONSE
|
||||
#define _Routing_Error_ARRAYSIZE ((Routing_Error)(Routing_Error_NO_RESPONSE+1))
|
||||
#define _Routing_Error_MAX Routing_Error_NOT_AUTHORIZED
|
||||
#define _Routing_Error_ARRAYSIZE ((Routing_Error)(Routing_Error_NOT_AUTHORIZED+1))
|
||||
|
||||
#define _MeshPacket_Priority_MIN MeshPacket_Priority_UNSET
|
||||
#define _MeshPacket_Priority_MAX MeshPacket_Priority_MAX
|
||||
@@ -224,27 +241,29 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define Position_init_default {0, 0, 0, 0, 0}
|
||||
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN}
|
||||
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN, 0}
|
||||
#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}}
|
||||
#define Routing_init_default {0, {RouteDiscovery_init_default}}
|
||||
#define Data_init_default {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0}
|
||||
#define MeshPacket_init_default {0, 0, 0, 0, {Data_init_default}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN}
|
||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0}
|
||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0}
|
||||
#define MeshPacket_init_default {0, 0, 0, 0, {Data_init_default}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0}
|
||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0}
|
||||
#define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN}
|
||||
#define FromRadio_init_default {0, 0, {MyNodeInfo_init_default}}
|
||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||
#define ToRadio_PeerInfo_init_default {0, 0}
|
||||
#define Position_init_zero {0, 0, 0, 0, 0}
|
||||
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN}
|
||||
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN, 0}
|
||||
#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
|
||||
#define Routing_init_zero {0, {RouteDiscovery_init_zero}}
|
||||
#define Data_init_zero {_PortNum_MIN, {0, {0}}, 0, 0, 0, 0}
|
||||
#define MeshPacket_init_zero {0, 0, 0, 0, {Data_init_zero}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN}
|
||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0}
|
||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0}
|
||||
#define MeshPacket_init_zero {0, 0, 0, 0, {Data_init_zero}, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN, 0}
|
||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0}
|
||||
#define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN}
|
||||
#define FromRadio_init_zero {0, 0, {MyNodeInfo_init_zero}}
|
||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||
#define ToRadio_PeerInfo_init_zero {0, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define Data_portnum_tag 1
|
||||
@@ -266,6 +285,7 @@ extern "C" {
|
||||
#define MyNodeInfo_error_code_tag 7
|
||||
#define MyNodeInfo_error_address_tag 8
|
||||
#define MyNodeInfo_error_count_tag 9
|
||||
#define MyNodeInfo_reboot_count_tag 10
|
||||
#define MyNodeInfo_message_timeout_msec_tag 13
|
||||
#define MyNodeInfo_min_app_version_tag 14
|
||||
#define MyNodeInfo_max_channels_tag 15
|
||||
@@ -275,11 +295,14 @@ extern "C" {
|
||||
#define Position_battery_level_tag 4
|
||||
#define Position_time_tag 9
|
||||
#define RouteDiscovery_route_tag 2
|
||||
#define ToRadio_PeerInfo_app_version_tag 1
|
||||
#define ToRadio_PeerInfo_mqtt_gateway_tag 2
|
||||
#define User_id_tag 1
|
||||
#define User_long_name_tag 2
|
||||
#define User_short_name_tag 3
|
||||
#define User_macaddr_tag 4
|
||||
#define User_hw_model_tag 6
|
||||
#define User_is_licensed_tag 7
|
||||
#define MeshPacket_from_tag 1
|
||||
#define MeshPacket_to_tag 2
|
||||
#define MeshPacket_channel_tag 3
|
||||
@@ -291,9 +314,11 @@ extern "C" {
|
||||
#define MeshPacket_hop_limit_tag 10
|
||||
#define MeshPacket_want_ack_tag 11
|
||||
#define MeshPacket_priority_tag 12
|
||||
#define MeshPacket_rx_rssi_tag 13
|
||||
#define NodeInfo_num_tag 1
|
||||
#define NodeInfo_user_tag 2
|
||||
#define NodeInfo_position_tag 3
|
||||
#define NodeInfo_last_heard_tag 4
|
||||
#define NodeInfo_snr_tag 7
|
||||
#define Routing_route_request_tag 1
|
||||
#define Routing_route_reply_tag 2
|
||||
@@ -306,7 +331,9 @@ extern "C" {
|
||||
#define FromRadio_rebooted_tag 9
|
||||
#define FromRadio_packet_tag 11
|
||||
#define ToRadio_packet_tag 2
|
||||
#define ToRadio_peer_info_tag 3
|
||||
#define ToRadio_want_config_id_tag 100
|
||||
#define ToRadio_disconnect_tag 104
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define Position_FIELDLIST(X, a) \
|
||||
@@ -323,7 +350,8 @@ X(a, STATIC, SINGULAR, STRING, id, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, long_name, 2) \
|
||||
X(a, STATIC, SINGULAR, STRING, short_name, 3) \
|
||||
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) \
|
||||
X(a, STATIC, SINGULAR, UENUM, hw_model, 6)
|
||||
X(a, STATIC, SINGULAR, UENUM, hw_model, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, is_licensed, 7)
|
||||
#define User_CALLBACK NULL
|
||||
#define User_DEFAULT NULL
|
||||
|
||||
@@ -362,7 +390,8 @@ X(a, STATIC, SINGULAR, FIXED32, rx_time, 7) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, rx_snr, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) \
|
||||
X(a, STATIC, SINGULAR, BOOL, want_ack, 11) \
|
||||
X(a, STATIC, SINGULAR, UENUM, priority, 12)
|
||||
X(a, STATIC, SINGULAR, UENUM, priority, 12) \
|
||||
X(a, STATIC, SINGULAR, INT32, rx_rssi, 13)
|
||||
#define MeshPacket_CALLBACK NULL
|
||||
#define MeshPacket_DEFAULT NULL
|
||||
#define MeshPacket_payloadVariant_decoded_MSGTYPE Data
|
||||
@@ -371,6 +400,7 @@ X(a, STATIC, SINGULAR, UENUM, priority, 12)
|
||||
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
|
||||
X(a, STATIC, SINGULAR, FIXED32, last_heard, 4) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, snr, 7)
|
||||
#define NodeInfo_CALLBACK NULL
|
||||
#define NodeInfo_DEFAULT NULL
|
||||
@@ -387,6 +417,7 @@ X(a, STATIC, SINGULAR, STRING, firmware_version, 6) \
|
||||
X(a, STATIC, SINGULAR, UENUM, error_code, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, error_address, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, error_count, 9) \
|
||||
X(a, STATIC, SINGULAR, UINT32, reboot_count, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, message_timeout_msec, 13) \
|
||||
X(a, STATIC, SINGULAR, UINT32, min_app_version, 14) \
|
||||
X(a, STATIC, SINGULAR, UINT32, max_channels, 15)
|
||||
@@ -418,10 +449,19 @@ X(a, STATIC, ONEOF, MESSAGE, (payloadVariant,packet,packet), 11)
|
||||
|
||||
#define ToRadio_FIELDLIST(X, a) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payloadVariant,packet,packet), 2) \
|
||||
X(a, STATIC, ONEOF, UINT32, (payloadVariant,want_config_id,want_config_id), 100)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payloadVariant,peer_info,peer_info), 3) \
|
||||
X(a, STATIC, ONEOF, UINT32, (payloadVariant,want_config_id,want_config_id), 100) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payloadVariant,disconnect,disconnect), 104)
|
||||
#define ToRadio_CALLBACK NULL
|
||||
#define ToRadio_DEFAULT NULL
|
||||
#define ToRadio_payloadVariant_packet_MSGTYPE MeshPacket
|
||||
#define ToRadio_payloadVariant_peer_info_MSGTYPE ToRadio_PeerInfo
|
||||
|
||||
#define ToRadio_PeerInfo_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, app_version, 1) \
|
||||
X(a, STATIC, SINGULAR, BOOL, mqtt_gateway, 2)
|
||||
#define ToRadio_PeerInfo_CALLBACK NULL
|
||||
#define ToRadio_PeerInfo_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t Position_msg;
|
||||
extern const pb_msgdesc_t User_msg;
|
||||
@@ -434,6 +474,7 @@ extern const pb_msgdesc_t MyNodeInfo_msg;
|
||||
extern const pb_msgdesc_t LogRecord_msg;
|
||||
extern const pb_msgdesc_t FromRadio_msg;
|
||||
extern const pb_msgdesc_t ToRadio_msg;
|
||||
extern const pb_msgdesc_t ToRadio_PeerInfo_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define Position_fields &Position_msg
|
||||
@@ -447,19 +488,21 @@ extern const pb_msgdesc_t ToRadio_msg;
|
||||
#define LogRecord_fields &LogRecord_msg
|
||||
#define FromRadio_fields &FromRadio_msg
|
||||
#define ToRadio_fields &ToRadio_msg
|
||||
#define ToRadio_PeerInfo_fields &ToRadio_PeerInfo_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define Position_size 37
|
||||
#define User_size 74
|
||||
#define User_size 76
|
||||
#define RouteDiscovery_size 40
|
||||
#define Routing_size 42
|
||||
#define Data_size 260
|
||||
#define MeshPacket_size 298
|
||||
#define NodeInfo_size 126
|
||||
#define MyNodeInfo_size 89
|
||||
#define MeshPacket_size 309
|
||||
#define NodeInfo_size 133
|
||||
#define MyNodeInfo_size 101
|
||||
#define LogRecord_size 81
|
||||
#define FromRadio_size 307
|
||||
#define ToRadio_size 301
|
||||
#define FromRadio_size 318
|
||||
#define ToRadio_size 312
|
||||
#define ToRadio_PeerInfo_size 8
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/* Automatically generated nanopb constant definitions */
|
||||
/* Generated by nanopb-0.4.4 */
|
||||
|
||||
#include "mqtt.pb.h"
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
PB_BIND(ServiceEnvelope, ServiceEnvelope, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/* Automatically generated nanopb header */
|
||||
/* Generated by nanopb-0.4.4 */
|
||||
|
||||
#ifndef PB_MQTT_PB_H_INCLUDED
|
||||
#define PB_MQTT_PB_H_INCLUDED
|
||||
#include <pb.h>
|
||||
#include "mesh.pb.h"
|
||||
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
/* Struct definitions */
|
||||
typedef struct _ServiceEnvelope {
|
||||
struct _MeshPacket *packet;
|
||||
char *channel_id;
|
||||
char *gateway_id;
|
||||
} ServiceEnvelope;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define ServiceEnvelope_init_default {NULL, NULL, NULL}
|
||||
#define ServiceEnvelope_init_zero {NULL, NULL, NULL}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define ServiceEnvelope_packet_tag 1
|
||||
#define ServiceEnvelope_channel_id_tag 2
|
||||
#define ServiceEnvelope_gateway_id_tag 3
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define ServiceEnvelope_FIELDLIST(X, a) \
|
||||
X(a, POINTER, OPTIONAL, MESSAGE, packet, 1) \
|
||||
X(a, POINTER, SINGULAR, STRING, channel_id, 2) \
|
||||
X(a, POINTER, SINGULAR, STRING, gateway_id, 3)
|
||||
#define ServiceEnvelope_CALLBACK NULL
|
||||
#define ServiceEnvelope_DEFAULT NULL
|
||||
#define ServiceEnvelope_packet_MSGTYPE MeshPacket
|
||||
|
||||
extern const pb_msgdesc_t ServiceEnvelope_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define ServiceEnvelope_fields &ServiceEnvelope_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* ServiceEnvelope_size depends on runtime parameters */
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -85,6 +85,10 @@ typedef struct _RadioConfig_UserPreferences {
|
||||
bool is_router;
|
||||
bool is_low_power;
|
||||
bool fixed_position;
|
||||
bool serial_disabled;
|
||||
float frequency_offset;
|
||||
char mqtt_server[32];
|
||||
bool mqtt_disabled;
|
||||
bool factory_reset;
|
||||
bool debug_log_enabled;
|
||||
pb_size_t ignore_incoming_count;
|
||||
@@ -150,9 +154,9 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
|
||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
|
||||
@@ -177,6 +181,10 @@ extern "C" {
|
||||
#define RadioConfig_UserPreferences_is_router_tag 37
|
||||
#define RadioConfig_UserPreferences_is_low_power_tag 38
|
||||
#define RadioConfig_UserPreferences_fixed_position_tag 39
|
||||
#define RadioConfig_UserPreferences_serial_disabled_tag 40
|
||||
#define RadioConfig_UserPreferences_frequency_offset_tag 41
|
||||
#define RadioConfig_UserPreferences_mqtt_server_tag 42
|
||||
#define RadioConfig_UserPreferences_mqtt_disabled_tag 43
|
||||
#define RadioConfig_UserPreferences_factory_reset_tag 100
|
||||
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
|
||||
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
|
||||
@@ -237,6 +245,10 @@ X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 36) \
|
||||
X(a, STATIC, SINGULAR, BOOL, is_router, 37) \
|
||||
X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
|
||||
X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \
|
||||
X(a, STATIC, SINGULAR, BOOL, serial_disabled, 40) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, frequency_offset, 41) \
|
||||
X(a, STATIC, SINGULAR, STRING, mqtt_server, 42) \
|
||||
X(a, STATIC, SINGULAR, BOOL, mqtt_disabled, 43) \
|
||||
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
|
||||
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
|
||||
@@ -276,8 +288,8 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg;
|
||||
#define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define RadioConfig_size 348
|
||||
#define RadioConfig_UserPreferences_size 345
|
||||
#define RadioConfig_size 394
|
||||
#define RadioConfig_UserPreferences_size 391
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "airtime.h"
|
||||
#include "main.h"
|
||||
#include "mesh/http/ContentHelper.h"
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <HTTPMultipartBodyParser.hpp>
|
||||
#include <HTTPURLEncodedBodyParser.hpp>
|
||||
#include <SPIFFS.h>
|
||||
#include "RadioLibInterface.h"
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
@@ -77,7 +77,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
|
||||
ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio);
|
||||
ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio);
|
||||
|
||||
ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
|
||||
ResourceNode *nodeHotspotApple = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
|
||||
ResourceNode *nodeHotspotAndroid = new ResourceNode("/generate_204", "GET", &handleHotspot);
|
||||
ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
|
||||
ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot);
|
||||
ResourceNode *nodeStaticBrowse = new ResourceNode("/static", "GET", &handleStaticBrowse);
|
||||
@@ -96,7 +97,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
|
||||
secureServer->registerNode(nodeAPIv1ToRadioOptions);
|
||||
secureServer->registerNode(nodeAPIv1ToRadio);
|
||||
secureServer->registerNode(nodeAPIv1FromRadio);
|
||||
secureServer->registerNode(nodeHotspot);
|
||||
secureServer->registerNode(nodeHotspotApple);
|
||||
secureServer->registerNode(nodeHotspotAndroid);
|
||||
secureServer->registerNode(nodeFavicon);
|
||||
secureServer->registerNode(nodeRoot);
|
||||
secureServer->registerNode(nodeStaticBrowse);
|
||||
@@ -117,7 +119,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
|
||||
insecureServer->registerNode(nodeAPIv1ToRadioOptions);
|
||||
insecureServer->registerNode(nodeAPIv1ToRadio);
|
||||
insecureServer->registerNode(nodeAPIv1FromRadio);
|
||||
insecureServer->registerNode(nodeHotspot);
|
||||
insecureServer->registerNode(nodeHotspotApple);
|
||||
insecureServer->registerNode(nodeHotspotAndroid);
|
||||
insecureServer->registerNode(nodeFavicon);
|
||||
insecureServer->registerNode(nodeRoot);
|
||||
insecureServer->registerNode(nodeStaticBrowse);
|
||||
@@ -931,6 +934,10 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
res->printf("\"is_charging\": %s\n", BoolToString(powerStatus->getIsCharging()));
|
||||
res->println("},");
|
||||
|
||||
res->println("\"device\": {");
|
||||
res->printf("\"reboot_counter\": %d\n", myNodeInfo.reboot_count);
|
||||
res->println("},");
|
||||
|
||||
res->println("\"radio\": {");
|
||||
res->printf("\"frequecy\": %f,\n", RadioLibInterface::instance->getFreq());
|
||||
res->printf("\"lora_channel\": %d\n", RadioLibInterface::instance->getChannelNum());
|
||||
@@ -938,8 +945,6 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
res->println("},");
|
||||
|
||||
|
||||
|
||||
res->println("\"status\": \"ok\"");
|
||||
res->println("}");
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ class HttpAPI : public PhoneAPI
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
// Nothing here yet
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() { return true; } // FIXME, be smarter about this
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -32,11 +32,10 @@ bool isWifiAvailable()
|
||||
{
|
||||
// If wifi status is connected, return true regardless of the radio configuration.
|
||||
if (isSoftAPForced()) {
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
|
||||
// strcpy(radioConfig.preferences.wifi_ssid, "meshtastic");
|
||||
// strcpy(radioConfig.preferences.wifi_password, "meshtastic!");
|
||||
@@ -47,10 +46,10 @@ bool isWifiAvailable()
|
||||
// radioConfig.preferences.wifi_ap_mode = true;
|
||||
// radioConfig.preferences.wifi_ap_mode = false;
|
||||
|
||||
if (*wifiName && *wifiPsw) {
|
||||
return 1;
|
||||
if (*wifiName) {
|
||||
return true;
|
||||
} else {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +95,10 @@ void initWifi(bool forceSoftAP)
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
|
||||
if ((*wifiName && *wifiPsw) || forceSoftAP) {
|
||||
if (!*wifiPsw) // Treat empty password as no password
|
||||
wifiPsw = NULL;
|
||||
|
||||
if (*wifiName || forceSoftAP) {
|
||||
if (forceSoftAP) {
|
||||
|
||||
DEBUG_MSG("Forcing SoftAP\n");
|
||||
@@ -178,8 +180,6 @@ void initWifi(bool forceSoftAP)
|
||||
DEBUG_MSG("Not using WIFI\n");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Called by the Espressif SDK to
|
||||
static void WiFiEvent(WiFiEvent_t event)
|
||||
{
|
||||
@@ -304,12 +304,15 @@ void handleDNSResponse()
|
||||
|
||||
void reconnectWiFi()
|
||||
{
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
|
||||
if (radioConfig.has_preferences) {
|
||||
|
||||
if (*wifiName && *wifiPsw) {
|
||||
const char *wifiName = radioConfig.preferences.wifi_ssid;
|
||||
const char *wifiPsw = radioConfig.preferences.wifi_password;
|
||||
|
||||
if (!*wifiPsw) // Treat empty password as no password
|
||||
wifiPsw = NULL;
|
||||
|
||||
if (*wifiName) {
|
||||
|
||||
DEBUG_MSG("... Reconnecting to WiFi access point");
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc
|
||||
|
||||
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
|
||||
if (!pb_encode(&stream, fields, src_struct)) {
|
||||
DEBUG_MSG("Panic: can't encode protobuf %s, did you make a field too large?\n", PB_GET_ERROR(&stream));
|
||||
DEBUG_MSG("Panic: can't encode protobuf reason='%s', reason=%s\n", PB_GET_ERROR(&stream));
|
||||
assert(0); // If this asser fails it probably means you made a field too large for the max limits specified in mesh.options
|
||||
} else {
|
||||
return stream.bytes_written;
|
||||
@@ -30,7 +30,7 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg
|
||||
{
|
||||
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
|
||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||
DEBUG_MSG("Error: can't decode protobuf %s, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
|
||||
DEBUG_MSG("Error: can't decode protobuf reason='%s', pb_msgdesc 0x%p, reason=%s\n", PB_GET_ERROR(&stream), fields);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "WiFiServerAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@@ -26,32 +25,29 @@ WiFiServerAPI::~WiFiServerAPI()
|
||||
// FIXME - delete this if the client dropps the connection!
|
||||
}
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
void WiFiServerAPI::onConnectionChanged(bool connected)
|
||||
{
|
||||
// FIXME - we really should be doing global reference counting to see if anyone is currently using serial or wifi and if so,
|
||||
// block sleep
|
||||
|
||||
if (connected) { // To prevent user confusion, turn off bluetooth while using the serial port api
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
} else {
|
||||
powerFSM.trigger(EVENT_SERIAL_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/// override close to also shutdown the TCP link
|
||||
void WiFiServerAPI::close() {
|
||||
void WiFiServerAPI::close()
|
||||
{
|
||||
client.stop(); // drop tcp connection
|
||||
StreamAPI::close();
|
||||
}
|
||||
|
||||
bool WiFiServerAPI::loop()
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
bool WiFiServerAPI::checkIsConnected()
|
||||
{
|
||||
return client.connected();
|
||||
}
|
||||
|
||||
int32_t WiFiServerAPI::runOnce()
|
||||
{
|
||||
if (client.connected()) {
|
||||
StreamAPI::loop();
|
||||
return true;
|
||||
return StreamAPI::runOnce();
|
||||
} else {
|
||||
return false;
|
||||
DEBUG_MSG("Client dropped connection, suspending API service\n");
|
||||
enabled = false; // we no longer need to run
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,18 +74,5 @@ int32_t WiFiServerPort::runOnce()
|
||||
openAPI = new WiFiServerAPI(client);
|
||||
}
|
||||
|
||||
if (openAPI) {
|
||||
// Allow idle processing so the API can read from its incoming stream
|
||||
if(!openAPI->loop()) {
|
||||
// If our API link was up, shut it down
|
||||
|
||||
DEBUG_MSG("Client dropped connection, closing API client\n");
|
||||
// Note: we can't call delete here because this object includes other state
|
||||
// besides the stream API. Instead kill it later when we start a new instance
|
||||
delete openAPI;
|
||||
openAPI = NULL;
|
||||
}
|
||||
return 0; // run fast while our API server is running
|
||||
} else
|
||||
return 100; // only check occasionally for incoming connections
|
||||
return 100; // only check occasionally for incoming connections
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "StreamAPI.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include <WiFi.h>
|
||||
|
||||
/**
|
||||
@@ -18,15 +17,18 @@ class WiFiServerAPI : public StreamAPI
|
||||
|
||||
virtual ~WiFiServerAPI();
|
||||
|
||||
/// @return true if we want to keep running, or false if we are ready to be destroyed
|
||||
virtual bool loop(); // Check for dropped client connections
|
||||
|
||||
/// override close to also shutdown the TCP link
|
||||
virtual void close();
|
||||
|
||||
protected:
|
||||
/// Hookable to find out when connection changes
|
||||
virtual void onConnectionChanged(bool connected);
|
||||
|
||||
/// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to stay in the POWERED state to prevent disabling wifi)
|
||||
virtual void onConnectionChanged(bool connected) {}
|
||||
|
||||
virtual int32_t runOnce(); // Check for dropped client connections
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "MQTT.h"
|
||||
#include "NodeDB.h"
|
||||
#include "main.h"
|
||||
#include "mesh/Channels.h"
|
||||
#include "mesh/Router.h"
|
||||
#include "mesh/generated/mqtt.pb.h"
|
||||
#include <WiFi.h>
|
||||
#include <assert.h>
|
||||
|
||||
MQTT *mqtt;
|
||||
|
||||
String statusTopic = "msh/1/stat/";
|
||||
String cryptTopic = "msh/1/c/"; // msh/1/c/CHANNELID/NODEID
|
||||
|
||||
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
|
||||
{
|
||||
mqtt->onPublish(topic, payload, length);
|
||||
}
|
||||
|
||||
void MQTT::onPublish(char *topic, byte *payload, unsigned int length)
|
||||
{
|
||||
// parsing ServiceEnvelope
|
||||
ServiceEnvelope e = ServiceEnvelope_init_default;
|
||||
if (!pb_decode_from_bytes(payload, length, ServiceEnvelope_fields, &e)) {
|
||||
DEBUG_MSG("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length);
|
||||
} else {
|
||||
if (strcmp(e.gateway_id, owner.id) == 0)
|
||||
DEBUG_MSG("Ignoring downlink message we originally sent.\n");
|
||||
else {
|
||||
if (e.packet) {
|
||||
DEBUG_MSG("Received MQTT topic %s, len=%u\n", topic, length);
|
||||
MeshPacket *p = packetPool.allocCopy(*e.packet);
|
||||
|
||||
// ignore messages sent by us or if we don't have the channel key
|
||||
if (router && p->from != nodeDB.getNodeNum() && perhapsDecode(p))
|
||||
router->enqueueReceivedMessage(p);
|
||||
else
|
||||
packetPool.release(p);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure to free both strings and the MeshPacket (passing in NULL is acceptable)
|
||||
free(e.channel_id);
|
||||
free(e.gateway_id);
|
||||
free(e.packet);
|
||||
}
|
||||
}
|
||||
|
||||
void mqttInit()
|
||||
{
|
||||
new MQTT();
|
||||
}
|
||||
|
||||
MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient)
|
||||
{
|
||||
assert(!mqtt);
|
||||
mqtt = this;
|
||||
|
||||
pubSub.setCallback(mqttCallback);
|
||||
}
|
||||
|
||||
void MQTT::reconnect()
|
||||
{
|
||||
// pubSub.setServer("devsrv.ezdevice.net", 1883); or 192.168.10.188
|
||||
const char *serverAddr = "mqtt.meshtastic.org"; // default hostname
|
||||
|
||||
if (*radioConfig.preferences.mqtt_server)
|
||||
serverAddr = radioConfig.preferences.mqtt_server; // Override the default
|
||||
|
||||
pubSub.setServer(serverAddr, 1883);
|
||||
|
||||
DEBUG_MSG("Connecting to MQTT server\n", serverAddr);
|
||||
auto myStatus = (statusTopic + owner.id);
|
||||
bool connected = pubSub.connect(owner.id, "meshdev", "large4cats", myStatus.c_str(), 1, true, "offline");
|
||||
if (connected) {
|
||||
DEBUG_MSG("MQTT connected\n");
|
||||
enabled = true; // Start running background process again
|
||||
runASAP = true;
|
||||
|
||||
/// FIXME, include more information in the status text
|
||||
bool ok = pubSub.publish(myStatus.c_str(), "online", true);
|
||||
DEBUG_MSG("published %d\n", ok);
|
||||
|
||||
sendSubscriptions();
|
||||
} else
|
||||
DEBUG_MSG("Failed to contact MQTT server...\n");
|
||||
}
|
||||
|
||||
void MQTT::sendSubscriptions()
|
||||
{
|
||||
size_t numChan = channels.getNumChannels();
|
||||
for (size_t i = 0; i < numChan; i++) {
|
||||
auto &ch = channels.getByIndex(i);
|
||||
if (ch.settings.downlink_enabled) {
|
||||
String topic = cryptTopic + channels.getGlobalId(i) + "/#";
|
||||
DEBUG_MSG("Subscribing to %s\n", topic.c_str());
|
||||
pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MQTT::wantsLink() const
|
||||
{
|
||||
bool hasChannel = false;
|
||||
|
||||
if (radioConfig.preferences.mqtt_disabled) {
|
||||
// DEBUG_MSG("MQTT disabled...\n");
|
||||
} else {
|
||||
// No need for link if no channel needed it
|
||||
size_t numChan = channels.getNumChannels();
|
||||
for (size_t i = 0; i < numChan; i++) {
|
||||
auto &ch = channels.getByIndex(i);
|
||||
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
|
||||
hasChannel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasChannel && WiFi.isConnected();
|
||||
}
|
||||
|
||||
int32_t MQTT::runOnce()
|
||||
{
|
||||
bool wantConnection = wantsLink();
|
||||
|
||||
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
|
||||
if (!pubSub.loop()) {
|
||||
if (wantConnection) {
|
||||
reconnect();
|
||||
|
||||
// If we succeeded, start reading rapidly, else try again in 30 seconds (TCP connections are EXPENSIVE so try rarely)
|
||||
return pubSub.connected() ? 20 : 30000;
|
||||
} else
|
||||
return 5000; // If we don't want connection now, check again in 5 secs
|
||||
} else {
|
||||
// we are connected to server, check often for new requests on the TCP port
|
||||
if (!wantConnection) {
|
||||
DEBUG_MSG("MQTT link not needed, dropping\n");
|
||||
pubSub.disconnect();
|
||||
}
|
||||
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::onSend(const MeshPacket &mp, ChannelIndex chIndex)
|
||||
{
|
||||
auto &ch = channels.getByIndex(chIndex);
|
||||
|
||||
// don't bother sending if not connected...
|
||||
if (pubSub.connected() && ch.settings.uplink_enabled) {
|
||||
const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel
|
||||
|
||||
ServiceEnvelope env = ServiceEnvelope_init_default;
|
||||
env.channel_id = (char *)channelId;
|
||||
env.gateway_id = owner.id;
|
||||
env.packet = (MeshPacket *)∓
|
||||
|
||||
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
|
||||
static uint8_t bytes[MeshPacket_size + 64];
|
||||
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), ServiceEnvelope_fields, &env);
|
||||
|
||||
String topic = cryptTopic + channelId + "/" + owner.id;
|
||||
DEBUG_MSG("publish %s, %u bytes\n", topic.c_str(), numBytes);
|
||||
|
||||
pubSub.publish(topic.c_str(), bytes, numBytes, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "mesh/Channels.h"
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
/**
|
||||
* Our wrapper/singleton for sending/receiving MQTT "udp" packets. This object isolates the MQTT protocol implementation from
|
||||
* the two components that use it: MQTTPlugin and MQTTSimInterface.
|
||||
*/
|
||||
class MQTT : private concurrency::OSThread
|
||||
{
|
||||
// supposedly the current version is busted:
|
||||
// http://www.iotsharing.com/2017/08/how-to-use-esp32-mqtts-with-mqtts-mosquitto-broker-tls-ssl.html
|
||||
// WiFiClientSecure wifiClient;
|
||||
WiFiClient mqttClient;
|
||||
PubSubClient pubSub;
|
||||
|
||||
public:
|
||||
MQTT();
|
||||
|
||||
/**
|
||||
* Publish a packet on the glboal MQTT server.
|
||||
* This hook must be called **after** the packet is encrypted (including the channel being changed to a hash).
|
||||
* @param chIndex the index of the channel for this message
|
||||
*
|
||||
* Note: for messages we are forwarding on the mesh that we can't find the channel for (because we don't have the keys), we
|
||||
* can not forward those messages to the cloud - becuase no way to find a global channel ID.
|
||||
*/
|
||||
void onSend(const MeshPacket &mp, ChannelIndex chIndex);
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce();
|
||||
|
||||
private:
|
||||
/** return true if we have a channel that wants uplink/downlink
|
||||
*/
|
||||
bool wantsLink() const;
|
||||
|
||||
/** Attempt to connect to server if necessary
|
||||
*/
|
||||
void reconnect();
|
||||
|
||||
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
|
||||
*/
|
||||
void sendSubscriptions();
|
||||
|
||||
/// Just C glue to call onPublish
|
||||
static void mqttCallback(char *topic, byte *payload, unsigned int length);
|
||||
|
||||
/// Called when a new publish arrives from the MQTT server
|
||||
void onPublish(char *topic, byte *payload, unsigned int length);
|
||||
};
|
||||
|
||||
void mqttInit();
|
||||
|
||||
extern MQTT *mqtt;
|
||||
@@ -487,7 +487,6 @@ void reinitBluetooth()
|
||||
DEBUG_MSG("Starting bluetooth\n");
|
||||
if (isFirstTime) {
|
||||
bluetoothPhoneAPI = new BluetoothPhoneAPI();
|
||||
bluetoothPhoneAPI->init();
|
||||
}
|
||||
|
||||
// FIXME - if waking from light sleep, only esp_nimble_hci_init?
|
||||
|
||||
@@ -31,6 +31,10 @@ void BluetoothPhoneAPI::onNowHasData(uint32_t fromRadioNum)
|
||||
}
|
||||
}
|
||||
|
||||
bool BluetoothPhoneAPI::checkIsConnected() {
|
||||
return curConnectionHandle >= 0;
|
||||
}
|
||||
|
||||
int toradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
auto om = ctxt->om;
|
||||
|
||||
@@ -6,10 +6,14 @@ extern uint16_t fromNumValHandle;
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum);
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected();
|
||||
};
|
||||
|
||||
extern PhoneAPI *bluetoothPhoneAPI;
|
||||
@@ -2,10 +2,10 @@
|
||||
#include "BluetoothCommon.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh/PhoneAPI.h"
|
||||
#include "mesh/mesh-pb-constants.h"
|
||||
#include <bluefruit.h>
|
||||
|
||||
|
||||
|
||||
static BLEService meshBleService = BLEService(BLEUuid(MESH_SERVICE_UUID_16));
|
||||
static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16));
|
||||
static BLECharacteristic fromRadio = BLECharacteristic(BLEUuid(FROMRADIO_UUID_16));
|
||||
@@ -21,6 +21,8 @@ static BLEDfu bledfu; // DFU software update helper service
|
||||
static uint8_t fromRadioBytes[FromRadio_size];
|
||||
static uint8_t toRadioBytes[ToRadio_size];
|
||||
|
||||
static bool bleConnected;
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI
|
||||
{
|
||||
/**
|
||||
@@ -33,6 +35,11 @@ class BluetoothPhoneAPI : public PhoneAPI
|
||||
DEBUG_MSG("BLE notify fromNum\n");
|
||||
fromNum.notify32(fromRadioNum);
|
||||
}
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() {
|
||||
return bleConnected;
|
||||
}
|
||||
};
|
||||
|
||||
static BluetoothPhoneAPI *bluetoothPhoneAPI;
|
||||
@@ -46,6 +53,7 @@ void connect_callback(uint16_t conn_handle)
|
||||
connection->getPeerName(central_name, sizeof(central_name));
|
||||
|
||||
DEBUG_MSG("BLE Connected to %s\n", central_name);
|
||||
bleConnected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +63,8 @@ void connect_callback(uint16_t conn_handle)
|
||||
*/
|
||||
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
{
|
||||
(void)conn_handle;
|
||||
// FIXME - we currently assume only one active connection
|
||||
bleConnected = false;
|
||||
|
||||
DEBUG_MSG("BLE Disconnected, reason = 0x%x\n", reason);
|
||||
}
|
||||
@@ -155,7 +164,6 @@ void fromNumAuthorizeCb(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt
|
||||
void setupMeshService(void)
|
||||
{
|
||||
bluetoothPhoneAPI = new BluetoothPhoneAPI();
|
||||
bluetoothPhoneAPI->init();
|
||||
|
||||
meshBleService.begin();
|
||||
|
||||
@@ -213,6 +221,7 @@ void NRF52Bluetooth::setup()
|
||||
{
|
||||
// Initialise the Bluefruit module
|
||||
DEBUG_MSG("Initialise the Bluefruit nRF52 module\n");
|
||||
Bluefruit.autoConnLed(false);
|
||||
Bluefruit.begin();
|
||||
|
||||
// Set the advertised device name (keep it short!)
|
||||
|
||||
+54
-52
@@ -1,7 +1,3 @@
|
||||
#include "NRF52Bluetooth.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "graphics/TFTDisplay.h"
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <assert.h>
|
||||
@@ -9,11 +5,13 @@
|
||||
#include <memory.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef NRF52840_XXAA
|
||||
// #include <nrf52840.h>
|
||||
#endif
|
||||
#include "NRF52Bluetooth.h"
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
|
||||
// #define USE_SOFTDEVICE
|
||||
#ifdef BQ25703A_ADDR
|
||||
#include "BQ25713.h"
|
||||
#endif
|
||||
|
||||
static inline void debugger_break(void)
|
||||
{
|
||||
@@ -21,6 +19,10 @@ static inline void debugger_break(void)
|
||||
"mov pc, lr\n\t");
|
||||
}
|
||||
|
||||
bool loopCanSleep() {
|
||||
return !tud_cdc_connected();
|
||||
}
|
||||
|
||||
// handle standard gcc assert failures
|
||||
void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr)
|
||||
{
|
||||
@@ -33,26 +35,36 @@ void __attribute__((noreturn)) __assert_func(const char *file, int line, const c
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
ble_gap_addr_t addr;
|
||||
if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) {
|
||||
memcpy(dmac, addr.addr, 6);
|
||||
} else {
|
||||
const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR;
|
||||
dmac[5] = src[0];
|
||||
dmac[4] = src[1];
|
||||
dmac[3] = src[2];
|
||||
dmac[2] = src[3];
|
||||
dmac[1] = src[4];
|
||||
dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SOFTDEVICE
|
||||
uint32_t res = sd_ble_gap_addr_get(&addr);
|
||||
assert(res == NRF_SUCCESS);
|
||||
memcpy(dmac, addr.addr, 6);
|
||||
#else
|
||||
const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR;
|
||||
dmac[5] = src[0];
|
||||
dmac[4] = src[1];
|
||||
dmac[3] = src[2];
|
||||
dmac[2] = src[3];
|
||||
dmac[1] = src[4];
|
||||
dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
|
||||
#endif
|
||||
static void initBrownout()
|
||||
{
|
||||
auto vccthresh = POWER_POFCON_THRESHOLD_V17;
|
||||
|
||||
auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled);
|
||||
assert(err_code == NRF_SUCCESS);
|
||||
|
||||
err_code = sd_power_pof_threshold_set(vccthresh);
|
||||
assert(err_code == NRF_SUCCESS);
|
||||
|
||||
// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
|
||||
}
|
||||
|
||||
NRF52Bluetooth *nrf52Bluetooth;
|
||||
|
||||
static bool bleOn = false;
|
||||
static const bool useSoftDevice = false; // Set to false for easier debugging
|
||||
static const bool useSoftDevice = true; // Set to false for easier debugging
|
||||
|
||||
void setBluetoothEnable(bool on)
|
||||
{
|
||||
@@ -64,6 +76,9 @@ void setBluetoothEnable(bool on)
|
||||
else {
|
||||
nrf52Bluetooth = new NRF52Bluetooth();
|
||||
nrf52Bluetooth->setup();
|
||||
|
||||
// We delay brownout init until after BLE because BLE starts soft device
|
||||
initBrownout();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -86,33 +101,14 @@ int printf(const char *fmt, ...)
|
||||
return res;
|
||||
}
|
||||
|
||||
#include "BQ25713.h"
|
||||
|
||||
void initBrownout()
|
||||
{
|
||||
auto vccthresh = POWER_POFCON_THRESHOLD_V28;
|
||||
auto vcchthresh = POWER_POFCON_THRESHOLDVDDH_V27;
|
||||
|
||||
if (useSoftDevice) {
|
||||
auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled);
|
||||
assert(err_code == NRF_SUCCESS);
|
||||
|
||||
err_code = sd_power_pof_threshold_set(vccthresh);
|
||||
assert(err_code == NRF_SUCCESS);
|
||||
}
|
||||
else {
|
||||
NRF_POWER->POFCON = POWER_POFCON_POF_Msk | (vccthresh << POWER_POFCON_THRESHOLD_Pos) | (vcchthresh << POWER_POFCON_THRESHOLDVDDH_Pos);
|
||||
}
|
||||
}
|
||||
|
||||
void checkSDEvents()
|
||||
{
|
||||
if (useSoftDevice) {
|
||||
uint32_t evt;
|
||||
while (NRF_ERROR_NOT_FOUND == sd_evt_get(&evt)) {
|
||||
while (NRF_SUCCESS == sd_evt_get(&evt)) {
|
||||
switch (evt) {
|
||||
case NRF_EVT_POWER_FAILURE_WARNING:
|
||||
recordCriticalError(CriticalErrorCode_Brownout);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_Brownout);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -121,8 +117,8 @@ void checkSDEvents()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(NRF_POWER->EVENTS_POFWARN)
|
||||
recordCriticalError(CriticalErrorCode_Brownout);
|
||||
if (NRF_POWER->EVENTS_POFWARN)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_Brownout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,12 +129,13 @@ void nrf52Loop()
|
||||
|
||||
void nrf52Setup()
|
||||
{
|
||||
|
||||
auto why = NRF_POWER->RESETREAS;
|
||||
// per https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html
|
||||
// per
|
||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html
|
||||
DEBUG_MSG("Reset reason: 0x%x\n", why);
|
||||
|
||||
// Per https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse
|
||||
// Per
|
||||
// https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse
|
||||
// This is the recommended setting for Monitor Mode Debugging
|
||||
NVIC_SetPriority(DebugMonitor_IRQn, 6UL);
|
||||
|
||||
@@ -156,26 +153,31 @@ void nrf52Setup()
|
||||
// randomSeed(r);
|
||||
DEBUG_MSG("FIXME, call randomSeed\n");
|
||||
// ::printf("TESTING PRINTF\n");
|
||||
|
||||
initBrownout();
|
||||
}
|
||||
|
||||
void cpuDeepSleep(uint64_t msecToWake)
|
||||
{
|
||||
// FIXME, configure RTC or button press to wake us
|
||||
// FIXME, power down SPI, I2C, RAMs
|
||||
#ifndef NO_WIRE
|
||||
Wire.end();
|
||||
#endif
|
||||
SPI.end();
|
||||
// This may cause crashes as debug messages continue to flow.
|
||||
Serial.end();
|
||||
Serial1.end();
|
||||
|
||||
#ifdef PIN_SERIAL_RX1
|
||||
Serial1.end();
|
||||
#endif
|
||||
setBluetoothEnable(false);
|
||||
// FIXME, use system off mode with ram retention for key state?
|
||||
// FIXME, use non-init RAM per
|
||||
// https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled
|
||||
|
||||
auto ok = sd_power_system_off();
|
||||
if (ok != NRF_SUCCESS) {
|
||||
DEBUG_MSG("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n");
|
||||
DEBUG_MSG("FIXME: Ignoring soft device (EasyDMA pending?) and forcing "
|
||||
"system-off!\n");
|
||||
NRF_POWER->SYSTEMOFF = 1;
|
||||
}
|
||||
|
||||
|
||||
+40
-17
@@ -6,6 +6,10 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifdef PORTDUINO
|
||||
#include "unistd.h"
|
||||
#endif
|
||||
|
||||
AdminPlugin *adminPlugin;
|
||||
|
||||
void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex)
|
||||
@@ -15,7 +19,7 @@ void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex)
|
||||
AdminMessage r = AdminMessage_init_default;
|
||||
r.get_channel_response = channels.getByIndex(channelIndex);
|
||||
r.which_variant = AdminMessage_get_channel_response_tag;
|
||||
reply = allocDataProtobuf(r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +30,14 @@ void AdminPlugin::handleGetRadio(const MeshPacket &req)
|
||||
AdminMessage r = AdminMessage_init_default;
|
||||
r.get_radio_response = radioConfig;
|
||||
|
||||
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
|
||||
// NOTE: The phone app needs to know the ls_secs & phone_timeout value so it can properly expect sleep behavior.
|
||||
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
|
||||
// using to the app (so that even old phone apps work with new device loads).
|
||||
r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
|
||||
r.get_radio_response.preferences.phone_timeout_secs = getPref_phone_timeout_secs();
|
||||
|
||||
r.which_variant = AdminMessage_get_radio_response_tag;
|
||||
reply = allocDataProtobuf(r);
|
||||
myReply = allocDataProtobuf(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,20 +56,42 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag
|
||||
break;
|
||||
|
||||
case AdminMessage_set_channel_tag:
|
||||
DEBUG_MSG("Client is setting channel\n");
|
||||
handleSetChannel(r->set_channel);
|
||||
DEBUG_MSG("Client is setting channel %d\n", r->set_channel.index);
|
||||
if (r->set_channel.index < 0 || r->set_channel.index >= (int)MAX_NUM_CHANNELS)
|
||||
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
else
|
||||
handleSetChannel(r->set_channel);
|
||||
break;
|
||||
|
||||
case AdminMessage_get_channel_request_tag:
|
||||
DEBUG_MSG("Client is getting channel %d\n", r->get_channel_request - 1);
|
||||
handleGetChannel(mp, r->get_channel_request - 1);
|
||||
case AdminMessage_get_channel_request_tag: {
|
||||
uint32_t i = r->get_channel_request - 1;
|
||||
DEBUG_MSG("Client is getting channel %u\n", i);
|
||||
if (i >= MAX_NUM_CHANNELS)
|
||||
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
|
||||
else
|
||||
handleGetChannel(mp, i);
|
||||
break;
|
||||
}
|
||||
|
||||
case AdminMessage_get_radio_request_tag:
|
||||
DEBUG_MSG("Client is getting radio\n");
|
||||
handleGetRadio(mp);
|
||||
break;
|
||||
|
||||
case AdminMessage_reboot_seconds_tag: {
|
||||
int32_t s = r->reboot_seconds;
|
||||
DEBUG_MSG("Rebooting in %d seconds\n", s);
|
||||
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef PORTDUINO
|
||||
case AdminMessage_exit_simulator_tag:
|
||||
DEBUG_MSG("Exiting simulator\n");
|
||||
_exit(0);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Probably a message sent by us or sent to our local node. FIXME, we should avoid scanning these messages
|
||||
DEBUG_MSG("Ignoring nonrelevant admin %d\n", r->which_variant);
|
||||
@@ -89,6 +116,10 @@ void AdminPlugin::handleSetOwner(const User &o)
|
||||
changed |= strcmp(owner.id, o.id);
|
||||
strcpy(owner.id, o.id);
|
||||
}
|
||||
if (owner.is_licensed != o.is_licensed) {
|
||||
changed = true;
|
||||
owner.is_licensed = o.is_licensed;
|
||||
}
|
||||
|
||||
if (changed) // If nothing really changed, don't broadcast on the network or write to flash
|
||||
service.reloadOwner();
|
||||
@@ -102,8 +133,7 @@ void AdminPlugin::handleSetChannel(const Channel &cc)
|
||||
if (cc.index == 0) {
|
||||
// FIXME, this updates the user preferences also, which isn't needed - we really just want to notify on configChanged
|
||||
service.reloadConfig();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
channels.onConfigChanged(); // tell the radios about this change
|
||||
nodeDB.saveChannelsToDisk();
|
||||
}
|
||||
@@ -116,13 +146,6 @@ void AdminPlugin::handleSetRadio(const RadioConfig &r)
|
||||
service.reloadConfig();
|
||||
}
|
||||
|
||||
MeshPacket *AdminPlugin::allocReply()
|
||||
{
|
||||
auto r = reply;
|
||||
reply = NULL; // Only use each reply once
|
||||
return r;
|
||||
}
|
||||
|
||||
AdminPlugin::AdminPlugin() : ProtobufPlugin("Admin", PortNum_ADMIN_APP, AdminMessage_fields)
|
||||
{
|
||||
// restrict to the admin channel for rx
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
*/
|
||||
class AdminPlugin : public ProtobufPlugin<AdminMessage>
|
||||
{
|
||||
MeshPacket *reply = NULL;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
@@ -21,10 +19,6 @@ class AdminPlugin : public ProtobufPlugin<AdminMessage>
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const AdminMessage *p);
|
||||
|
||||
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
||||
* so that subclasses can (optionally) send a response back to the original sender. */
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
private:
|
||||
void handleSetOwner(const User &o);
|
||||
void handleSetChannel(const Channel &cc);
|
||||
|
||||
@@ -176,5 +176,5 @@ bool ExternalNotificationPlugin::handleReceived(const MeshPacket &mp)
|
||||
|
||||
#endif
|
||||
|
||||
return true; // Let others look at this message also if they want
|
||||
return false; // Very important to never return TRUE here. TRUE means we handled the packet and we will stop letting other plugins see it
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* To developers:
|
||||
* Use this to enable / disable features in your plugin that you don't want to risk checking into GitHub.
|
||||
*
|
||||
*/
|
||||
|
||||
// Enable development more for StoreForwardPlugin
|
||||
bool StoreForward_Dev = false;
|
||||
@@ -10,10 +10,8 @@ PositionPlugin *positionPlugin;
|
||||
PositionPlugin::PositionPlugin()
|
||||
: ProtobufPlugin("position", PortNum_POSITION_APP, Position_fields), concurrency::OSThread("PositionPlugin")
|
||||
{
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
setIntervalFromNow(60 *
|
||||
1000); // Send our initial position 60 seconds after we start (to give GPS time to setup)
|
||||
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
setIntervalFromNow(60 * 1000); // Send our initial position 60 seconds after we start (to give GPS time to setup)
|
||||
}
|
||||
|
||||
bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position *pptr)
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
|
||||
// Because (FIXME) we currently don't tell API clients status on sent messages
|
||||
// we need to throttle our sending, so that if a gpio is bouncing up and down we
|
||||
// don't generate more messages than the net can send. So we limit watch messages to
|
||||
// don't generate more messages than the net can send. So we limit watch messages to
|
||||
// a max of one change per 30 seconds
|
||||
#define WATCH_INTERVAL_MSEC (30 * 1000)
|
||||
|
||||
/// Set pin modes for every set bit in a mask
|
||||
static void pinModes(uint64_t mask, uint8_t mode) {
|
||||
static void pinModes(uint64_t mask, uint8_t mode)
|
||||
{
|
||||
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
pinMode(i, mode);
|
||||
@@ -24,7 +25,8 @@ static void pinModes(uint64_t mask, uint8_t mode) {
|
||||
}
|
||||
|
||||
/// Read all the pins mentioned in a mask
|
||||
static uint64_t digitalReads(uint64_t mask) {
|
||||
static uint64_t digitalReads(uint64_t mask)
|
||||
{
|
||||
uint64_t res = 0;
|
||||
|
||||
pinModes(mask, INPUT_PULLUP);
|
||||
@@ -40,10 +42,9 @@ static uint64_t digitalReads(uint64_t mask) {
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
RemoteHardwarePlugin::RemoteHardwarePlugin()
|
||||
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields),
|
||||
concurrency::OSThread("remotehardware")
|
||||
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields), concurrency::OSThread(
|
||||
"remotehardware")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -69,26 +70,26 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
|
||||
|
||||
case HardwareMessage_Type_READ_GPIOS: {
|
||||
// Print notification to LCD screen
|
||||
if(screen)
|
||||
if (screen)
|
||||
screen->print("Read GPIOs\n");
|
||||
|
||||
uint64_t res = digitalReads(p.gpio_mask);
|
||||
|
||||
// Send the reply
|
||||
HardwareMessage reply = HardwareMessage_init_default;
|
||||
reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
|
||||
reply.gpio_value = res;
|
||||
MeshPacket *p = allocDataProtobuf(reply);
|
||||
HardwareMessage r = HardwareMessage_init_default;
|
||||
r.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
|
||||
r.gpio_value = res;
|
||||
MeshPacket *p = allocDataProtobuf(r);
|
||||
setReplyTo(p, req);
|
||||
service.sendToMesh(p);
|
||||
myReply = p;
|
||||
break;
|
||||
}
|
||||
|
||||
case HardwareMessage_Type_WATCH_GPIOS: {
|
||||
watchGpios = p.gpio_mask;
|
||||
lastWatchMsec = 0; // Force a new publish soon
|
||||
lastWatchMsec = 0; // Force a new publish soon
|
||||
previousWatch = ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish)
|
||||
enabled = true; // Let our thread run at least once
|
||||
enabled = true; // Let our thread run at least once
|
||||
DEBUG_MSG("Now watching GPIOs 0x%llx\n", watchGpios);
|
||||
break;
|
||||
}
|
||||
@@ -101,31 +102,31 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
|
||||
DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ);
|
||||
break;
|
||||
}
|
||||
|
||||
return true; // handled
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t RemoteHardwarePlugin::runOnce() {
|
||||
if(watchGpios) {
|
||||
int32_t RemoteHardwarePlugin::runOnce()
|
||||
{
|
||||
if (watchGpios) {
|
||||
uint32_t now = millis();
|
||||
|
||||
if(now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
|
||||
if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
|
||||
uint64_t curVal = digitalReads(watchGpios);
|
||||
|
||||
if(curVal != previousWatch) {
|
||||
if (curVal != previousWatch) {
|
||||
previousWatch = curVal;
|
||||
DEBUG_MSG("Broadcasting GPIOS 0x%llx changed!\n", curVal);
|
||||
|
||||
// Something changed! Tell the world with a broadcast message
|
||||
HardwareMessage reply = HardwareMessage_init_default;
|
||||
reply.typ = HardwareMessage_Type_GPIOS_CHANGED;
|
||||
reply.gpio_value = curVal;
|
||||
MeshPacket *p = allocDataProtobuf(reply);
|
||||
HardwareMessage r = HardwareMessage_init_default;
|
||||
r.typ = HardwareMessage_Type_GPIOS_CHANGED;
|
||||
r.gpio_value = curVal;
|
||||
MeshPacket *p = allocDataProtobuf(r);
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// No longer watching anything - stop using CPU
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
@@ -18,17 +18,16 @@ bool RoutingPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Routing *
|
||||
printPacket("Delivering rx packet", &mp);
|
||||
service.handleFromRadio(&mp);
|
||||
}
|
||||
|
||||
|
||||
return false; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
|
||||
MeshPacket *RoutingPlugin::allocReply()
|
||||
{
|
||||
assert(currentRequest);
|
||||
|
||||
// We only consider making replies if the request was a legit routing packet (not just something we were sniffing)
|
||||
if(currentRequest->decoded.portnum == PortNum_ROUTING_APP) {
|
||||
if (currentRequest->decoded.portnum == PortNum_ROUTING_APP) {
|
||||
assert(0); // 1.2 refactoring fixme, Not sure if anything needs this yet?
|
||||
// return allocDataProtobuf(u);
|
||||
}
|
||||
@@ -37,26 +36,12 @@ MeshPacket *RoutingPlugin::allocReply()
|
||||
|
||||
void RoutingPlugin::sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex)
|
||||
{
|
||||
Routing c = Routing_init_default;
|
||||
|
||||
c.error_reason = err;
|
||||
|
||||
auto p = allocDataProtobuf(c);
|
||||
p->priority = MeshPacket_Priority_ACK;
|
||||
|
||||
p->hop_limit = 0; // Assume just immediate neighbors for now
|
||||
p->to = to;
|
||||
p->decoded.request_id = idFrom;
|
||||
p->channel = chIndex;
|
||||
DEBUG_MSG("Sending an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
|
||||
auto p = allocAckNak(err, to, idFrom, chIndex);
|
||||
|
||||
router->sendLocal(p); // we sometimes send directly to the local node
|
||||
}
|
||||
|
||||
RoutingPlugin::RoutingPlugin()
|
||||
: ProtobufPlugin("routing", PortNum_ROUTING_APP, Routing_fields)
|
||||
RoutingPlugin::RoutingPlugin() : ProtobufPlugin("routing", PortNum_ROUTING_APP, Routing_fields)
|
||||
{
|
||||
isPromiscuous = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -186,9 +186,19 @@ bool SerialPluginRadio::handleReceived(const MeshPacket &mp)
|
||||
}
|
||||
|
||||
} else {
|
||||
// DEBUG_MSG("* * Message came from the mesh\n");
|
||||
// Serial2.println("* * Message came from the mesh");
|
||||
Serial2.printf("%s", p.payload.bytes);
|
||||
|
||||
if (radioConfig.preferences.serialplugin_mode == 0 || radioConfig.preferences.serialplugin_mode == 1) {
|
||||
// DEBUG_MSG("* * Message came from the mesh\n");
|
||||
// Serial2.println("* * Message came from the mesh");
|
||||
Serial2.printf("%s", p.payload.bytes);
|
||||
|
||||
} else if (radioConfig.preferences.serialplugin_mode == 10) {
|
||||
/*
|
||||
@jobionekabnoi
|
||||
Add code here to handle what gets sent out to the serial interface.
|
||||
Format it the way you want.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -35,9 +35,9 @@ int32_t RangeTestPlugin::runOnce()
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
*/
|
||||
|
||||
//radioConfig.preferences.range_test_plugin_enabled = 1;
|
||||
//radioConfig.preferences.range_test_plugin_sender = 45;
|
||||
//radioConfig.preferences.range_test_plugin_save = 1;
|
||||
// radioConfig.preferences.range_test_plugin_enabled = 1;
|
||||
// radioConfig.preferences.range_test_plugin_sender = 45;
|
||||
// radioConfig.preferences.range_test_plugin_save = 1;
|
||||
|
||||
// Fixed position is useful when testing indoors.
|
||||
// radioConfig.preferences.fixed_position = 1;
|
||||
@@ -112,7 +112,7 @@ void RangeTestPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
packetSequence++;
|
||||
|
||||
static char heartbeatString[20];
|
||||
snprintf(heartbeatString, sizeof(heartbeatString), "seq %d", packetSequence);
|
||||
snprintf(heartbeatString, sizeof(heartbeatString), "seq %u", packetSequence);
|
||||
|
||||
p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
|
||||
memcpy(p->decoded.payload.bytes, heartbeatString, p->decoded.payload.size);
|
||||
@@ -255,7 +255,9 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx snr,distance,payload")) {
|
||||
// Print the CSV header
|
||||
if (fileToWrite.println(
|
||||
"time,from,sender name,sender lat,sender long,rx lat,rx long,rx snr,rx elevation,distance,payload")) {
|
||||
DEBUG_MSG("File was written\n");
|
||||
} else {
|
||||
DEBUG_MSG("File write failed\n");
|
||||
@@ -290,13 +292,15 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
|
||||
fileToAppend.printf("??:??:??,"); // Time
|
||||
}
|
||||
|
||||
fileToAppend.printf("%d,", getFrom(&mp)); // From
|
||||
fileToAppend.printf("%d,", getFrom(&mp)); // From
|
||||
fileToAppend.printf("%s,", n->user.long_name); // Long Name
|
||||
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
|
||||
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
|
||||
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
|
||||
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
|
||||
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
|
||||
fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
|
||||
|
||||
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
|
||||
|
||||
if (n->position.latitude_i && n->position.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) {
|
||||
float distance = latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7,
|
||||
|
||||
@@ -5,92 +5,40 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "plugins/PluginDev.h"
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
|
||||
#define STOREFORWARD_MAX_PACKETS 7500
|
||||
#define STOREFORWARD_SEND_HISTORY_SHORT 600
|
||||
#define STOREFORWARD_MAX_PACKETS 0
|
||||
#define STOREFORWARD_SEND_HISTORY_PERIOD 10 * 60
|
||||
#define STOREFORWARD_SEND_HISTORY_MAX 0
|
||||
|
||||
StoreForwardPlugin *storeForwardPlugin;
|
||||
StoreForwardPluginRadio *storeForwardPluginRadio;
|
||||
|
||||
StoreForwardPlugin::StoreForwardPlugin() : concurrency::OSThread("StoreForwardPlugin") {}
|
||||
|
||||
int32_t StoreForwardPlugin::runOnce()
|
||||
{
|
||||
|
||||
#ifndef NO_ESP32
|
||||
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the plugin
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
|
||||
attn @mc-hamster I moved this back inside the comment because I don't think it was intended to checkin. It was forcing all
|
||||
nodes to be running this and turning off is_router.
|
||||
|
||||
radioConfig.preferences.store_forward_plugin_enabled = 1;
|
||||
radioConfig.preferences.is_router = 0;
|
||||
*/
|
||||
|
||||
if (radioConfig.preferences.store_forward_plugin_enabled) {
|
||||
|
||||
if (firstTime) {
|
||||
|
||||
firstTime = 0;
|
||||
|
||||
if (radioConfig.preferences.is_router) {
|
||||
DEBUG_MSG("Initializing Store & Forward Plugin - Enabled as Router\n");
|
||||
// Router
|
||||
if (ESP.getPsramSize()) {
|
||||
if (ESP.getFreePsram() >= 2048 * 1024) {
|
||||
// Do the startup here
|
||||
storeForwardPluginRadio = new StoreForwardPluginRadio();
|
||||
|
||||
this->populatePSRAM();
|
||||
|
||||
// packetHistory[0].bytes;
|
||||
return (10 * 1000);
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Device has less than 2M of PSRAM free. Aborting startup.\n");
|
||||
DEBUG_MSG("Store & Forward Plugin - Aborting Startup.\n");
|
||||
|
||||
return (INT32_MAX);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Device doesn't have PSRAM.\n");
|
||||
DEBUG_MSG("Store & Forward Plugin - Aborting Startup.\n");
|
||||
|
||||
return (INT32_MAX);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Initializing Store & Forward Plugin - Enabled as Client\n");
|
||||
return (5 * 1000);
|
||||
}
|
||||
|
||||
if (radioConfig.preferences.is_router) {
|
||||
// Maybe some cleanup functions?
|
||||
this->historyReport();
|
||||
return (60 * 1000);
|
||||
} else {
|
||||
/*
|
||||
* If the plugin is turned on and is_router is not enabled, then we'll send a heartbeat every
|
||||
* few minutes.
|
||||
*
|
||||
* This behavior is expected to change. It's only here until we come up with something better.
|
||||
*/
|
||||
|
||||
if (radioConfig.preferences.is_router) {
|
||||
// Maybe some cleanup functions?
|
||||
this->sawNodeReport();
|
||||
this->historyReport();
|
||||
return (10 * 1000);
|
||||
} else {
|
||||
/*
|
||||
* If the plugin is turned on and is_router is not enabled, then we'll send a heartbeat every
|
||||
* few minutes.
|
||||
*/
|
||||
DEBUG_MSG("Store & Forward Plugin - Sending heartbeat\n");
|
||||
|
||||
DEBUG_MSG("Store & Forward Plugin - Sending heartbeat\n");
|
||||
storeForwardPlugin->sendPayload();
|
||||
|
||||
// storeForwardPluginRadio->sendPayloadHeartbeat();
|
||||
if(storeForwardPluginRadio)
|
||||
storeForwardPluginRadio->sendPayload();
|
||||
|
||||
return (1 * 60 * 1000);
|
||||
}
|
||||
return (4 * 60 * 1000);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -110,62 +58,41 @@ void StoreForwardPlugin::populatePSRAM()
|
||||
https://learn.upesy.com/en/programmation/psram.html#psram-tab
|
||||
*/
|
||||
|
||||
DEBUG_MSG("Before PSRAM initilization\n");
|
||||
DEBUG_MSG("Before PSRAM initilization:\n");
|
||||
|
||||
DEBUG_MSG("Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG("Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
|
||||
// PacketHistoryStruct *packetHistory = (PacketHistoryStruct *)ps_calloc(STOREFORWARD_MAX_PACKETS,
|
||||
// sizeof(PacketHistoryStruct));
|
||||
this->packetHistory = (PacketHistoryStruct *)ps_calloc(STOREFORWARD_MAX_PACKETS, sizeof(PacketHistoryStruct));
|
||||
DEBUG_MSG("After PSRAM initilization\n");
|
||||
// Use a maximum of half the available PSRAM unless otherwise specified.
|
||||
uint32_t numberOfPackets =
|
||||
STOREFORWARD_MAX_PACKETS ? STOREFORWARD_MAX_PACKETS : ((ESP.getPsramSize() / 2) / sizeof(PacketHistoryStruct));
|
||||
|
||||
DEBUG_MSG("Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG("Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG("Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG("Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
// this->packetHistory = (PacketHistoryStruct *)ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct));
|
||||
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)));
|
||||
DEBUG_MSG("After PSRAM initilization:\n");
|
||||
|
||||
DEBUG_MSG("packetHistory Size - %u", sizeof(packetHistory));
|
||||
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
DEBUG_MSG("Store and Forward Stats:\n");
|
||||
DEBUG_MSG(" numberOfPackets - %u\n", numberOfPackets);
|
||||
}
|
||||
|
||||
// We saw a node.
|
||||
uint32_t StoreForwardPlugin::sawNode(uint32_t node)
|
||||
void StoreForwardPlugin::sawNode(uint32_t whoWeSaw, uint32_t sawSecAgo)
|
||||
{
|
||||
if (radioConfig.preferences.is_router) {
|
||||
|
||||
/*
|
||||
TODO: Move receivedRecord into the PSRAM
|
||||
|
||||
TODO: Gracefully handle the case where we run out of records.
|
||||
Maybe replace the oldest record that hasn't been seen in a while and assume they won't be back.
|
||||
|
||||
TODO: Implment this as a std::map for quicker lookups (maybe it doesn't matter?).
|
||||
*/
|
||||
// DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff,
|
||||
// p->want_ack, p->hop_limit);
|
||||
DEBUG_MSG("looking for node - from-0x%08x\n", node);
|
||||
for (int i = 0; i < 50; i++) {
|
||||
// DEBUG_MSG("Iterating through the seen nodes - %u %u %u\n", i, receivedRecord[i][0], receivedRecord[i][1]);
|
||||
// First time seeing that node.
|
||||
if (receivedRecord[i][0] == 0) {
|
||||
// DEBUG_MSG("New node! Woohoo! Win!\n");
|
||||
receivedRecord[i][0] = node;
|
||||
receivedRecord[i][1] = millis();
|
||||
|
||||
return receivedRecord[i][1];
|
||||
}
|
||||
|
||||
// We've seen this node before.
|
||||
if (receivedRecord[i][0] == node) {
|
||||
// DEBUG_MSG("We've seen this node before\n");
|
||||
uint32_t lastSaw = receivedRecord[i][1];
|
||||
receivedRecord[i][1] = millis();
|
||||
return lastSaw;
|
||||
// If node has been away for more than 10 minutes, send the node the last 10 minutes of
|
||||
// messages
|
||||
if (sawSecAgo > STOREFORWARD_SEND_HISTORY_PERIOD) {
|
||||
// Node has been away for a while.
|
||||
storeForwardPlugin->historySend(STOREFORWARD_SEND_HISTORY_PERIOD, whoWeSaw);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StoreForwardPlugin::historyReport()
|
||||
@@ -180,8 +107,15 @@ void StoreForwardPlugin::historyReport()
|
||||
}
|
||||
DEBUG_MSG("StoreForwardPlugin::historyReport runtime - %u ms\n", millis() - startTimer);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void StoreForwardPlugin::historySend(uint32_t msAgo, uint32_t to)
|
||||
{
|
||||
// Send "Welcome back"
|
||||
this->sendPayloadWelcome(to, false);
|
||||
|
||||
for (int i = 0; i < this->packetHistoryCurrent; i++) {
|
||||
if (this->packetHistory[i].time) {
|
||||
// DEBUG_MSG("... time-%u to-0x%08x\n", this->packetHistory[i].time, this->packetHistory[i].to & 0xffffffff);
|
||||
@@ -207,111 +141,74 @@ void StoreForwardPlugin::historyAdd(const MeshPacket *mp)
|
||||
this->packetHistoryCurrent++;
|
||||
}
|
||||
|
||||
// We saw a node.
|
||||
void StoreForwardPlugin::sawNodeReport()
|
||||
{
|
||||
|
||||
/*
|
||||
TODO: Move receivedRecord into the PSRAM
|
||||
|
||||
TODO: Gracefully handle the case where we run out of records.
|
||||
Maybe replace the oldest record that hasn't been seen in a while and assume they won't be back.
|
||||
|
||||
TODO: Implment this as a std::map for quicker lookups (maybe it doesn't matter?).
|
||||
*/
|
||||
|
||||
DEBUG_MSG("Iterating through the seen nodes in receivedRecord...\n");
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (receivedRecord[i][1]) {
|
||||
DEBUG_MSG("... record-%u from-0x%08x secAgo-%u\n", i, receivedRecord[i][0], (millis() - receivedRecord[i][1]) / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MeshPacket *StoreForwardPluginRadio::allocReply()
|
||||
MeshPacket *StoreForwardPlugin::allocReply()
|
||||
{
|
||||
auto reply = allocDataPacket(); // Allocate a packet for sending
|
||||
return reply; // attn @mc-hamster this code was commented out and was causing memory corruption
|
||||
return reply;
|
||||
}
|
||||
|
||||
void StoreForwardPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
void StoreForwardPlugin::sendPayload(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
/*
|
||||
MeshPacket *p = this->allocReply(); // attn @mc-hamster, I moved inside the commented block to prevent leaking memory
|
||||
DEBUG_MSG("Sending S&F Payload\n");
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = true;
|
||||
*/
|
||||
// static char heartbeatString[20];
|
||||
// snprintf(heartbeatString, sizeof(heartbeatString), "1");
|
||||
|
||||
// p->decoded.data.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
|
||||
// memcpy(p->decoded.data.payload.bytes, "1", 1);
|
||||
static char heartbeatString[20];
|
||||
snprintf(heartbeatString, sizeof(heartbeatString), "1");
|
||||
|
||||
// service.sendToMesh(p);
|
||||
}
|
||||
|
||||
void StoreForwardPluginRadio::sendPayloadHeartbeat(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
DEBUG_MSG("Sending S&F Heartbeat\n");
|
||||
MeshPacket *p = this->allocReply();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
|
||||
memcpy(p->decoded.payload.bytes, "1", 1);
|
||||
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
|
||||
bool StoreForwardPluginRadio::handleReceived(const MeshPacket &mp)
|
||||
void StoreForwardPlugin::sendPayloadWelcome(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
DEBUG_MSG("*********************************\n");
|
||||
DEBUG_MSG("*********************************\n");
|
||||
DEBUG_MSG("*********************************\n");
|
||||
DEBUG_MSG("Sending S&F Welcome Message\n");
|
||||
DEBUG_MSG("*********************************\n");
|
||||
DEBUG_MSG("*********************************\n");
|
||||
DEBUG_MSG("*********************************\n");
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = true;
|
||||
|
||||
p->decoded.portnum = PortNum_TEXT_MESSAGE_APP;
|
||||
|
||||
static char heartbeatString[80];
|
||||
snprintf(heartbeatString, sizeof(heartbeatString), "Welcome back to the mesh. We have not seen you in x minutes!");
|
||||
|
||||
p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
|
||||
memcpy(p->decoded.payload.bytes, heartbeatString, p->decoded.payload.size);
|
||||
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
|
||||
bool StoreForwardPlugin::handleReceived(const MeshPacket &mp)
|
||||
{
|
||||
#ifndef NO_ESP32
|
||||
if (radioConfig.preferences.store_forward_plugin_enabled) {
|
||||
|
||||
if (getFrom(&mp) != nodeDB.getNodeNum()) {
|
||||
// DEBUG_MSG("Store & Forward Plugin -- Print Start ---------- ---------- ---------- ---------- ----------\n\n\n");
|
||||
// DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d", prefix, p->id, p->from & 0xff, p->to & 0xff,
|
||||
// p->want_ack, p->hop_limit);
|
||||
printPacket("----- PACKET FROM RADIO -----", &mp);
|
||||
uint32_t sawTime = storeForwardPlugin->sawNode(getFrom(&mp) & 0xffffffff);
|
||||
DEBUG_MSG("We last saw this node (%u), %u sec ago\n", mp.from & 0xffffffff, (millis() - sawTime) / 1000);
|
||||
|
||||
if (mp.decoded.portnum == PortNum_UNKNOWN_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_UNKNOWN_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
|
||||
// uint32_t sawTime = storeForwardPlugin->sawNode(getFrom(&mp) & 0xffffffff);
|
||||
// DEBUG_MSG("We last saw this node (%u), %u sec ago\n", mp.from & 0xffffffff, (millis() - sawTime) / 1000);
|
||||
DEBUG_MSG(" -------------- ");
|
||||
if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n");
|
||||
|
||||
storeForwardPlugin->historyAdd(&mp);
|
||||
|
||||
} else if (mp.decoded.portnum == PortNum_REMOTE_HARDWARE_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_REMOTE_HARDWARE_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_POSITION_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_POSITION_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_NODEINFO_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_NODEINFO_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_REPLY_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_REPLY_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_IP_TUNNEL_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_IP_TUNNEL_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_SERIAL_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_SERIAL_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_STORE_FORWARD_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_RANGE_TEST_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_RANGE_TEST_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_PRIVATE_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_PRIVATE_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_RANGE_TEST_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_RANGE_TEST_APP\n");
|
||||
} else if (mp.decoded.portnum == PortNum_ATAK_FORWARDER) {
|
||||
DEBUG_MSG("Packet came from - PortNum_ATAK_FORWARDER\n");
|
||||
} else {
|
||||
DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum);
|
||||
}
|
||||
|
||||
if ((millis() - sawTime) > STOREFORWARD_SEND_HISTORY_SHORT) {
|
||||
// Node has been away for a while.
|
||||
storeForwardPlugin->historySend(sawTime, mp.from);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -322,3 +219,50 @@ bool StoreForwardPluginRadio::handleReceived(const MeshPacket &mp)
|
||||
|
||||
return true; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
StoreForwardPlugin::StoreForwardPlugin()
|
||||
: SinglePortPlugin("StoreForwardPlugin", PortNum_STORE_FORWARD_APP), concurrency::OSThread("StoreForwardPlugin")
|
||||
{
|
||||
|
||||
#ifndef NO_ESP32
|
||||
|
||||
isPromiscuous = true; // Brown chicken brown cow
|
||||
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the plugin
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
*/
|
||||
|
||||
if (StoreForward_Dev) {
|
||||
radioConfig.preferences.store_forward_plugin_enabled = 1;
|
||||
radioConfig.preferences.is_router = 1;
|
||||
}
|
||||
|
||||
if (radioConfig.preferences.store_forward_plugin_enabled) {
|
||||
|
||||
// Router
|
||||
if (radioConfig.preferences.is_router) {
|
||||
DEBUG_MSG("Initializing Store & Forward Plugin - Enabled as Router\n");
|
||||
if (ESP.getPsramSize()) {
|
||||
if (ESP.getFreePsram() >= 1024 * 1024) {
|
||||
|
||||
// Do the startup here
|
||||
|
||||
this->populatePSRAM();
|
||||
} else {
|
||||
DEBUG_MSG("Device has less than 1M of PSRAM free. Aborting startup.\n");
|
||||
DEBUG_MSG("Store & Forward Plugin - Aborting Startup.\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Device doesn't have PSRAM.\n");
|
||||
DEBUG_MSG("Store & Forward Plugin - Aborting Startup.\n");
|
||||
}
|
||||
|
||||
// Client
|
||||
} else {
|
||||
DEBUG_MSG("Initializing Store & Forward Plugin - Enabled as Client\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
|
||||
struct PacketHistoryStruct {
|
||||
uint32_t time;
|
||||
uint32_t to;
|
||||
@@ -14,7 +13,7 @@ struct PacketHistoryStruct {
|
||||
uint8_t bytes[MAX_RHPACKETLEN];
|
||||
};
|
||||
|
||||
class StoreForwardPlugin : private concurrency::OSThread
|
||||
class StoreForwardPlugin : public SinglePortPlugin, private concurrency::OSThread
|
||||
{
|
||||
bool firstTime = 1;
|
||||
|
||||
@@ -30,48 +29,25 @@ class StoreForwardPlugin : private concurrency::OSThread
|
||||
Update our local reference of when we last saw that node.
|
||||
@return 0 if we have never seen that node before otherwise return the last time we saw the node.
|
||||
*/
|
||||
uint32_t sawNode(uint32_t);
|
||||
void sawNodeReport();
|
||||
void sawNode(uint32_t whoWeSaw, uint32_t sawSecAgo);
|
||||
void historyAdd(const MeshPacket *mp);
|
||||
void historyReport();
|
||||
void historySend(uint32_t msAgo, uint32_t to);
|
||||
void populatePSRAM();
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
void sendPayloadWelcome(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
virtual MeshPacket *allocReply();
|
||||
virtual bool wantPortnum(PortNum p) { return true; };
|
||||
|
||||
private:
|
||||
// Nothing here
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern StoreForwardPlugin *storeForwardPlugin;
|
||||
|
||||
/*
|
||||
* Radio interface for StoreForwardPlugin
|
||||
*
|
||||
*/
|
||||
class StoreForwardPluginRadio : public SinglePortPlugin
|
||||
{
|
||||
// uint32_t lastRxID;
|
||||
|
||||
public:
|
||||
StoreForwardPluginRadio() : SinglePortPlugin("StoreForwardPluginRadio", PortNum_STORE_FORWARD_APP) {}
|
||||
// StoreForwardPluginRadio() : SinglePortPlugin("StoreForwardPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayloadHeartbeat(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
protected:
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
virtual bool wantPortnum(PortNum p) { return true; };
|
||||
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
@@ -80,4 +56,27 @@ class StoreForwardPluginRadio : public SinglePortPlugin
|
||||
virtual bool handleReceived(const MeshPacket &mp);
|
||||
};
|
||||
|
||||
extern StoreForwardPlugin *storeForwardPlugin;
|
||||
|
||||
/*
|
||||
* Radio interface for StoreForwardPlugin
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
class StoreForwardPluginRadio : public SinglePortPlugin
|
||||
{
|
||||
// uint32_t lastRxID;
|
||||
|
||||
public:
|
||||
StoreForwardPluginRadio() : SinglePortPlugin("StoreForwardPluginRadio", PortNum_STORE_FORWARD_APP) {}
|
||||
// StoreForwardPluginRadio() : SinglePortPlugin("StoreForwardPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
|
||||
|
||||
void sendPayloadHeartbeat(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
protected:
|
||||
virtual MeshPacket *allocReply2();
|
||||
};
|
||||
|
||||
extern StoreForwardPluginRadio *storeForwardPluginRadio;
|
||||
*/
|
||||
|
||||
@@ -1,97 +1,104 @@
|
||||
#include "CryptoEngine.h"
|
||||
#include "target_specific.h"
|
||||
#include "PortduinoGPIO.h"
|
||||
#include "SPIChip.h"
|
||||
#include "mesh/RF95Interface.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
#include <Utility.h>
|
||||
#include <assert.h>
|
||||
#include <linux/gpio/LinuxGPIOPin.h>
|
||||
|
||||
// FIXME - move getMacAddr/setBluetoothEnable into a HALPlatform class
|
||||
|
||||
uint32_t hwId; // fixme move into portduino
|
||||
|
||||
void getMacAddr(uint8_t *dmac)
|
||||
{
|
||||
if (!hwId) {
|
||||
notImplemented("getMacAddr");
|
||||
hwId = random();
|
||||
}
|
||||
|
||||
dmac[0] = 0x80;
|
||||
dmac[1] = 0;
|
||||
dmac[2] = 0;
|
||||
dmac[3] = hwId >> 16;
|
||||
dmac[4] = hwId >> 8;
|
||||
dmac[5] = hwId & 0xff;
|
||||
}
|
||||
// FIXME - move setBluetoothEnable into a HALPlatform class
|
||||
|
||||
void setBluetoothEnable(bool on)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
void cpuDeepSleep(uint64_t msecs) {
|
||||
void cpuDeepSleep(uint64_t msecs)
|
||||
{
|
||||
notImplemented("cpuDeepSleep");
|
||||
}
|
||||
|
||||
void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel");
|
||||
|
||||
/** Dear pinetab hardware geeks!
|
||||
*
|
||||
* The current pinetab lora module has a slight bug. The ch341 part only provides ISR assertions on edges.
|
||||
* This makes sense because USB interrupts happen through fast/repeated special irq urbs that are constantly
|
||||
* chattering on the USB bus.
|
||||
*
|
||||
* But this isn't sufficient for level triggered ISR sources like the sx127x radios. The common way that seems to
|
||||
* be addressed by cs341 users is to **always** connect the INT# (pin 26 on the ch341f) signal to one of the GPIO signals
|
||||
* on the part. I'd recommend connecting that LORA_DIO0/INT# line to pin 19 (data 4) on the pinetab board. This would
|
||||
* provide an efficent mechanism so that the (kernel) code in the cs341 driver that I've slightly hacked up to see the
|
||||
* current state of LORA_DIO0. Without that access, I can't know if the interrupt is still pending - which would create
|
||||
* race conditions in packet handling.
|
||||
*
|
||||
* My workaround is to poll the status register internally to the sx127x. Which is expensive because it involves a number of
|
||||
* i2c transactions and many trips back and forth between kernel and my userspace app. I think shipping the current version
|
||||
* of the pinetab lora device would be fine because I can poll slowly (because lora is slow). But if you ever have cause to
|
||||
* rev this board. I highly encourage this small change.
|
||||
*
|
||||
* Btw - your little "USB lora dongle" is really neat. I encourage you to sell it, because even non pinetab customers could
|
||||
* use it to easily add lora to rasberry pi, desktop pcs etc...
|
||||
*
|
||||
|
||||
/** a simulated pin for busted IRQ hardware
|
||||
* Porduino helper class to do this i2c based polling:
|
||||
*/
|
||||
class R595PolledIrqPin : public GPIOPin {
|
||||
public:
|
||||
R595PolledIrqPin() : GPIOPin(LORA_DIO0, "LORA_DIO0") {}
|
||||
class PolledIrqPin : public GPIOPin
|
||||
{
|
||||
public:
|
||||
PolledIrqPin() : GPIOPin(LORA_DIO1, "loraIRQ") {}
|
||||
|
||||
/// Read the low level hardware for this pin
|
||||
virtual PinStatus readPinHardware()
|
||||
{
|
||||
if(isrPinStatus < 0)
|
||||
if (isrPinStatus < 0)
|
||||
return LOW; // No interrupt handler attached, don't bother polling i2c right now
|
||||
else {
|
||||
extern RadioInterface *rIf; // FIXME, temporary hack until we know if we need to keep this
|
||||
extern RadioInterface *rIf; // FIXME, temporary hack until we know if we need to keep this
|
||||
|
||||
assert(rIf);
|
||||
RF95Interface *rIf95 = static_cast<RF95Interface *>(rIf);
|
||||
RadioLibInterface *rIf95 = static_cast<RadioLibInterface *>(rIf);
|
||||
bool p = rIf95->isIRQPending();
|
||||
// log(SysGPIO, LogDebug, "R595PolledIrqPin::readPinHardware(%s, %d, %d)", getName(), getPinNum(), p);
|
||||
log(SysGPIO, LogDebug, "PolledIrqPin::readPinHardware(%s, %d, %d)", getName(), getPinNum(), p);
|
||||
return p ? HIGH : LOW;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static GPIOPin *loraIrq;
|
||||
|
||||
/** apps run under portduino can optionally define a portduinoSetup() to
|
||||
/** apps run under portduino can optionally define a portduinoSetup() to
|
||||
* use portduino specific init code (such as gpioBind) to setup portduino on their host machine,
|
||||
* before running 'arduino' code.
|
||||
*/
|
||||
void portduinoSetup() {
|
||||
printf("Setting up Meshtastic on Porduino...\n");
|
||||
void portduinoSetup()
|
||||
{
|
||||
printf("Setting up Meshtastic on Porduino...\n");
|
||||
|
||||
// FIXME: disable while not testing with real hardware
|
||||
// gpioBind(new R595PolledIrqPin());
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
SPI.begin(); // We need to create SPI
|
||||
bool usePineLora = !spiChip->isSimulated();
|
||||
if(usePineLora) {
|
||||
printf("Connecting to PineLora board...\n");
|
||||
|
||||
// gpioBind((new SimGPIOPin(LORA_RESET, "LORA_RESET")));
|
||||
// gpioBind((new SimGPIOPin(RF95_NSS, "RF95_NSS"))->setSilent());
|
||||
// FIXME: remove this hack once interrupts are confirmed to work on new pine64 board
|
||||
// loraIrq = new PolledIrqPin();
|
||||
loraIrq = new LinuxGPIOPin(LORA_DIO1, "ch341", "int", "loraIrq"); // or "err"?
|
||||
loraIrq->setSilent();
|
||||
gpioBind(loraIrq);
|
||||
|
||||
// BUSY hw was busted on current board - just use the simulated pin (which will read low)
|
||||
auto busy = new LinuxGPIOPin(SX1262_BUSY, "ch341", "slct", "loraBusy");
|
||||
busy->setSilent();
|
||||
gpioBind(busy);
|
||||
|
||||
gpioBind(new LinuxGPIOPin(SX1262_RESET, "ch341", "ini", "loraReset"));
|
||||
|
||||
auto loraCs = new LinuxGPIOPin(SX1262_CS, "ch341", "cs0", "loraCs");
|
||||
loraCs->setSilent();
|
||||
gpioBind(loraCs);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
|
||||
{
|
||||
auto fakeBusy = new SimGPIOPin(SX1262_BUSY, "fakeBusy");
|
||||
fakeBusy->writePin(LOW);
|
||||
fakeBusy->setSilent(true);
|
||||
gpioBind(fakeBusy);
|
||||
|
||||
auto cs = new SimGPIOPin(SX1262_CS, "fakeLoraCS");
|
||||
cs->setSilent(true);
|
||||
gpioBind(cs);
|
||||
|
||||
gpioBind(new SimGPIOPin(SX1262_RESET, "fakeLoraReset"));
|
||||
gpioBind(new SimGPIOPin(LORA_DIO1, "fakeLoraIrq"));
|
||||
}
|
||||
|
||||
// gpioBind((new SimGPIOPin(LORA_RESET, "LORA_RESET")));
|
||||
// gpioBind((new SimGPIOPin(RF95_NSS, "RF95_NSS"))->setSilent());
|
||||
}
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ static void waitEnterSleep()
|
||||
delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
|
||||
|
||||
if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep
|
||||
recordCriticalError(CriticalErrorCode_SleepEnterWait);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SleepEnterWait);
|
||||
assert(0); // FIXME - for now we just restart, need to fix bug #167
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user