# Conflicts:
#	geeksville-private/windows-build-instructions.md
#	src/graphics/Screen.h
This commit is contained in:
Kevin Hester
2021-05-25 03:43:47 +08:00
215 changed files with 3914 additions and 60067 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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;
+6 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+66
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#pragma once
void playBeep();
void playStartMelody();
void playShutdownMelody();
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "configuration.h"
#include "../freertosinc.h"
namespace concurrency
@@ -28,4 +27,4 @@ class BinarySemaphoreFreeRTOS
#endif
}
} // namespace concurrency
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "configuration.h"
#include "../freertosinc.h"
namespace concurrency
@@ -28,4 +27,4 @@ class BinarySemaphorePosix
#endif
}
} // namespace concurrency
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+7 -17
View File
@@ -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
-2
View File
@@ -4,8 +4,6 @@
void reinitUpdateService();
void bluetoothRebootCheck();
#ifdef __cplusplus
extern "C" {
#endif
+14 -2
View File
@@ -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
View File
@@ -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
}
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
+6
View File
@@ -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();
+6 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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 = &mp;
/// 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
View File
@@ -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
+9 -7
View File
@@ -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);
+2 -3
View File
@@ -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;
};
+3 -5
View File
@@ -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
View File
@@ -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
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+5 -5
View File
@@ -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
View File
@@ -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
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -2
View File
@@ -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);
+3
View File
@@ -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);
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+37 -10
View File
@@ -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.
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
*/
+3 -1
View File
@@ -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);
+8 -2
View File
@@ -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" */
-3
View File
@@ -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)
-24
View File
@@ -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
+1 -1
View File
@@ -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
+3
View File
@@ -39,6 +39,9 @@ PB_BIND(FromRadio, FromRadio, 2)
PB_BIND(ToRadio, ToRadio, 2)
PB_BIND(ToRadio_PeerInfo, ToRadio_PeerInfo, AUTO)
+68 -25
View File
@@ -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" */
+12
View File
@@ -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)
+55
View File
@@ -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
+16 -4
View File
@@ -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" */
+11 -6
View File
@@ -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("}");
}
+3 -1
View File
@@ -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
};
+15 -12
View File
@@ -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");
+2 -2
View File
@@ -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;
+14 -31
View File
@@ -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
}
+8 -6
View File
@@ -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();
};
/**
+169
View File
@@ -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 *)&mp;
// 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);
}
}
+60
View File
@@ -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;
-1
View File
@@ -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?
+4
View File
@@ -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;
+4
View File
@@ -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;
+13 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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
}
+10
View File
@@ -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;
+2 -4
View File
@@ -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)
+27 -26
View File
@@ -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;
}
+4 -19
View File
@@ -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;
}
+13 -3
View File
@@ -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 {
+11 -7
View File
@@ -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,
+135 -191
View File
@@ -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
}
+33 -34
View File
@@ -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;
*/
+62 -55
View File
@@ -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
View File
@@ -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;
}