Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Hester
b6740548f3 todo update 2021-08-28 13:46:28 -07:00
113 changed files with 1468 additions and 2858 deletions

View File

@@ -58,19 +58,6 @@ jobs:
run: |
pio upgrade
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: "meshtastic/meshtastic-web"
file: "build.tar"
target: "build.tar"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Unpack web ui
run: |
tar -xf build.tar -C data/static
rm build.tar
# We now run integration test before other build steps (to quickly see runtime failures)
- name: Build for native
run: platformio run -e native

View File

@@ -47,19 +47,6 @@ jobs:
run: |
pio upgrade
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: "meshtastic/meshtastic-web"
file: "build.tar"
target: "build.tar"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Unpack web ui
run: |
tar -xf build.tar -C data/static
rm build.tar
# Will be available in steps.version.outputs.version
- name: Get version string
run: echo "::set-output name=version::$(./bin/buildinfo.py long)"

2
.gitignore vendored
View File

@@ -9,7 +9,7 @@ main/credentials.h
!.vscode/extensions.json
*.code-workspace
.idea
.idea/workspace.xml
.DS_Store
Thumbs.db

7
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<clangFormatSettings>
<option name="ENABLED" value="true" />
</clangFormatSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

2
.idea/meshtastic-esp32.iml generated Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

4
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/meshtastic-esp32.iml" filepath="$PROJECT_DIR$/.idea/meshtastic-esp32.iml" />
</modules>
</component>
</project>

9
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/design" vcs="Git" />
<mapping directory="$PROJECT_DIR$/proto" vcs="Git" />
<mapping directory="$PROJECT_DIR$/sdk-nrfxlib" vcs="Git" />
</component>
</project>

View File

@@ -2,10 +2,4 @@
[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-device)
## This repository contains the device firmware used in the [Meshtastic](https://meshtastic.org) project.
Update Instructions
[For ESP32 devices click here](https://meshtastic.org/docs/getting-started/flashing-esp32)
[For nRF52 devices click here](https://meshtastic.org/docs/getting-started/flashing-nrf52)
For developer information and specific building instructions, please see the [developer doccumentation](https://meshtastic.org/docs/developers)

Binary file not shown.

View File

@@ -6,10 +6,6 @@ VERSION=`bin/buildinfo.py long`
# Must have a V prefix to trigger github
git tag "v${VERSION}"
# Commented out per https://github.com/meshtastic/Meshtastic-device/issues/947
#git push root "v${VERSION}" # push the tag
git push origin "v${VERSION}" # push the tag
git push root "v${VERSION}" # push the tag
echo "Tag ${VERSION} pushed to github, github actions should now be building the draft release. If it seems good, click to publish it"

View File

@@ -1 +0,0 @@
cd proto && ..\nanopb-0.4.4\generator-bin\protoc.exe --nanopb_out=-v:..\src\mesh\generated -I=..\proto *.proto

View File

@@ -1,2 +0,0 @@
@echo off
if [%1]==[] (echo "Please specify a platformio NRF target (i.e. rak4631) as the first argument.") else (python3 .\bin\uf2conv.py .\.pio\build\%1\firmware.hex -c -o .\.pio\build\%1\firmware.uf2 -f 0xADA52840)

View File

@@ -16,9 +16,9 @@
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_flags": "-DS113",
"sd_name": "s113",
"sd_version": "7.2.0",
"sd_fwid": "0x00B6"
},
"bootloader": {

View File

1
data/static/index.html Normal file
View File

@@ -0,0 +1 @@
not yet supported - soon will be included in build

BIN
deprecated/icon-18dp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

1
deprecated/icon-24px.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 5c-3.87 0-7 3.13-7 7h2c0-2.76 2.24-5 5-5s5 2.24 5 5h2c0-3.87-3.13-7-7-7zm1 9.29c.88-.39 1.5-1.26 1.5-2.29 0-1.38-1.12-2.5-2.5-2.5S9.5 10.62 9.5 12c0 1.02.62 1.9 1.5 2.29v3.3L7.59 21 9 22.41l3-3 3 3L16.41 21 13 17.59v-3.3zM12 1C5.93 1 1 5.93 1 12h2c0-4.97 4.03-9 9-9s9 4.03 9 9h2c0-6.07-4.93-11-11-11z"/></svg>

After

Width:  |  Height:  |  Size: 442 B

BIN
deprecated/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -20,6 +20,4 @@ make
* todo run hello world on hardware (check for bl604 vs bl602 first)
* build/run in the crummy arduino environment
* build in platformio
https://lupyuen.github.io/articles/lorawan2
* build in platformio

View File

@@ -0,0 +1,7 @@
* install python
* install git (including git-bash)
* install platformio
* install vscode
* install https://sourceforge.net/projects/mingw-w64/ (for windows gcc/g++) - you'll need to add the bin directory to your PATH

View File

@@ -16,12 +16,11 @@
;default_envs = tlora_v1_3
;default_envs = tlora-v2
;default_envs = lora-relay-v1 # nrf board
;default_envs = t-echo
default_envs = t-echo
;default_envs = nrf52840dk-geeksville
;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
;default_envs = rak4631
;default_envs = rak4630
default_envs = meshtastic-diy-v1
[common]
; common is not currently used
@@ -69,11 +68,11 @@ debug_tool = jlink
lib_deps =
https://github.com/meshtastic/esp8266-oled-ssd1306.git#35d796226b853b0c0ff818b2f1aa3d35e7296a96 ; ESP8266_SSD1306
https://github.com/meshtastic/OneButton.git#3bcba9492d01e2a8a86f46700ab16f96dd2cf1f5 ; OneButton library for non-blocking button debounce
https://github.com/geeksville/OneButton.git ; OneButton library for non-blocking button debounce
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
https://github.com/meshtastic/arduino-fsm.git
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
https://github.com/meshtastic/RadioLib.git#5582ac30578ff3f53f20630a00b2a8a4b8f92c74
https://github.com/meshtastic/RadioLib.git#80ed10d689a0568782c5bd152906b0f97d2bce93
https://github.com/meshtastic/TinyGPSPlus.git#f0f47067ef2f67c856475933188251c1ef615e79
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
Wire ; explicitly needed here because the AXP202 library forgets to add it
@@ -111,8 +110,6 @@ lib_deps =
https://github.com/meshtastic/esp32_https_server.git
adafruit/DHT sensor library@^1.4.1
adafruit/Adafruit Unified Sensor@^1.1.4
paulstoffregen/OneWire@^2.3.5
robtillaart/DS18B20@^0.1.11
# Hmm - this doesn't work yet
# board_build.ldscript = linker/esp32.extram.bss.ld
lib_ignore = segger_rtt
@@ -193,15 +190,6 @@ board = ttgo-lora32-v1
build_flags =
${esp32_base.build_flags} -D TLORA_V2_1_16
; Meshtastic DIY v1 by Nano VHF Schematic based on ESP32-WROOM-32 (38 pins) devkit & EBYTE E22 SX1262/SX1268 module
[env:meshtastic-diy-v1]
extends = esp32_base
board = esp32doit-devkit-v1
build_flags =
${esp32_base.build_flags}
-D DIY_V1
-D EBYTE_E22
; The Heltec Cubecell plus
; IMPORTANT NOTE: This target doesn't yet work and probably won't ever work. I'm keeping it around for now.
; For more details see my post in the forum.

2
proto

Submodule proto updated: 5a556ee4a9...f5b3d0643b

1
src/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
main.ino.cpp

View File

@@ -1,11 +1,8 @@
#pragma once
#include "Status.h"
#include "configuration.h"
#include "NodeDB.h"
#include <Arduino.h>
extern NodeDB nodeDB;
namespace meshtastic
{
@@ -19,39 +16,28 @@ class GPSStatus : public Status
bool hasLock = false; // default to false, until we complete our first read
bool isConnected = false; // Do we have a GPS we are talking to
Position p = Position_init_default;
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
int32_t altitude = 0;
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
// scaling before use)
uint32_t heading = 0;
uint32_t numSatellites = 0;
public:
GPSStatus() { statusType = STATUS_TYPE_GPS; }
// proposed for deprecation
GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop,
uint32_t heading, uint32_t numSatellites)
: Status()
{
this->hasLock = hasLock;
this->isConnected = isConnected;
this->p.latitude_i = latitude;
this->p.longitude_i = longitude;
this->p.altitude = altitude;
this->p.PDOP = dop;
this->p.ground_track = heading;
this->p.sats_in_view = numSatellites;
this->latitude = latitude;
this->longitude = longitude;
this->altitude = altitude;
this->dop = dop;
this->heading = heading;
this->numSatellites = numSatellites;
}
// preferred method
GPSStatus(bool hasLock, bool isConnected, Position pos)
: Status()
{
this->hasLock = hasLock;
this->isConnected = isConnected;
// all-in-one struct copy
this->p = pos;
}
GPSStatus(const GPSStatus &);
GPSStatus &operator=(const GPSStatus &);
@@ -61,85 +47,45 @@ class GPSStatus : public Status
bool getIsConnected() const { return isConnected; }
int32_t getLatitude() const {
if (radioConfig.preferences.fixed_position){
DEBUG_MSG("WARNING: Using fixed latitude\n");
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
return node->position.latitude_i;
} else {
return p.latitude_i;
}
}
int32_t getLatitude() const { return latitude; }
int32_t getLongitude() const {
if (radioConfig.preferences.fixed_position){
DEBUG_MSG("WARNING: Using fixed longitude\n");
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
return node->position.longitude_i;
} else {
return p.longitude_i;
}
}
int32_t getLongitude() const { return longitude; }
int32_t getAltitude() const {
if (radioConfig.preferences.fixed_position){
DEBUG_MSG("WARNING: Using fixed altitude\n");
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
return node->position.altitude;
} else {
return p.altitude;
}
}
int32_t getAltitude() const { return altitude; }
uint32_t getDOP() const { return p.PDOP; }
uint32_t getDOP() const { return dop; }
uint32_t getHeading() const { return p.ground_track; }
uint32_t getHeading() const { return heading; }
uint32_t getNumSatellites() const { return p.sats_in_view; }
uint32_t getNumSatellites() const { return numSatellites; }
bool matches(const GPSStatus *newStatus) const
{
#if GPS_EXTRAVERBOSE
DEBUG_MSG("GPSStatus.match() new pos@%x to old pos@%x\n",
newStatus->p.pos_timestamp, p.pos_timestamp);
#endif
return (newStatus->hasLock != hasLock ||
newStatus->isConnected != isConnected ||
newStatus->p.latitude_i != p.latitude_i ||
newStatus->p.longitude_i != p.longitude_i ||
newStatus->p.altitude != p.altitude ||
newStatus->p.altitude_hae != p.altitude_hae ||
newStatus->p.PDOP != p.PDOP ||
newStatus->p.ground_track != p.ground_track ||
newStatus->p.sats_in_view != p.sats_in_view);
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->latitude != latitude ||
newStatus->longitude != longitude || newStatus->altitude != altitude || newStatus->dop != dop ||
newStatus->heading != heading || newStatus->numSatellites != numSatellites);
}
int updateStatus(const GPSStatus *newStatus)
{
// Only update the status if values have actually changed
bool isDirty = matches(newStatus);
if (isDirty && p.pos_timestamp &&
(newStatus->p.pos_timestamp == p.pos_timestamp)) {
// We can NEVER be in two locations at the same time! (also PR #886)
DEBUG_MSG("BUG!! positional timestamp unchanged from prev solution\n");
bool isDirty;
{
isDirty = matches(newStatus);
initialized = true;
hasLock = newStatus->hasLock;
isConnected = newStatus->isConnected;
latitude = newStatus->latitude;
longitude = newStatus->longitude;
altitude = newStatus->altitude;
dop = newStatus->dop;
heading = newStatus->heading;
numSatellites = newStatus->numSatellites;
}
initialized = true;
hasLock = newStatus->hasLock;
isConnected = newStatus->isConnected;
p = newStatus->p;
if (isDirty) {
if (hasLock) {
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
DEBUG_MSG("New GPS pos@%x:3 lat=%f, lon=%f, alt=%d, pdop=%.2f, track=%.2f, sats=%d\n",
p.pos_timestamp,
p.latitude_i * 1e-7, p.longitude_i * 1e-7,
p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
p.sats_in_view);
} else
if (hasLock)
DEBUG_MSG("New GPS pos lat=%f, lon=%f, alt=%d, pdop=%f, heading=%f, sats=%d\n", latitude * 1e-7, longitude * 1e-7,
altitude, dop * 1e-2, heading * 1e-5, numSatellites);
else
DEBUG_MSG("No GPS lock\n");
onNewStatus.notifyObservers(this);
}

View File

@@ -192,7 +192,6 @@ bool Power::setup()
found = analogInit();
}
enabled = found;
low_voltage_counter = 0;
return found;
}
@@ -239,24 +238,9 @@ void Power::readPowerStatus()
powerStatus.getIsCharging(), powerStatus.getBatteryVoltageMv(), powerStatus.getBatteryChargePercent());
newStatus.notifyObservers(&powerStatus);
// If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row
// Supect fluctuating voltage on the RAK4631 to force it to deep sleep even if battery is at 85% after only a few days
#ifdef NRF52_SERIES
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB()){
if (batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS){
low_voltage_counter++;
if (low_voltage_counter>3)
powerFSM.trigger(EVENT_LOW_BATTERY);
} else {
low_voltage_counter = 0;
}
}
#else
// If we have a battery at all and it is less than 10% full, force deep sleep
if (powerStatus.getHasBattery() && !powerStatus.getHasUSB() && batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS)
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
} else {
// No power sensing on this board - tell everyone else we have no idea what is happening
const PowerStatus powerStatus = PowerStatus(OptUnknown, OptUnknown, OptUnknown, -1, -1);

View File

@@ -11,11 +11,6 @@
/// Should we behave as if we have AC power now?
static bool isPowered()
{
// Completely circumvents the battery / power sensing logic and assumes constant power source
if (radioConfig.preferences.is_always_powered) {
return true;
}
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

View File

@@ -188,11 +188,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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 SX126X_CS RF95_NSS // FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_E22 // Not really an E22 but TTGO seems to be trying to clone that
#define SX1262_CS RF95_NSS // FIXME - we really should define LORA_CS instead
#define SX1262_DIO1 LORA_DIO1
#define SX1262_BUSY LORA_DIO2
#define SX1262_RESET LORA_RESET
#define SX1262_E22 // Not really an E22 but TTGO seems to be trying to clone that
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
// code)
#endif
@@ -226,52 +226,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define GPS_RX_PIN 12
#define GPS_TX_PIN 15
#elif defined(DIY_V1)
// This string must exactly match the case used in release file names or the android updater won't work
#define HW_VENDOR HardwareModel_DIY_V1
// For OLED LCD
#define I2C_SDA 21
#define I2C_SCL 22
// GPS
#undef GPS_RX_PIN
#define GPS_RX_PIN 15
//#undef GPS_TX_PIN
//#define GPS_TX_PIN 12 // not connected
#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
#define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module
#define LORA_RESET 23 // RST for SX1276, and for SX1262/SX1268
#define LORA_DIO1 33 // IRQ for SX1262/SX1268
#define LORA_DIO2 32 // BUSY for SX1262/SX1268
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262/SX1268, if DIO3 is high the TXCO is enabled
#define RF95_SCK 5
#define RF95_MISO 19
#define RF95_MOSI 27
#define RF95_NSS 18
// supported modules list
#define USE_SX1262
#define USE_SX1268
#define USE_LLCC68
// common pinouts for SX126X modules
#define SX126X_CS 18 // NSS for SX126X
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_RXEN 14
#define SX126X_TXEN 13
#ifdef EBYTE_E22
// Internally the TTGO module hooks the SX126x-DIO2 in to control the TX/RX switch
// (which is the default for the sx1262interface code)
#define SX126X_E22
#endif
#elif defined(ARDUINO_HELTEC_WIFI_LORA_32_V2)
// the default ESP32 Pin of 15 is the Oled SCL, set to 36 and 37 and works fine.
@@ -327,8 +281,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define GPS_RX_PIN 36
#define GPS_TX_PIN 37
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define I2C_SDA 4 // I2C pins for this board
#define I2C_SCL 15
@@ -510,11 +462,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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 SX126X_CS 20 // CS0 on pinelora schematic, hooked to gpio D0 on ch341f
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
// HOPE RFM90 does not have a TCXO therefore not SX126X_E22
#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

View File

@@ -80,8 +80,6 @@ GPS::~GPS()
notifyDeepSleepObserver.unobserve();
}
bool GPS::hasLock() { return hasValidLocation; }
// Allow defining the polarity of the WAKE output. default is active high
#ifndef GPS_WAKE_ACTIVE
#define GPS_WAKE_ACTIVE 1
@@ -202,13 +200,11 @@ void GPS::publishUpdate()
if (shouldPublish) {
shouldPublish = false;
// In debug logs, identify position by @timestamp:stage (stage 2 = publish)
DEBUG_MSG("publishing pos@%x:2, hasVal=%d, GPSlock=%d\n",
p.pos_timestamp, hasValidLocation, hasLock());
DEBUG_MSG("publishing GPS lock=%d\n", hasLock());
// Notify any status instances that are observing us
const meshtastic::GPSStatus status =
meshtastic::GPSStatus(hasValidLocation, isConnected(), p);
meshtastic::GPSStatus(hasLock(), isConnected(), latitude, longitude, altitude, dop, heading, numSatellites);
newStatus.notifyObservers(&status);
}
}
@@ -246,7 +242,6 @@ int32_t GPS::runOnce()
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
DEBUG_MSG("hasValidLocation RISING EDGE\n");
hasValidLocation = true;
shouldPublish = true;
}
@@ -263,10 +258,6 @@ int32_t GPS::runOnce()
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
DEBUG_MSG("hasValidLocation FALLING EDGE (last read: %d)\n", gotLoc);
}
p = Position_init_default;
hasValidLocation = false;
}
@@ -334,11 +325,6 @@ GPS *createGps()
#ifdef NO_GPS
return nullptr;
#else
#ifdef GPS_ALTITUDE_HAE
DEBUG_MSG("Using HAE altitude model\n");
#else
DEBUG_MSG("Using MSL altitude model\n");
#endif
// If we don't have bidirectional comms, we can't even try talking to UBLOX
#ifdef GPS_TX_PIN
// Init GPS - first try ublox

View File

@@ -17,10 +17,6 @@ class GPS : private concurrency::OSThread
private:
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastWhileActiveMsec = 0;
/**
* hasValidLocation - indicates that the position variables contain a complete
* GPS location, valid and fresh (< gps_update_interval + gps_attempt_time)
*/
bool hasValidLocation = false; // default to false, until we complete our first read
bool isAwake = false; // true if we want a location right now
@@ -43,7 +39,11 @@ class GPS : private concurrency::OSThread
/** If !0 we will attempt to connect to the GPS over I2C */
static uint8_t i2cAddress;
Position p = Position_init_default;
int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double
int32_t altitude = 0;
uint32_t dop = 0; // Diminution of position; PDOP where possible (UBlox), HDOP otherwise (TinyGPS) in 10^2 units (needs
// scaling before use)
uint32_t heading = 0; // Heading of motion, in degrees * 10^-5
GPS() : concurrency::OSThread("GPS") {}
@@ -57,8 +57,8 @@ class GPS : private concurrency::OSThread
*/
virtual bool setup();
/// Returns true if we have acquired GPS lock.
virtual bool hasLock();
/// Returns ture if we have acquired GPS lock.
bool hasLock() const { return hasValidLocation; }
/// Return true if we are connected to a GPS
bool isConnected() const { return hasGPS; }

View File

@@ -1,450 +0,0 @@
#include "GeoCoord.h"
GeoCoord::GeoCoord() {
_dirty = true;
}
GeoCoord::GeoCoord (int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _longitude(lon), _altitude(alt) {
GeoCoord::setCoords();
}
GeoCoord::GeoCoord (float lat, float lon, int32_t alt) : _altitude(alt) {
// Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
}
GeoCoord::GeoCoord(double lat, double lon, int32_t alt): _altitude(alt) {
// Change decimial reprsentation to int32_t. I.e., 12.345 becomes 123450000
_latitude = int32_t(lat * 1e+7);
_longitude = int32_t(lon * 1e+7);
GeoCoord::setCoords();
}
// Initialize all the coordinate systems
void GeoCoord::setCoords() {
double lat = _latitude * 1e-7;
double lon = _longitude * 1e-7;
GeoCoord::latLongToDMS(lat, lon, _dms);
GeoCoord::latLongToUTM(lat, lon, _utm);
GeoCoord::latLongToMGRS(lat, lon, _mgrs);
GeoCoord::latLongToOSGR(lat, lon, _osgr);
GeoCoord::latLongToOLC(lat, lon, _olc);
_dirty = false;
}
void GeoCoord::updateCoords(int32_t lat, int32_t lon, int32_t alt) {
// If marked dirty or new coordiantes
if(_dirty || _latitude != lat || _longitude != lon || _altitude != alt) {
_dirty = true;
_latitude = lat;
_longitude = lon;
_altitude = alt;
setCoords();
}
}
void GeoCoord::updateCoords(const double lat, const double lon, const int32_t alt) {
int32_t iLat = lat * 1e+7;
int32_t iLon = lon * 1e+7;
// If marked dirty or new coordiantes
if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) {
_dirty = true;
_latitude = iLat;
_longitude = iLon;
_altitude = alt;
setCoords();
}
}
void GeoCoord::updateCoords(const float lat, const float lon, const int32_t alt) {
int32_t iLat = lat * 1e+7;
int32_t iLon = lon * 1e+7;
// If marked dirty or new coordiantes
if(_dirty || _latitude != iLat || _longitude != iLon || _altitude != alt) {
_dirty = true;
_latitude = iLat;
_longitude = iLon;
_altitude = alt;
setCoords();
}
}
/**
* Converts lat long coordinates from decimal degrees to degrees minutes seconds format.
* DD°MM'SS"C DDD°MM'SS"C
*/
void GeoCoord::latLongToDMS(const double lat, const double lon, DMS &dms) {
if (lat < 0) dms.latCP = 'S';
else dms.latCP = 'N';
double latDeg = lat;
if (lat < 0)
latDeg = latDeg * -1;
dms.latDeg = floor(latDeg);
double latMin = (latDeg - dms.latDeg) * 60;
dms.latMin = floor(latMin);
dms.latSec = (latMin - dms.latMin) * 60;
if (lon < 0) dms.lonCP = 'W';
else dms.lonCP = 'E';
double lonDeg = lon;
if (lon < 0)
lonDeg = lonDeg * -1;
dms.lonDeg = floor(lonDeg);
double lonMin = (lonDeg - dms.lonDeg) * 60;
dms.lonMin = floor(lonMin);
dms.lonSec = (lonMin - dms.lonMin) * 60;
}
/**
* Converts lat long coordinates to UTM.
* based on this: https://github.com/walvok/LatLonToUTM/blob/master/latlon_utm.ino
*/
void GeoCoord::latLongToUTM(const double lat, const double lon, UTM &utm) {
const std::string latBands = "CDEFGHJKLMNPQRSTUVWXX";
utm.zone = int((lon + 180)/6 + 1);
utm.band = latBands[int(lat/8 + 10)];
double a = 6378137; // WGS84 - equatorial radius
double k0 = 0.9996; // UTM point scale on the central meridian
double eccSquared = 0.00669438; // eccentricity squared
double lonTemp = (lon + 180) - int((lon + 180)/360) * 360 - 180; //Make sure the longitude is between -180.00 .. 179.9
double latRad = toRadians(lat);
double lonRad = toRadians(lonTemp);
// Special Zones for Norway and Svalbard
if( lat >= 56.0 && lat < 64.0 && lonTemp >= 3.0 && lonTemp < 12.0 ) // Norway
utm.zone = 32;
if( lat >= 72.0 && lat < 84.0 ) { // Svalbard
if ( lonTemp >= 0.0 && lonTemp < 9.0 ) utm.zone = 31;
else if( lonTemp >= 9.0 && lonTemp < 21.0 ) utm.zone = 33;
else if( lonTemp >= 21.0 && lonTemp < 33.0 ) utm.zone = 35;
else if( lonTemp >= 33.0 && lonTemp < 42.0 ) utm.zone = 37;
}
double lonOrigin = (utm.zone - 1)*6 - 180 + 3; // puts origin in middle of zone
double lonOriginRad = toRadians(lonOrigin);
double eccPrimeSquared = (eccSquared)/(1 - eccSquared);
double N = a/sqrt(1 - eccSquared*sin(latRad)*sin(latRad));
double T = tan(latRad)*tan(latRad);
double C = eccPrimeSquared*cos(latRad)*cos(latRad);
double A = cos(latRad)*(lonRad - lonOriginRad);
double M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64 - 5*eccSquared*eccSquared*eccSquared/256)*latRad
- (3*eccSquared/8 + 3*eccSquared*eccSquared/32 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*latRad)
+ (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*latRad)
- (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*latRad));
utm.easting = (double)(k0*N*(A+(1-T+C)*pow(A, 3)/6 + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120)
+ 500000.0);
utm.northing = (double)(k0*(M+N*tan(latRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24
+ (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720)));
if(lat < 0)
utm.northing += 10000000.0; //10000000 meter offset for southern hemisphere
}
// Converts lat long coordinates to an MGRS.
void GeoCoord::latLongToMGRS(const double lat, const double lon, MGRS &mgrs) {
const std::string e100kLetters[3] = { "ABCDEFGH", "JKLMNPQR", "STUVWXYZ" };
const std::string n100kLetters[2] = { "ABCDEFGHJKLMNPQRSTUV", "FGHJKLMNPQRSTUVABCDE" };
UTM utm;
latLongToUTM(lat, lon, utm);
mgrs.zone = utm.zone;
mgrs.band = utm.band;
double col = floor(utm.easting / 100000);
mgrs.east100k = e100kLetters[(mgrs.zone - 1) % 3][col - 1];
double row = (int32_t)floor(utm.northing / 100000.0) % 20;
mgrs.north100k = n100kLetters[(mgrs.zone-1)%2][row];
mgrs.easting = (int32_t)utm.easting % 100000;
mgrs.northing = (int32_t)utm.northing % 100000;
}
/**
* Converts lat long coordinates to Ordnance Survey Grid Reference (UK National Grid Ref).
* Based on: https://www.movable-type.co.uk/scripts/latlong-os-gridref.html
*/
void GeoCoord::latLongToOSGR(const double lat, const double lon, OSGR &osgr) {
char letter[] = "ABCDEFGHJKLMNOPQRSTUVWXYZ"; // No 'I' in OSGR
double a = 6377563.396; // Airy 1830 semi-major axis
double b = 6356256.909; // Airy 1830 semi-minor axis
double f0 = 0.9996012717; // National Grid point scale factor on the central meridian
double phi0 = toRadians(49);
double lambda0 = toRadians(-2);
double n0 = -100000;
double e0 = 400000;
double e2 = 1 - (b*b)/(a*a); // eccentricity squared
double n = (a - b)/(a + b);
double osgb_Latitude;
double osgb_Longitude;
convertWGS84ToOSGB36(lat, lon, osgb_Latitude, osgb_Longitude);
double phi = osgb_Latitude; // already in radians
double lambda = osgb_Longitude; // already in radians
double v = a * f0 / sqrt(1 - e2 * sin(phi) * sin(phi));
double rho = a * f0 * (1 - e2) / pow(1 - e2 * sin(phi) * sin(phi), 1.5);
double eta2 = v / rho - 1;
double mA = (1 + n + (5/4)*n*n + (5/4)*n*n*n) * (phi - phi0);
double mB = (3*n + 3*n*n + (21/8)*n*n*n) * sin(phi - phi0) * cos(phi + phi0);
// loss of precision in mC & mD due to floating point rounding can cause innaccuracy of northing by a few meters
double mC = (15/8*n*n + 15/8*n*n*n) * sin(2*(phi - phi0)) * cos(2*(phi + phi0));
double mD = (35/24)*n*n*n * sin(3*(phi - phi0)) * cos(3*(phi + phi0));
double m = b*f0*(mA - mB + mC - mD);
double cos3Phi = cos(phi)*cos(phi)*cos(phi);
double cos5Phi = cos3Phi*cos(phi)*cos(phi);
double tan2Phi = tan(phi)*tan(phi);
double tan4Phi = tan2Phi*tan2Phi;
double I = m + n0;
double II = (v/2)*sin(phi)*cos(phi);
double III = (v/24)*sin(phi)*cos3Phi*(5 - tan2Phi + 9*eta2);
double IIIA = (v/720)*sin(phi)*cos5Phi*(61 - 58*tan2Phi + tan4Phi);
double IV = v*cos(phi);
double V = (v/6)*cos3Phi*(v/rho - tan2Phi);
double VI = (v/120)*cos5Phi*(5 - 18*tan2Phi + tan4Phi + 14*eta2 - 58*tan2Phi*eta2);
double deltaLambda = lambda - lambda0;
double deltaLambda2 = deltaLambda*deltaLambda;
double northing = I + II*deltaLambda2 + III*deltaLambda2*deltaLambda2 + IIIA*deltaLambda2*deltaLambda2*deltaLambda2;
double easting = e0 + IV*deltaLambda + V*deltaLambda2*deltaLambda + VI*deltaLambda2*deltaLambda2*deltaLambda;
if (easting < 0 || easting > 700000 || northing < 0 || northing > 1300000) // Check if out of boundaries
osgr = { 'I', 'I', 0, 0 };
else {
uint32_t e100k = floor(easting / 100000);
uint32_t n100k = floor(northing / 100000);
int8_t l1 = (19 - n100k) - (19 - n100k) % 5 + floor((e100k + 10) / 5);
int8_t l2 = (19 - n100k) * 5 % 25 + e100k % 5;
osgr.e100k = letter[l1];
osgr.n100k = letter[l2];
osgr.easting = floor((int)easting % 100000);
osgr.northing = floor((int)northing % 100000);
}
}
/**
* Converts lat long coordinates to Open Location Code.
* Based on: https://github.com/google/open-location-code/blob/main/c/src/olc.c
*/
void GeoCoord::latLongToOLC(double lat, double lon, OLC &olc) {
char tempCode[] = "1234567890abc";
const char kAlphabet[] = "23456789CFGHJMPQRVWX";
double latitude;
double longitude = lon;
double latitude_degrees = std::min(90.0, std::max(-90.0, lat));
if (latitude_degrees < 90) // Check latitude less than lat max
latitude = latitude_degrees;
else {
double precision;
if (OLC_CODE_LEN <= 10)
precision = pow_neg(20, floor((OLC_CODE_LEN / -2) + 2));
else
precision = pow_neg(20, -3) / pow(5, OLC_CODE_LEN - 10);
latitude = latitude_degrees - precision / 2;
}
while (longitude < -180) // Normalize longitude
longitude += 360;
while (longitude >= 180)
longitude -= 360;
int64_t lat_val = 90 * 2.5e7;
int64_t lng_val = 180 * 8.192e6;
lat_val += latitude * 2.5e7;
lng_val += longitude * 8.192e6;
size_t pos = OLC_CODE_LEN;
if (OLC_CODE_LEN > 10) { // Compute grid part of code if needed
for (size_t i = 0; i < 5; i++) {
int lat_digit = lat_val % 5;
int lng_digit = lng_val % 4;
int ndx = lat_digit * 4 + lng_digit;
tempCode[pos--] = kAlphabet[ndx];
lat_val /= 5;
lng_val /= 4;
}
} else {
lat_val /= pow(5, 5);
lng_val /= pow(4, 5);
}
pos = 10;
for (size_t i = 0; i < 5; i++) { // Compute pair section of code
int lat_ndx = lat_val % 20;
int lng_ndx = lng_val % 20;
tempCode[pos--] = kAlphabet[lng_ndx];
tempCode[pos--] = kAlphabet[lat_ndx];
lat_val /= 20;
lng_val /= 20;
if (i == 0)
tempCode[pos--] = '+';
}
if (OLC_CODE_LEN < 9) { // Add padding if needed
for (size_t i = OLC_CODE_LEN; i < 9; i++)
tempCode[i] = '0';
tempCode[9] = '+';
}
size_t char_count = OLC_CODE_LEN;
if (10 > char_count) {
char_count = 10;
}
for (size_t i = 0; i < char_count; i++) {
olc.code[i] = tempCode[i];
}
olc.code[char_count] = '\0';
}
// Converts the coordinate in WGS84 datum to the OSGB36 datum.
void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude) {
// Convert lat long to cartesian
double phi = toRadians(lat);
double lambda = toRadians(lon);
double h = 0.0; // No OSTN height data used, some loss of accuracy (up to 5m)
double wgsA = 6378137; // WGS84 datum semi major axis
double wgsF = 1 / 298.257223563; // WGS84 datum flattening
double ecc = 2*wgsF - wgsF*wgsF;
double vee = wgsA / sqrt(1 - ecc * pow(sin(phi), 2));
double wgsX = (vee + h) * cos(phi) * cos(lambda);
double wgsY = (vee + h) * cos(phi) * sin(lambda);
double wgsZ = ((1 - ecc) * vee + h) * sin(phi);
// 7-parameter Helmert transform
double tx = -446.448; // x shift in meters
double ty = 125.157; // y shift in meters
double tz = -542.060; // z shift in meters
double s = 20.4894/1e6 + 1; // scale normalized parts per million to (s + 1)
double rx = toRadians(-0.1502/3600); // x rotation normalize arcseconds to radians
double ry = toRadians(-0.2470/3600); // y rotation normalize arcseconds to radians
double rz = toRadians(-0.8421/3600); // z rotation normalize arcseconds to radians
double osgbX = tx + wgsX*s - wgsY*rz + wgsZ*ry;
double osgbY = ty + wgsX*rz + wgsY*s - wgsZ*rx;
double osgbZ = tz - wgsX*ry + wgsY*rx + wgsZ*s;
// Convert cartesian to lat long
double airyA = 6377563.396; // Airy1830 datum semi major axis
double airyB = 6356256.909; // Airy1830 datum semi minor axis
double airyF = 1/ 299.3249646; // Airy1830 datum flattening
double airyEcc = 2*airyF - airyF*airyF;
double airyEcc2 = airyEcc / (1 - airyEcc);
double p = sqrt(osgbX*osgbX + osgbY*osgbY);
double R = sqrt(p*p + osgbZ*osgbZ);
double tanBeta = (airyB*osgbZ) / (airyA*p) * (1 + airyEcc2*airyB/R);
double sinBeta = tanBeta / sqrt(1 + tanBeta*tanBeta);
double cosBeta = sinBeta / tanBeta;
osgb_Latitude = atan2(osgbZ + airyEcc2*airyB*sinBeta*sinBeta*sinBeta, p - airyEcc*airyA*cosBeta*cosBeta*cosBeta); // leave in radians
osgb_Longitude = atan2(osgbY, osgbX); // leave in radians
//osgb height = p*cos(osgb.latitude) + osgbZ*sin(osgb.latitude) -
//(airyA*airyA/(airyA / sqrt(1 - airyEcc*sin(osgb.latitude)*sin(osgb.latitude)))); // Not used, no OSTN data
}
/// Ported from my old java code, returns distance in meters along the globe
/// surface (by magic?)
float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
double pk = (180 / 3.14169);
double a1 = lat_a / pk;
double a2 = lng_a / pk;
double b1 = lat_b / pk;
double b2 = lng_b / pk;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
double t3 = sin(a1) * sin(b1);
double tt = acos(t1 + t2 + t3);
if (std::isnan(tt))
tt = 0.0; // Must have been the same point?
return (float)(6366000 * tt);
}
/**
* Computes the bearing in degrees between two points on Earth. Ported from my
* old Gaggle android app.
*
* @param lat1
* Latitude of the first point
* @param lon1
* Longitude of the first point
* @param lat2
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in radians. A value of 0 means due
* north.
*/
float GeoCoord::bearing(double lat1, double lon1, double lat2, double lon2)
{
double lat1Rad = toRadians(lat1);
double lat2Rad = toRadians(lat2);
double deltaLonRad = toRadians(lon2 - lon1);
double y = sin(deltaLonRad) * cos(lat2Rad);
double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad));
return atan2(y, x);
}
/**
* Ported from http://www.edwilliams.org/avform147.htm#Intro
* @brief Convert from meters to range in radians on a great circle
* @param range_meters
* The range in meters
* @return range in radians on a great circle
*/
float GeoCoord::rangeMetersToRadians(double range_meters) {
// 1 nm is 1852 meters
double distance_nm = range_meters * 1852;
return (PI / (180 * 60)) *distance_nm;
}
/**
* Ported from http://www.edwilliams.org/avform147.htm#Intro
* @brief Convert from radians to range in meters on a great circle
* @param range_radians
* The range in radians
* @return Range in meters on a great circle
*/
float GeoCoord::rangeRadiansToMeters(double range_radians) {
double distance_nm = ((180 * 60) / PI) * range_radians;
// 1 meter is 0.000539957 nm
return distance_nm * 0.000539957;
}
// Find distance from point to passed in point
int32_t GeoCoord::distanceTo(GeoCoord pointB) {
return latLongToMeter(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7);
}
// Find bearing from point to passed in point
int32_t GeoCoord::bearingTo(GeoCoord pointB) {
return bearing(this->getLatitude() * 1e-7, this->getLongitude() * 1e-7, pointB.getLatitude() * 1e-7, pointB.getLongitude() * 1e-7);
}
/**
* Create a new point bassed on the passed in poin
* Ported from http://www.edwilliams.org/avform147.htm#LL
* @param bearing
* The bearing in raidans
* @param range_meters
* range in meters
* @return GeoCoord object of point at bearing and range from initial point
*/
std::shared_ptr<GeoCoord> GeoCoord::pointAtDistance(double bearing, double range_meters) {
double range_radians = rangeMetersToRadians(range_meters);
double lat1 = this->getLatitude() * 1e-7;
double lon1 = this->getLongitude() * 1e-7;
double lat = asin(sin(lat1) * cos(range_radians) + cos(lat1) * sin(range_radians) * cos(bearing));
double dlon = atan2(sin(bearing) * sin(range_radians) * cos(lat1), cos(range_radians) - sin(lat1) * sin(lat));
double lon = fmod(lon1 - dlon + PI, 2 * PI) - PI;
return std::make_shared<GeoCoord>(double(lat), double(lon), this->getAltitude());
}

View File

@@ -1,164 +0,0 @@
#pragma once
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdint>
#include <math.h>
#include <stdint.h>
#include <stdexcept>
#include <memory>
#define PI 3.1415926535897932384626433832795
#define OLC_CODE_LEN 11
// Helper functions
// Raises a number to an exponent, handling negative exponents.
static inline double pow_neg(double base, double exponent) {
if (exponent == 0) {
return 1;
} else if (exponent > 0) {
return pow(base, exponent);
}
return 1 / pow(base, -exponent);
}
static inline double toRadians(double deg)
{
return deg * PI / 180;
}
static inline double toDegrees(double r)
{
return r * 180 / PI;
}
// GeoCoord structs/classes
// A struct to hold the data for a DMS coordinate.
struct DMS
{
uint8_t latDeg;
uint8_t latMin;
uint32_t latSec;
char latCP;
uint8_t lonDeg;
uint8_t lonMin;
uint32_t lonSec;
char lonCP;
};
// A struct to hold the data for a UTM coordinate, this is also used when creating an MGRS coordinate.
struct UTM
{
uint8_t zone;
char band;
uint32_t easting;
uint32_t northing;
};
// A struct to hold the data for a MGRS coordinate.
struct MGRS
{
uint8_t zone;
char band;
char east100k;
char north100k;
uint32_t easting;
uint32_t northing;
};
// A struct to hold the data for a OSGR coordiante
struct OSGR {
char e100k;
char n100k;
uint32_t easting;
uint32_t northing;
};
// A struct to hold the data for a OLC coordinate
struct OLC {
char code[OLC_CODE_LEN + 1]; // +1 for null termination
};
class GeoCoord {
private:
int32_t _latitude = 0;
int32_t _longitude = 0;
int32_t _altitude = 0;
DMS _dms;
UTM _utm;
MGRS _mgrs;
OSGR _osgr;
OLC _olc;
bool _dirty = true;
void setCoords();
public:
GeoCoord();
GeoCoord(int32_t lat, int32_t lon, int32_t alt);
GeoCoord(double lat, double lon, int32_t alt);
GeoCoord(float lat, float lon, int32_t alt);
void updateCoords(const int32_t lat, const int32_t lon, const int32_t alt);
void updateCoords(const double lat, const double lon, const int32_t alt);
void updateCoords(const float lat, const float lon, const int32_t alt);
// Conversions
static void latLongToDMS(const double lat, const double lon, DMS &dms);
static void latLongToUTM(const double lat, const double lon, UTM &utm);
static void latLongToMGRS(const double lat, const double lon, MGRS &mgrs);
static void latLongToOSGR(const double lat, const double lon, OSGR &osgr);
static void latLongToOLC(const double lat, const double lon, OLC &olc);
static void convertWGS84ToOSGB36(const double lat, const double lon, double &osgb_Latitude, double &osgb_Longitude);
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b);
static float bearing(double lat1, double lon1, double lat2, double lon2);
static float rangeRadiansToMeters(double range_radians);
static float rangeMetersToRadians(double range_meters);
// Point to point conversions
int32_t distanceTo(GeoCoord pointB);
int32_t bearingTo(GeoCoord pointB);
std::shared_ptr<GeoCoord> pointAtDistance(double bearing, double range);
// Lat lon alt getters
int32_t getLatitude() const { return _latitude; }
int32_t getLongitude() const { return _longitude; }
int32_t getAltitude() const { return _altitude; }
// DMS getters
uint8_t getDMSLatDeg() const { return _dms.latDeg; }
uint8_t getDMSLatMin() const { return _dms.latMin; }
uint32_t getDMSLatSec() const { return _dms.latSec; }
char getDMSLatCP() const { return _dms.latCP; }
uint8_t getDMSLonDeg() const { return _dms.lonDeg; }
uint8_t getDMSLonMin() const { return _dms.lonMin; }
uint32_t getDMSLonSec() const { return _dms.lonSec; }
char getDMSLonCP() const { return _dms.lonCP; }
// UTM getters
uint8_t getUTMZone() const { return _utm.zone; }
char getUTMBand() const { return _utm.band; }
uint32_t getUTMEasting() const { return _utm.easting; }
uint32_t getUTMNorthing() const { return _utm.northing; }
// MGRS getters
uint8_t getMGRSZone() const { return _mgrs.zone; }
char getMGRSBand() const { return _mgrs.band; }
char getMGRSEast100k() const { return _mgrs.east100k; }
char getMGRSNorth100k() const { return _mgrs.north100k; }
uint32_t getMGRSEasting() const { return _mgrs.easting; }
uint32_t getMGRSNorthing() const { return _mgrs.northing; }
// OSGR getters
char getOSGRE100k() const { return _osgr.e100k; }
char getOSGRN100k() const { return _osgr.n100k; }
uint32_t getOSGREasting() const { return _osgr.easting; }
uint32_t getOSGRNorthing() const { return _osgr.northing; }
// OLC getter
void getOLCCode(char* code) { strncpy(code, _olc.code, OLC_CODE_LEN + 1); } // +1 for null termination
};

View File

@@ -2,12 +2,6 @@
#include "NMEAGPS.h"
#include "RTC.h"
#include <TinyGPS++.h>
// GPS solutions older than this will be rejected - see TinyGPSDatum::age()
#define GPS_SOL_EXPIRY_MS 300 // in millis
#define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc)
static int32_t toDegInt(RawDegrees d)
{
int32_t degMult = 10000000; // 1e7
@@ -27,17 +21,6 @@ bool NMEAGPS::setupGPS()
pinMode(PIN_GPS_PPS, INPUT);
#endif
// Currently disabled per issue #525 (TinyGPS++ crash bug)
// when fixed upstream, can be un-disabled to enable 3D FixType and PDOP
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// see NMEAGPS.h
gsafixtype.begin(reader, NMEA_MSG_GXGSA, 2);
gsapdop.begin(reader, NMEA_MSG_GXGSA, 15);
DEBUG_MSG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n");
#else
DEBUG_MSG("GxGSA NOT available\n");
#endif
return true;
}
@@ -65,7 +48,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
DEBUG_MSG("NMEA GPS time %d\n", t.tm_sec);
perhapsSetRTC(RTCQualityGPS, t);
return true;
@@ -81,140 +64,47 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
*/
bool NMEAGPS::lookForLocation()
{
// By default, TinyGPS++ does not parse GPGSA lines, which give us
// the 2D/3D fixType (see NMEAGPS.h)
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
fixQual = reader.fixQuality();
bool foundLocation = false;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
fixType = atoi(gsafixtype.value()); // will set to zero if no data
DEBUG_MSG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
#endif
// uint8_t fixtype = reader.fixQuality();
// hasValidLocation = ((fixtype >= 1) && (fixtype <= 5));
// check if GPS has an acceptable lock
if (! hasLock())
return false;
#ifdef GPS_EXTRAVERBOSE
DEBUG_MSG("AGE: LOC=%d FIX=%d DATE=%d TIME=%d\n",
reader.location.age(),
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
gsafixtype.age(),
#else
0,
#endif
reader.date.age(), reader.time.age());
#endif // GPS_EXTRAVERBOSE
// check if a complete GPS solution set is available for reading
// tinyGPSDatum::age() also includes isValid() test
// FIXME
if (! ((reader.location.age() < GPS_SOL_EXPIRY_MS) &&
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
(gsafixtype.age() < GPS_SOL_EXPIRY_MS) &&
#endif
(reader.time.age() < GPS_SOL_EXPIRY_MS) &&
(reader.date.age() < GPS_SOL_EXPIRY_MS)))
{
DEBUG_MSG("SOME data is TOO OLD\n");
return false;
}
// Is this a new point or are we re-reading the previous one?
if (! reader.location.isUpdated())
return false;
// We know the solution is fresh and valid, so just read the data
auto loc = reader.location.value();
// Some GPSes (Air530) seem to send a zero longitude when the current fix is bogus
// Bail out EARLY to avoid overwriting previous good data (like #857)
if(toDegInt(loc.lat) == 0) {
DEBUG_MSG("Ignoring bogus NMEA position\n");
return false;
}
p.location_source = Position_LocSource_LOCSRC_GPS_INTERNAL;
// Dilution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
p.HDOP = reader.hdop.value();
p.PDOP = TinyGPSPlus::parseDecimal(gsapdop.value());
DEBUG_MSG("PDOP=%d, HDOP=%d\n", dop, reader.hdop.value());
#else
// FIXME! naive PDOP emulation (assumes VDOP==HDOP)
// correct formula is PDOP = SQRT(HDOP^2 + VDOP^2)
p.HDOP = reader.hdop.value();
p.PDOP = 1.41 * reader.hdop.value();
#endif
// Discard incomplete or erroneous readings
if (reader.hdop.value() == 0)
return false;
p.latitude_i = toDegInt(loc.lat);
p.longitude_i = toDegInt(loc.lng);
p.alt_geoid_sep = reader.geoidHeight.meters();
p.altitude_hae = reader.altitude.meters() + p.alt_geoid_sep;
p.altitude = reader.altitude.meters();
p.fix_quality = fixQual;
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
p.fix_type = fixType;
#endif
// positional timestamp
struct tm t;
t.tm_sec = reader.time.second();
t.tm_min = reader.time.minute();
t.tm_hour = reader.time.hour();
t.tm_mday = reader.date.day();
t.tm_mon = reader.date.month() - 1;
t.tm_year = reader.date.year() - 1900;
t.tm_isdst = false;
p.pos_timestamp = mktime(&t);
// Nice to have, if available
if (reader.satellites.isUpdated()) {
p.sats_in_view = reader.satellites.value();
setNumSatellites(reader.satellites.value());
}
if (reader.course.isUpdated() && reader.course.isValid()) {
if (reader.course.value() < 36000) { // sanity check
p.ground_track = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
} else {
DEBUG_MSG("BOGUS course.value() REJECTED: %d\n",
reader.course.value());
// Diminution of precision (an accuracy metric) is reported in 10^2 units, so we need to scale down when we use it
if (reader.hdop.isUpdated()) {
dop = reader.hdop.value();
}
if (reader.course.isUpdated()) {
heading = reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
}
if (reader.altitude.isUpdated())
altitude = reader.altitude.meters();
if (reader.location.isUpdated()) {
auto loc = reader.location.value();
latitude = toDegInt(loc.lat);
longitude = toDegInt(loc.lng);
// Some GPSes (Air530) seem to send a zero longitude when the current fix is bogus
if(longitude == 0)
DEBUG_MSG("Ignoring bogus NMEA position\n");
else {
foundLocation = true;
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, hdop=%g, heading=%f\n", latitude * 1e-7, longitude * 1e-7, altitude,
dop * 1e-2, heading * 1e-5);
}
}
/*
// REDUNDANT?
// expect gps pos lat=37.520825, lon=-122.309162, alt=158
DEBUG_MSG("new NMEA GPS pos lat=%f, lon=%f, alt=%d, dop=%g, heading=%f\n",
latitude * 1e-7, longitude * 1e-7, altitude, dop * 1e-2,
heading * 1e-5);
*/
return true;
return foundLocation;
}
bool NMEAGPS::hasLock()
{
// Using GPGGA fix quality indicator
if (fixQual >= 1 && fixQual <= 5) {
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// Use GPGSA fix type 2D/3D (better) if available
if (fixType == 3 || fixType == 0) // zero means "no data received"
#endif
return true;
}
return false;
}
bool NMEAGPS::whileIdle()
{
bool isValid = false;

View File

@@ -12,15 +12,6 @@
class NMEAGPS : public GPS
{
TinyGPSPlus reader;
uint8_t fixQual = 0; // fix quality from GPGGA
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
// (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field
// via optional feature "custom fields", currently disabled (bug #525)
TinyGPSCustom gsafixtype; // custom extract fix type from GPGSA
TinyGPSCustom gsapdop; // custom extract PDOP from GPGSA
uint8_t fixType = 0; // fix type from GPGSA
#endif
public:
virtual bool setupGPS();
@@ -47,6 +38,4 @@ class NMEAGPS : public GPS
* @return true if we've acquired a new location
*/
virtual bool lookForLocation();
virtual bool hasLock();
};

View File

@@ -5,15 +5,6 @@
#include "sleep.h"
#include <assert.h>
// if gps_update_interval below this value, do not powercycle the GPS
#define UBLOX_POWEROFF_THRESHOLD 90
#define PDOP_INVALID 9999
// #define UBX_MODE_NMEA
extern RadioConfig radioConfig;
UBloxGPS::UBloxGPS() {}
bool UBloxGPS::tryConnect()
@@ -50,22 +41,12 @@ bool UBloxGPS::setupGPS()
delay(500);
if (isConnected()) {
#ifdef UBX_MODE_NMEA
DEBUG_MSG("Connected to UBLOX GPS, downgrading to NMEA mode\n");
DEBUG_MSG("- GPS errors below are related and safe to ignore\n");
#else
DEBUG_MSG("Connected to UBLOX GPS successfully\n");
#endif
if (!setUBXMode())
RECORD_CRITICALERROR(CriticalErrorCode_UBloxInitFailed); // Don't halt the boot if saving the config fails, but do report the bug
#ifdef UBX_MODE_NMEA
return false;
#else
return true;
#endif
} else {
return false;
}
@@ -73,17 +54,6 @@ bool UBloxGPS::setupGPS()
bool UBloxGPS::setUBXMode()
{
#ifdef UBX_MODE_NMEA
if (_serial_gps) {
ublox.setUART1Output(COM_TYPE_NMEA, 1000);
}
if (i2cAddress) {
ublox.setI2COutput(COM_TYPE_NMEA, 1000);
}
return false; // pretend initialization failed to force NMEA mode
#endif
if (_serial_gps) {
if (!ublox.setUART1Output(COM_TYPE_UBX, 1000)) // Use native API
return false;
@@ -141,17 +111,19 @@ bool UBloxGPS::factoryReset()
/** Idle processing while GPS is looking for lock */
void UBloxGPS::whileActive()
{
ublox.flushPVT(); // reset ALL freshness flags first
ublox.getT(maxWait()); // ask for new time data - hopefully ready when we come back
// Ask for a new position fix - hopefully it will have results ready by next time
// the order here is important, because we only check for has latitude when reading
ublox.getSIV(maxWait());
ublox.getPDOP(maxWait());
ublox.getP(maxWait());
//ublox.getSIV(maxWait()); // redundant with getPDOP below
ublox.getPDOP(maxWait()); // will trigger getSOL on NEO6, getP on others
ublox.getP(maxWait()); // will trigger getPosLLH on NEO6, getP on others
// the fixType flag will be checked and updated in lookForLocation()
// Update fixtype
if (ublox.moduleQueried.fixType) {
fixType = ublox.getFixType(0);
// DEBUG_MSG("GPS fix type %d, numSats %d\n", fixType, numSatellites);
}
}
/**
@@ -192,110 +164,33 @@ bool UBloxGPS::lookForLocation()
{
bool foundLocation = false;
// check if a complete GPS solution set is available for reading
// (some of these, like lat/lon are redundant and can be removed)
if ( ! (ublox.moduleQueried.fixType &&
ublox.moduleQueried.latitude &&
ublox.moduleQueried.longitude &&
ublox.moduleQueried.altitude &&
ublox.moduleQueried.pDOP &&
ublox.moduleQueried.SIV &&
ublox.moduleQueried.gpsDay))
{
// Not ready? No problem! We'll try again later.
return false;
}
if (ublox.moduleQueried.SIV)
setNumSatellites(ublox.getSIV(0));
fixType = ublox.getFixType();
#ifdef UBLOX_EXTRAVERBOSE
DEBUG_MSG("FixType=%d\n", fixType);
#endif
if (ublox.moduleQueried.pDOP)
dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
// we only notify if position has changed due to a new fix
if ((fixType >= 3 && fixType <= 4)) {
if (ublox.moduleQueried.latitude) // rd fixes only
{
latitude = ublox.getLatitude(0);
longitude = ublox.getLongitude(0);
altitude = ublox.getAltitudeMSL(0) / 1000; // in mm convert to meters
// check if GPS has an acceptable lock
if (! hasLock()) {
ublox.flushPVT(); // reset ALL freshness flags
return false;
}
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
// don't read it here - it will generate an ignored getPVT command on the 6ms
// heading = ublox.getHeading(0);
// read lat/lon/alt/dop data into temporary variables to avoid
// overwriting global variables with potentially invalid data
int32_t tmp_dop = ublox.getPDOP(0); // PDOP (an accuracy metric) is reported in 10^2 units so we have to scale down when we use it
int32_t tmp_lat = ublox.getLatitude(0);
int32_t tmp_lon = ublox.getLongitude(0);
int32_t tmp_alt_msl = ublox.getAltitudeMSL(0);
int32_t tmp_alt_hae = ublox.getAltitude(0);
int32_t max_dop = PDOP_INVALID;
if (radioConfig.preferences.gps_max_dop)
max_dop = radioConfig.preferences.gps_max_dop * 100; // scaling
// Note: heading is only currently implmented in the ublox for the 8m chipset - therefore
// don't read it here - it will generate an ignored getPVT command on the 6ms
// heading = ublox.getHeading(0);
// read positional timestamp
struct tm t;
t.tm_sec = ublox.getSecond(0);
t.tm_min = ublox.getMinute(0);
t.tm_hour = ublox.getHour(0);
t.tm_mday = ublox.getDay(0);
t.tm_mon = ublox.getMonth(0) - 1;
t.tm_year = ublox.getYear(0) - 1900;
t.tm_isdst = false;
time_t tmp_ts = mktime(&t);
// FIXME - can opportunistically attempt to set RTC from GPS timestamp?
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
// FIXME - NULL ISLAND is a real location on Earth!
foundLocation = (tmp_lat != 0) && (tmp_lon != 0) &&
(tmp_lat <= 900000000) && (tmp_lat >= -900000000) &&
(tmp_dop < max_dop);
// only if entire dataset is valid, update globals from temp vars
if (foundLocation) {
p.location_source = Position_LocSource_LOCSRC_GPS_INTERNAL;
p.longitude_i = tmp_lon;
p.latitude_i = tmp_lat;
if (fixType > 2) {
// if fix is 2d, ignore altitude data
p.altitude = tmp_alt_msl / 1000;
p.altitude_hae = tmp_alt_hae / 1000;
p.alt_geoid_sep = (tmp_alt_hae - tmp_alt_msl) / 1000;
} else {
#ifdef GPS_EXTRAVERBOSE
DEBUG_MSG("no altitude data (fixType=%d)\n", fixType);
#endif
// clean up old values in case it's a 3d-2d fix transition
p.altitude = p.altitude_hae = p.alt_geoid_sep = 0;
// bogus lat lon is reported as 0 or 0 (can be bogus just for one)
// Also: apparently when the GPS is initially reporting lock it can output a bogus latitude > 90 deg!
foundLocation = (latitude != 0) && (longitude != 0) && (latitude <= 900000000 && latitude >= -900000000);
}
p.pos_timestamp = tmp_ts;
p.PDOP = tmp_dop;
p.fix_type = fixType;
p.sats_in_view = ublox.getSIV(0);
// In debug logs, identify position by @timestamp:stage (stage 1 = birth)
DEBUG_MSG("lookForLocation() new pos@%x:1\n", tmp_ts);
} else {
// INVALID solution - should never happen
DEBUG_MSG("Invalid location lat/lon/hae/dop %d/%d/%d/%d - discarded\n",
tmp_lat, tmp_lon, tmp_alt_hae, tmp_dop);
}
ublox.flushPVT(); // reset ALL freshness flags at the end
return foundLocation;
}
bool UBloxGPS::hasLock()
{
if (radioConfig.preferences.gps_accept_2d)
return (fixType >= 2 && fixType <= 4);
else
return (fixType >= 3 && fixType <= 4);
}
bool UBloxGPS::whileIdle()
{
// if using i2c or serial look too see if any chars are ready
@@ -306,20 +201,15 @@ bool UBloxGPS::whileIdle()
/// Note: ublox doesn't need a wake method, because as soon as we send chars to the GPS it will wake up
void UBloxGPS::sleep()
{
if (radioConfig.preferences.gps_update_interval > UBLOX_POWEROFF_THRESHOLD) {
// Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
ublox.powerOff();
// setGPSPower(false);
}
// Tell GPS to power down until we send it characters on serial port (we leave vcc connected)
ublox.powerOff();
// setGPSPower(false);
}
void UBloxGPS::wake()
{
if (radioConfig.preferences.gps_update_interval > UBLOX_POWEROFF_THRESHOLD) {
fixType = 0; // assume we have no fix yet
}
fixType = 0; // assume we hace no fix yet
// this is idempotent
setGPSPower(true);
// Note: no delay needed because now we leave gps power on always and instead use ublox.powerOff()

View File

@@ -54,7 +54,6 @@ class UBloxGPS : public GPS
* @return true if we've acquired a new location
*/
virtual bool lookForLocation();
virtual bool hasLock();
/// If possible force the GPS into sleep/low power mode
virtual void sleep();

View File

@@ -35,7 +35,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "plugins/TextMessagePlugin.h"
#include "target_specific.h"
#include "utils.h"
#include "gps/GeoCoord.h"
#ifndef NO_ESP32
#include "mesh/http/WiFiAPClient.h"
@@ -73,9 +72,6 @@ std::vector<MeshPlugin *> pluginFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
// GeoCoord object for the screen
GeoCoord geoCoord;
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
#endif
@@ -147,17 +143,6 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
drawIconScreen(region, display, state, x, y);
}
// Used on boot when a certificate is being created
static void drawSSLScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_SMALL);
display->drawString(64 + x, y, "Creating SSL certificate");
display->setFont(FONT_SMALL);
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait...");
}
#ifdef HAS_EINK
/// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -237,13 +222,6 @@ static void drawCriticalFaultFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawString(0 + x, FONT_HEIGHT_MEDIUM + y, "For help, please post on\nmeshtastic.discourse.group");
}
// Ignore messages orginating from phone (from the current node 0x0) unless range test or store and forward plugin are enabled
static bool shouldDrawMessage(const MeshPacket *packet) {
return packet->from != 0 &&
!radioConfig.preferences.range_test_plugin_enabled &&
!radioConfig.preferences.store_forward_plugin_enabled;
}
/// Draw the last text message we received
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -355,11 +333,6 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no
// Draw GPS status summary
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
if (radioConfig.preferences.fixed_position) {
// GPS coordinates are currently fixed
display->drawString(x - 1, y - 2, "Fixed GPS");
return;
}
if (!gps->getIsConnected()) {
display->drawString(x, y - 2, "No GPS");
return;
@@ -394,15 +367,15 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
String displayLine = "";
if (!gps->getIsConnected() && !radioConfig.preferences.fixed_position) {
if (!gps->getIsConnected()) {
// displayLine = "No GPS Module";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !radioConfig.preferences.fixed_position) {
} else if (!gps->getHasLock()) {
// displayLine = "No GPS Lock";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m";
displayLine = "Altitude: " + String(gps->getAltitude()) + "m";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
}
}
@@ -410,51 +383,76 @@ static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GP
// Draw GPS status coordinates
static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
auto gpsFormat = radioConfig.preferences.gps_format;
String displayLine = "";
if (!gps->getIsConnected() && !radioConfig.preferences.fixed_position) {
if (!gps->getIsConnected()) {
displayLine = "No GPS Module";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !radioConfig.preferences.fixed_position) {
} else if (!gps->getHasLock()) {
displayLine = "No GPS Lock";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else {
if (gpsFormat != GpsCoordinateFormat_GpsFormatDMS) {
char coordinateLine[22];
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
if (gpsFormat == GpsCoordinateFormat_GpsFormatDec) { // Decimal Degrees
sprintf(coordinateLine, "%f %f", geoCoord.getLatitude() * 1e-7, geoCoord.getLongitude() * 1e-7);
} else if (gpsFormat == GpsCoordinateFormat_GpsFormatUTM) { // Universal Transverse Mercator
sprintf(coordinateLine, "%2i%1c %06i %07i", geoCoord.getUTMZone(), geoCoord.getUTMBand(),
geoCoord.getUTMEasting(), geoCoord.getUTMNorthing());
} else if (gpsFormat == GpsCoordinateFormat_GpsFormatMGRS) { // Military Grid Reference System
sprintf(coordinateLine, "%2i%1c %1c%1c %05i %05i", geoCoord.getMGRSZone(), geoCoord.getMGRSBand(), geoCoord.getMGRSEast100k(),
geoCoord.getMGRSNorth100k(), geoCoord.getMGRSEasting(), geoCoord.getMGRSNorthing());
} else if (gpsFormat == GpsCoordinateFormat_GpsFormatOLC) { // Open Location Code
geoCoord.getOLCCode(coordinateLine);
} else if (gpsFormat == GpsCoordinateFormat_GpsFormatOSGR) { // Ordnance Survey Grid Reference
if (geoCoord.getOSGRE100k() == 'I' || geoCoord.getOSGRN100k() == 'I') // OSGR is only valid around the UK region
sprintf(coordinateLine, "%s", "Out of Boundary");
else
sprintf(coordinateLine, "%1c%1c %05i %05i", geoCoord.getOSGRE100k(),geoCoord.getOSGRN100k(),
geoCoord.getOSGREasting(), geoCoord.getOSGRNorthing());
}
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
} else {
char latLine[22];
char lonLine[22];
sprintf(latLine, "%2i° %2i' %2.4f\" %1c", geoCoord.getDMSLatDeg(), geoCoord.getDMSLatMin(), geoCoord.getDMSLatSec(),
geoCoord.getDMSLatCP());
sprintf(lonLine, "%3i° %2i' %2.4f\" %1c", geoCoord.getDMSLonDeg(), geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(),
geoCoord.getDMSLonCP());
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(latLine))) / 2, y - FONT_HEIGHT_SMALL * 1, latLine);
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(lonLine))) / 2, y, lonLine);
}
char coordinateLine[22];
sprintf(coordinateLine, "%f %f", gps->getLatitude() * 1e-7, gps->getLongitude() * 1e-7);
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(coordinateLine))) / 2, y, coordinateLine);
}
}
/// Ported from my old java code, returns distance in meters along the globe
/// surface (by magic?)
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
double pk = (180 / 3.14169);
double a1 = lat_a / pk;
double a2 = lng_a / pk;
double b1 = lat_b / pk;
double b2 = lng_b / pk;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
double t3 = sin(a1) * sin(b1);
double tt = acos(t1 + t2 + t3);
if (isnan(tt))
tt = 0.0; // Must have been the same point?
return (float)(6366000 * tt);
}
static inline double toRadians(double deg)
{
return deg * PI / 180;
}
static inline double toDegrees(double r)
{
return r * 180 / PI;
}
/**
* Computes the bearing in degrees between two points on Earth. Ported from my
* old Gaggle android app.
*
* @param lat1
* Latitude of the first point
* @param lon1
* Longitude of the first point
* @param lat2
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in radians. A value of 0 means due
* north.
*/
static float bearing(double lat1, double lon1, double lat2, double lon2)
{
double lat1Rad = toRadians(lat1);
double lat2Rad = toRadians(lat2);
double deltaLonRad = toRadians(lon2 - lon1);
double y = sin(deltaLonRad) * cos(lat2Rad);
double x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad));
return atan2(y, x);
}
namespace
{
@@ -515,11 +513,11 @@ static float estimatedHeading(double lat, double lon)
return b;
}
float d = GeoCoord::latLongToMeter(oldLat, oldLon, lat, lon);
float d = latLongToMeter(oldLat, oldLon, lat, lon);
if (d < 10) // haven't moved enough, just keep current bearing
return b;
b = GeoCoord::bearing(oldLat, oldLon, lat, lon);
b = bearing(oldLat, oldLon, lat, lon);
oldLat = lat;
oldLon = lon;
@@ -639,7 +637,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// display direction toward node
hasNodeHeading = true;
Position &p = node->position;
float d = GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
if (d < 2000)
snprintf(distStr, sizeof(distStr), "%.0f m", d);
else
@@ -647,7 +645,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
// FIXME, also keep the guess at the operators heading and add/substract
// it. currently we don't do this and instead draw north up only.
float bearingToOther = GeoCoord::bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
headingRadian = bearingToOther - myHeading;
drawNodeHeading(display, compassX, compassY, headingRadian);
}
@@ -805,8 +803,6 @@ void Screen::forceDisplay()
#endif
}
static uint32_t lastScreenTransition;
int32_t Screen::runOnce()
{
// If we don't have a screen, don't ever spend any CPU for us.
@@ -883,13 +879,8 @@ int32_t Screen::runOnce()
// standard screen switching is stopped.
if (showingNormalScreen) {
// standard screen loop handling here
if (radioConfig.preferences.auto_screen_carousel_secs > 0 &&
(millis() - lastScreenTransition) > (radioConfig.preferences.auto_screen_carousel_secs * 1000)) {
DEBUG_MSG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition));
handleOnPress();
}
}
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
// ui.getUiState()->frameState); If we are scrolling we need to be called
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
@@ -916,16 +907,6 @@ void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiStat
screen->debugInfo.drawFrameWiFi(display, state, x, y);
}
/* show a message that the SSL cert is being built
* it is expected that this will be used during the boot phase */
void Screen::setSSLFrames()
{
DEBUG_MSG("showing SSL frames\n");
static FrameCallback sslFrames[] = {drawSSLScreen};
ui.setFrames(sslFrames, 1);
ui.update();
}
// restore our regular frame list
void Screen::setFrames()
{
@@ -960,10 +941,9 @@ void Screen::setFrames()
if (myNodeInfo.error_code)
normalFrames[numframes++] = drawCriticalFaultFrame;
// If we have a text message - show it next, unless it's a phone message and we aren't using any special plugins
if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) {
// If we have a text message - show it next
if (devicestate.has_rx_text_message)
normalFrames[numframes++] = drawTextMessageFrame;
}
// then all the nodes
// We only show a few nodes in our scrolling list - because meshes with many nodes would have too many screens
@@ -1053,13 +1033,12 @@ void Screen::handlePrint(const char *text)
}
void Screen::handleOnPress()
{
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
if (ui.getUiState()->frameState == FIXED) {
ui.nextFrame();
DEBUG_MSG("Setting LastScreenTransition\n");
lastScreenTransition = millis();
setFastFramerate();
}
}
@@ -1351,8 +1330,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
#endif
// Line 3
if (radioConfig.preferences.gps_format != GpsCoordinateFormat_GpsFormatDMS) // if DMS then don't draw altitude
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
drawGPSAltitude(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
@@ -1394,7 +1372,8 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
return 0;
}
int Screen::handleTextMessage(const MeshPacket *packet) {
int Screen::handleTextMessage(const MeshPacket *arg)
{
if (showingNormalScreen) {
setFrames(); // Regen the list of screens (will show new text message)
}

View File

@@ -220,9 +220,6 @@ class Screen : public concurrency::OSThread
/// Used to force (super slow) eink displays to draw critical frames
void forceDisplay();
/// Draws our SSL cert screen during boot (called from WebServer)
void setSSLFrames();
protected:
/// Updates the UI.
//

View File

@@ -39,8 +39,6 @@
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
#include "LLCC68Interface.h"
#ifdef NRF52_SERIES
#include "variant.h"
@@ -493,10 +491,10 @@ void setup()
}
}
#ifdef SX126X_ANT_SW
// make analog PA vs not PA switch on SX126x eval board work properly
pinMode(SX126X_ANT_SW, OUTPUT);
digitalWrite(SX126X_ANT_SW, 1);
#ifdef SX1262_ANT_SW
// make analog PA vs not PA switch on SX1262 eval board work properly
pinMode(SX1262_ANT_SW, OUTPUT);
digitalWrite(SX1262_ANT_SW, 1);
#endif
// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init)
@@ -514,9 +512,9 @@ void setup()
}
#endif
#if defined(USE_SX1262)
#if defined(SX1262_CS)
if (!rIf) {
rIf = new SX1262Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI);
rIf = new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI);
if (!rIf->init()) {
DEBUG_MSG("Warning: Failed to find SX1262 radio\n");
delete rIf;
@@ -527,32 +525,6 @@ void setup()
}
#endif
#if defined(USE_SX1268)
if (!rIf) {
rIf = new SX1268Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI);
if (!rIf->init()) {
DEBUG_MSG("Warning: Failed to find SX1268 radio\n");
delete rIf;
rIf = NULL;
} else {
DEBUG_MSG("SX1268 Radio init succeeded, using SX1268 radio\n");
}
}
#endif
#if defined(USE_LLCC68)
if (!rIf) {
rIf = new LLCC68Interface(SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY, SPI);
if (!rIf->init()) {
DEBUG_MSG("Warning: Failed to find LLCC68 radio\n");
delete rIf;
rIf = NULL;
} else {
DEBUG_MSG("LLCC68 Radio init succeeded, using LLCC68 radio\n");
}
}
#endif
#ifdef USE_SIM_RADIO
if (!rIf) {
rIf = new SimRadio;

View File

@@ -17,7 +17,7 @@ ErrorCode FloodingRouter::send(MeshPacket *p)
return Router::send(p);
}
bool FloodingRouter::shouldFilterReceived(MeshPacket *p)
bool FloodingRouter::shouldFilterReceived(const MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
printPacket("Ignoring incoming msg, because we've already seen it", p);

View File

@@ -50,7 +50,7 @@ class FloodingRouter : public Router, protected PacketHistory
* Called immedately on receiption, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(MeshPacket *p);
virtual bool shouldFilterReceived(const MeshPacket *p);
/**
* Look for broadcasts we need to rebroadcast

View File

@@ -1,7 +0,0 @@
#include "SX126xInterface.h"
#include "SX126xInterface.cpp"
// We need this declaration for proper linking in derived classes
template class SX126xInterface<SX1262>;
template class SX126xInterface<SX1268>;
template class SX126xInterface<LLCC68>;

View File

@@ -1,9 +0,0 @@
#include "configuration.h"
#include "LLCC68Interface.h"
#include "error.h"
LLCC68Interface::LLCC68Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi)
: SX126xInterface(cs, irq, rst, busy, spi)
{
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include "SX126xInterface.h"
/**
* Our adapter for LLCC68 radios
* https://www.semtech.com/products/wireless-rf/lora-core/llcc68
* ⚠️⚠️⚠️
* Be aware that LLCC68 does not support Spreading Factor 12 (SF12) and will not work on the default "Long Slow" channel.
* You must change the channel if you get `Critical Error #3` with this module.
* ⚠️⚠️⚠️
*/
class LLCC68Interface : public SX126xInterface<LLCC68>
{
public:
LLCC68Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
};

View File

@@ -66,7 +66,7 @@ MeshPacket *MeshPlugin::allocErrorResponse(Routing_Error err, const MeshPacket *
return r;
}
void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
void MeshPlugin::callPlugins(const MeshPacket &mp)
{
// DEBUG_MSG("In call plugins\n");
bool pluginFound = false;
@@ -79,7 +79,6 @@ void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB.getNodeNum();
bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
for (auto i = plugins->begin(); i != plugins->end(); ++i) {
auto &pi = **i;
@@ -88,11 +87,6 @@ void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
/// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious)
bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
if ((src == RX_SRC_LOCAL) && !(pi.loopbackOk)) {
// new case, monitor separately for now, then FIXME merge above
wantsPacket = false;
}
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
if (wantsPacket) {
@@ -122,7 +116,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
printPacket("packet on wrong channel, but can't respond", &mp);
} else {
ProcessMessage handled = pi.handleReceived(mp);
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
@@ -146,7 +140,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
pi.myReply = NULL;
}
if (handled == ProcessMessage::STOP) {
if (handled) {
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
break;
}
@@ -175,9 +169,7 @@ void MeshPlugin::callPlugins(const MeshPacket &mp, RxSource src)
}
if (!pluginFound)
DEBUG_MSG("No plugins interested in portnum=%d, src=%s\n",
mp.decoded.portnum,
(src == RX_SRC_LOCAL) ? "LOCAL":"REMOTE");
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
}
MeshPacket *MeshPlugin::allocReply()

View File

@@ -9,18 +9,6 @@
#include <OLEDDisplayUi.h>
#endif
/** handleReceived return enumeration
*
* Use ProcessMessage::CONTINUE to allows other modules to process a message.
*
* Use ProcessMessage::STOP to stop further message processing.
*/
enum class ProcessMessage
{
CONTINUE = 0,
STOP = 1,
};
/** A baseclass for any mesh "plugin".
*
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
@@ -45,7 +33,7 @@ class MeshPlugin
/** For use only by MeshService
*/
static void callPlugins(const MeshPacket &mp, RxSource src = RX_SRC_RADIO);
static void callPlugins(const MeshPacket &mp);
static std::vector<MeshPlugin *> GetMeshPluginsWithUIFrames();
#ifndef NO_SCREEN
@@ -60,10 +48,6 @@ class MeshPlugin
*/
bool isPromiscuous = false;
/** Also receive a copy of LOCALLY GENERATED messages - most plugins should leave
* this setting disabled - see issue #877 */
bool loopbackOk = false;
/** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this
* flag */
bool encryptedOk = false;
@@ -103,9 +87,9 @@ class MeshPlugin
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp) { return ProcessMessage::CONTINUE; }
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.

View File

@@ -112,11 +112,7 @@ bool MeshService::reloadConfig()
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
void MeshService::reloadOwner()
{
// DEBUG_MSG("reloadOwner()\n");
// update our local data directly
nodeDB.updateUser(nodeDB.getNodeNum(), owner);
assert(nodeInfoPlugin);
// update everyone else
if (nodeInfoPlugin)
nodeInfoPlugin->sendOurNodeInfo();
nodeDB.saveToDisk();
@@ -144,7 +140,7 @@ void MeshService::handleToRadio(MeshPacket &p)
// Send the packet into the mesh
sendToMesh(packetPool.allocCopy(p), RX_SRC_USER);
sendToMesh(packetPool.allocCopy(p));
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
if (loopback) {
@@ -161,12 +157,12 @@ bool MeshService::cancelSending(PacketId id)
return router->cancelSending(nodeDB.getNodeNum(), id);
}
void MeshService::sendToMesh(MeshPacket *p, RxSource src)
void MeshService::sendToMesh(MeshPacket *p)
{
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
router->sendLocal(p, src);
router->sendLocal(p);
}
void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
@@ -213,39 +209,34 @@ NodeInfo *MeshService::refreshMyNodeInfo()
return node;
}
int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
{
// Update our local node info with our position (even if we don't decide to update anyone else)
NodeInfo *node = refreshMyNodeInfo();
Position pos = Position_init_default;
Position pos = node->position;
if (newStatus->getHasLock()) {
// load data from GPS object, will add timestamp + battery further down
pos = gps->p;
if (gps->hasLock()) {
if (gps->altitude != 0)
pos.altitude = gps->altitude;
pos.latitude_i = gps->latitude;
pos.longitude_i = gps->longitude;
} else {
// The GPS has lost lock, if we are fixed position we should just keep using
// the old position
#if GPS_EXTRAVERBOSE
DEBUG_MSG("onGPSchanged() - lost validLocation\n");
#endif
if (radioConfig.preferences.fixed_position) {
DEBUG_MSG("WARNING: Using fixed position\n");
pos = node->position;
} else {
// throw away old position
pos.latitude_i = 0;
pos.longitude_i = 0;
pos.altitude = 0;
}
}
// Finally add a fresh timestamp and battery level reading
// I KNOW this is redundant with refreshMyNodeInfo() above, but these are
// inexpensive nonblocking calls and can be refactored in due course
pos.time = getValidTime(RTCQualityGPS);
pos.battery_level = powerStatus->getBatteryChargePercent();
// In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB)
DEBUG_MSG("onGPSChanged() pos@%x:4, time=%u, lat=%d, bat=%d\n",
pos.pos_timestamp, pos.time, pos.latitude_i, pos.battery_level);
DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.time, pos.latitude_i, pos.battery_level);
// Update our current position in the local DB
nodeDB.updatePosition(nodeDB.getNodeNum(), pos, RX_SRC_LOCAL);
nodeDB.updatePosition(nodeDB.getNodeNum(), pos);
return 0;
}

View File

@@ -75,7 +75,7 @@ class MeshService
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
/// cache
void sendToMesh(MeshPacket *p, RxSource src = RX_SRC_LOCAL);
void sendToMesh(MeshPacket *p);
/** Attempt to cancel a previously sent packet from this _local_ node. Returns true if a packet was found we could cancel */
bool cancelSending(PacketId id);

View File

@@ -15,15 +15,6 @@ typedef uint32_t PacketId; // A packet sequence number
#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
/*
* Source of a received message
*/
enum RxSource {
RX_SRC_LOCAL, // message was generated locally
RX_SRC_RADIO, // message was received from radio mesh
RX_SRC_USER // message was received from end-user device
};
/**
* the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket.
*

View File

@@ -125,11 +125,6 @@ void NodeDB::installDefaultRadioConfig()
memset(&radioConfig, 0, sizeof(radioConfig));
radioConfig.has_preferences = true;
resetRadioConfig();
// for backward compat, default position flags are BAT+ALT+MSL (0x23 = 35)
radioConfig.preferences.position_flags = (PositionFlags_POS_BATTERY |
PositionFlags_POS_ALTITUDE | PositionFlags_POS_ALT_MSL);
}
void NodeDB::installDefaultChannels()
@@ -447,42 +442,25 @@ size_t NodeDB::getNumOnlineNodes()
/** Update position info for this node based on received position data
*/
void NodeDB::updatePosition(uint32_t nodeId, const Position &p, RxSource src)
void NodeDB::updatePosition(uint32_t nodeId, const Position &p)
{
NodeInfo *info = getOrCreateNode(nodeId);
if (src == RX_SRC_LOCAL) {
// Local packet, fully authoritative
DEBUG_MSG("updatePosition LOCAL pos@%x:5, time=%u, latI=%d, lonI=%d\n",
p.pos_timestamp, p.time, p.latitude_i, p.longitude_i);
info->position = p;
DEBUG_MSG("DB update position node=0x%x time=%u, latI=%d, lonI=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.pos_timestamp &&
!p.location_source) {
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
// (stop-gap fix for issue #900)
DEBUG_MSG("updatePosition SPECIAL time setting time=%u\n", p.time);
// 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 (p.time)
info->position.time = p.time;
} else {
// Be careful to only update fields that have been set by the REMOTE 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
//
// FIXME perhaps handle RX_SRC_USER separately?
DEBUG_MSG("updatePosition REMOTE node=0x%x time=%u, latI=%d, lonI=%d\n",
nodeId, p.time, p.latitude_i, p.longitude_i);
// First, back up fields that we want to protect from overwrite
uint32_t tmp_time = info->position.time;
// Next, update atomically
info->position = p;
// Last, restore any fields that may have been overwritten
if (! info->position.time)
info->position.time = tmp_time;
if (p.battery_level)
info->position.battery_level = p.battery_level;
if (p.latitude_i || p.longitude_i) {
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

View File

@@ -59,7 +59,7 @@ class NodeDB
/** Update position info for this node based on received position data
*/
void updatePosition(uint32_t nodeId, const Position &p, RxSource src = RX_SRC_RADIO);
void updatePosition(uint32_t nodeId, const Position &p);
/** Update user info for this node based on received user data
*/

View File

@@ -49,9 +49,9 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
private:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp)
virtual bool handleReceived(const MeshPacket &mp)
{
// FIXME - we currently update position data in the DB only if the message was a broadcast or destined to us
// it would be better to update even if the message was destined to others.
@@ -70,6 +70,6 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
DEBUG_MSG("Error decoding protobuf plugin!\n");
}
return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE;
return handleReceivedProtobuf(mp, decoded);
}
};

View File

@@ -67,15 +67,9 @@ bool RF95Interface::init()
#endif
setTransmitEnable(false);
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, currentLimit, preambleLength);
int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength);
DEBUG_MSG("RF95 init result %d\n", res);
// current limit was removed from module' ctor
// override default value (60 mA)
res = lora->setCurrentLimit(currentLimit);
DEBUG_MSG("Current limit set to %f\n", currentLimit);
DEBUG_MSG("Current limit set result %d\n", res);
if (res == ERR_NONE)
res = lora->setCRC(SX126X_LORA_CRC_ON);
@@ -119,7 +113,7 @@ bool RF95Interface::reconfigure()
err = lora->setPreambleLength(preambleLength);
assert(err == ERR_NONE);
err = lora->setFrequency(getFreq());
err = lora->setFrequency(freq);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);

View File

@@ -307,8 +307,6 @@ void RadioInterface::applyModemConfig()
if (bw == 31) // This parameter is not an integer
bw = 31.25;
if (bw == 62) // Fix for 62.5Khz bandwidth
bw = 62.5;
}
power = channelSettings.tx_power;
@@ -320,18 +318,18 @@ 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;
float freq = myRegion->freq + radioConfig.preferences.frequency_offset + myRegion->spacing * channel_num;
saveChannelNum(channel_num);
saveFreq(freq);
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);
DEBUG_MSG("Radio myRegion->spacing: %f\n", myRegion->spacing);
DEBUG_MSG("Radio myRegion->numChannels: %d\n", myRegion->numChannels);
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
DEBUG_MSG("Radio frequency: %f\n", getFreq()); // the frequency could be overridden in RadioInterface::getFreq() for some modules
DEBUG_MSG("Radio frequency: %f\n", freq);
DEBUG_MSG("Short packet time: %u msec\n", shortPacketMsec);
saveChannelNum(channel_num);
saveFreq(freq);
}
/**

View File

@@ -78,6 +78,8 @@ class RadioInterface
void deliverToReceiver(MeshPacket *p);
public:
float freq = 915.0;
/** pool is the pool we will alloc our rx packets from
*/
RadioInterface();
@@ -147,7 +149,7 @@ class RadioInterface
/**
* Get the frequency we saved.
*/
virtual float getFreq();
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; }

View File

@@ -87,8 +87,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* for a long time.
*/
const uint8_t syncWord = 0x2b;
float currentLimit = 100; // 100mA OCP - Should be acceptable for RFM95/SX127x chipset.
float currentLimit = 100; // FIXME
LockingModule module; // The HW interface to the radio

View File

@@ -10,21 +10,15 @@
RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {}
int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power,
int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint8_t currentLimit,
uint16_t preambleLength, uint8_t gain)
{
// execute common part
int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, preambleLength);
int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength);
if (state != ERR_NONE)
state = SX127x::begin(RF95_ALT_VERSION, syncWord, preambleLength);
state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength);
RADIOLIB_ASSERT(state);
// current limit was removed from module' ctor
// override default value (60 mA)
state = setCurrentLimit(currentLimit);
DEBUG_MSG("Current limit set to %f\n", currentLimit);
DEBUG_MSG("Current limit set result %d\n", state);
// configure settings not accessible by API
state = config();
RADIOLIB_ASSERT(state);

View File

@@ -35,6 +35,9 @@ class RadioLibRF95: public SX1278 {
\param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm.
\param currentLimit Trim value for OCP (over current protection) in mA. Can be set to multiplies of 5 in range 45 to 120 mA and to multiples of 10 in range 120 to 240 mA.
Set to 0 to disable OCP (not recommended).
\param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number.
Allowed values range from 6 to 65535.
@@ -43,7 +46,7 @@ class RadioLibRF95: public SX1278 {
\returns \ref status_codes
*/
int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint16_t preambleLength = 8, uint8_t gain = 0);
int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint8_t currentLimit = 100, uint16_t preambleLength = 8, uint8_t gain = 0);
// configuration methods
@@ -62,11 +65,6 @@ class RadioLibRF95: public SX1278 {
/// For debugging
uint8_t readReg(uint8_t addr);
protected:
// since default current limit for SX126x/127x in updated RadioLib is 60mA
// use the previous value
float currentLimit = 100;
#ifndef RADIOLIB_GODMODE
private:
#endif

View File

@@ -26,7 +26,7 @@ ErrorCode ReliableRouter::send(MeshPacket *p)
return FloodingRouter::send(p);
}
bool ReliableRouter::shouldFilterReceived(MeshPacket *p)
bool ReliableRouter::shouldFilterReceived(const MeshPacket *p)
{
// Note: do not use getFrom() here, because we want to ignore messages sent from phone
if (p->to == NODENUM_BROADCAST && p->from == getNodeNum()) {
@@ -54,17 +54,6 @@ bool ReliableRouter::shouldFilterReceived(MeshPacket *p)
}
}
/* send acks for repeated packets that want acks and are destined for us
* this way if an ACK is dropped and a packet is resent we'll ACK the resent packet
* make sure wasSeenRecently _doesn't_ update
* finding the channel requires decoding the packet. */
if (p->want_ack && (p->to == getNodeNum()) && wasSeenRecently(p, false)) {
if (perhapsDecode(p)) {
sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel);
DEBUG_MSG("acking a repeated want_ack packet\n");
}
}
return FloodingRouter::shouldFilterReceived(p);
}

View File

@@ -102,7 +102,7 @@ class ReliableRouter : public FloodingRouter
/**
* We hook this method so we can see packets before FloodingRouter says they should be discarded
*/
virtual bool shouldFilterReceived(MeshPacket *p);
virtual bool shouldFilterReceived(const MeshPacket *p);
/**
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.

View File

@@ -145,7 +145,7 @@ void Router::setReceivedMessage()
runASAP = true;
}
ErrorCode Router::sendLocal(MeshPacket *p, RxSource src)
ErrorCode Router::sendLocal(MeshPacket *p)
{
// No need to deliver externally if the destination is the local node
if (p->to == nodeDB.getNodeNum()) {
@@ -161,7 +161,7 @@ ErrorCode Router::sendLocal(MeshPacket *p, RxSource src)
// If we are sending a broadcast, we also treat it as if we just received it ourself
// this allows local apps (and PCs) to see broadcasts sourced locally
if (p->to == NODENUM_BROADCAST) {
handleReceived(p, src);
handleReceived(p);
}
return send(p);
@@ -324,7 +324,7 @@ NodeNum Router::getNodeNum()
* Handle any packet that is received by an interface on this node.
* Note: some packets may merely being passed through this node and will be forwarded elsewhere.
*/
void Router::handleReceived(MeshPacket *p, RxSource src)
void Router::handleReceived(MeshPacket *p)
{
// Also, we should set the time from the ISR and it should have msec level resolution
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
@@ -333,18 +333,13 @@ void Router::handleReceived(MeshPacket *p, RxSource src)
bool decoded = perhapsDecode(p);
if (decoded) {
// parsing was successful, queue for our recipient
if (src == RX_SRC_LOCAL)
printPacket("handleReceived(LOCAL)", p);
else if (src == RX_SRC_USER)
printPacket("handleReceived(USER)", p);
else
printPacket("handleReceived(REMOTE)", p);
printPacket("handleReceived", p);
} else {
printPacket("packet decoding failed (no PSK?)", p);
}
// call plugins here
MeshPlugin::callPlugins(*p, src);
MeshPlugin::callPlugins(*p);
}
void Router::perhapsHandleReceived(MeshPacket *p)

View File

@@ -45,7 +45,7 @@ class Router : protected concurrency::OSThread
*
* NOTE: This method will free the provided packet (even if we return an error code)
*/
ErrorCode sendLocal(MeshPacket *p, RxSource src = RX_SRC_RADIO);
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);
@@ -90,7 +90,7 @@ class Router : protected concurrency::OSThread
* Called immedately on receiption, before any further processing.
* @return true to abandon the packet
*/
virtual bool shouldFilterReceived(MeshPacket *p) { return false; }
virtual bool shouldFilterReceived(const MeshPacket *p) { return false; }
/**
* Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to
@@ -122,7 +122,7 @@ class Router : protected concurrency::OSThread
* Note: this packet will never be called for messages sent/generated by this node.
* Note: this method will free the provided packet.
*/
void handleReceived(MeshPacket *p, RxSource src = RX_SRC_RADIO);
void handleReceived(MeshPacket *p);
/** Frees the provided packet, and generates a NAK indicating the speicifed error while sending */
void abortSendAndNak(Routing_Error err, MeshPacket *p);

View File

@@ -2,8 +2,250 @@
#include "SX1262Interface.h"
#include "error.h"
// Particular boards might define a different max power based on what their hardware can do
#ifndef SX1262_MAX_POWER
#define SX1262_MAX_POWER 22
#endif
SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi)
: SX126xInterface(cs, irq, rst, busy, spi)
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
{
}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
bool SX1262Interface::init()
{
#ifdef SX1262_POWER_EN
digitalWrite(SX1262_POWER_EN, HIGH);
pinMode(SX1262_POWER_EN, OUTPUT);
#endif
#ifdef SX1262_RXEN // set not rx or tx mode
digitalWrite(SX1262_RXEN, LOW); // Set low before becoming an output
pinMode(SX1262_RXEN, OUTPUT);
#endif
#ifdef SX1262_TXEN
digitalWrite(SX1262_TXEN, LOW);
pinMode(SX1262_TXEN, OUTPUT);
#endif
#ifndef SX1262_E22
float tcxoVoltage = 0; // None - we use an XTAL
#else
// 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?
RadioLibInterface::init();
if (power == 0)
power = SX1262_MAX_POWER;
if (power > SX1262_MAX_POWER) // This chip has lower power limits than some
power = SX1262_MAX_POWER;
limitPower();
int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
DEBUG_MSG("SX1262 init result %d\n", res);
#ifdef SX1262_TXEN
// lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
if (res == ERR_NONE)
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);
if (res == ERR_NONE)
startReceive(); // start receiving
return res == ERR_NONE;
}
bool SX1262Interface::reconfigure()
{
RadioLibInterface::reconfigure();
// set mode to standby
setStandby();
// configure publicly accessible settings
int err = lora.setSpreadingFactor(sf);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora.setBandwidth(bw);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora.setCodingRate(cr);
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);
assert(err == ERR_NONE);
err = lora.setSyncWord(syncWord);
assert(err == ERR_NONE);
err = lora.setCurrentLimit(currentLimit);
assert(err == ERR_NONE);
err = lora.setPreambleLength(preambleLength);
assert(err == ERR_NONE);
err = lora.setFrequency(freq);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
if (power > 22) // This chip has lower power limits than some
power = 22;
err = lora.setOutputPower(power);
assert(err == ERR_NONE);
startReceive(); // restart receiving
return ERR_NONE;
}
void INTERRUPT_ATTR SX1262Interface::disableInterrupt()
{
lora.clearDio1Action();
}
void SX1262Interface::setStandby()
{
checkNotification(); // handle any pending interrupts before we force standby
int err = lora.standby();
assert(err == ERR_NONE);
#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn off RX and TX power
digitalWrite(SX1262_RXEN, LOW);
#endif
#ifdef SX1262_TXEN
digitalWrite(SX1262_TXEN, LOW);
#endif
isReceiving = false; // If we were receiving, not any more
disableInterrupt();
completeSending(); // If we were sending, not anymore
}
/**
* Add SNR data to received messages
*/
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.
*/
void SX1262Interface::configHardwareForSend()
{
#ifdef SX1262_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
digitalWrite(SX1262_TXEN, HIGH);
#endif
RadioLibInterface::configHardwareForSend();
}
// For power draw measurements, helpful to force radio to stay sleeping
// #define SLEEP_ONLY
void SX1262Interface::startReceive()
{
#ifdef SLEEP_ONLY
sleep();
#else
setStandby();
#ifdef SX1262_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
digitalWrite(SX1262_RXEN, HIGH);
#endif
// int err = lora.startReceive();
int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in
// standby mostly.
assert(err == ERR_NONE);
isReceiving = true;
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);
#endif
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool SX1262Interface::isActivelyReceiving()
{
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
// received and handled the interrupt for reading the packet/handling errors.
// FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
// never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
uint16_t irq = lora.getIrqStatus();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
// this is not correct - often always true - need to add an extra conditional
// size_t bytesPending = lora.getPacketLength();
// if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
return hasPreamble;
}
bool SX1262Interface::sleep()
{
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
DEBUG_MSG("sx1262 entering sleep mode (FIXME, don't keep config)\n");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = true;
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
#ifdef SX1262_POWER_EN
digitalWrite(SX1262_POWER_EN, LOW);
#endif
return true;
}

View File

@@ -1,12 +1,62 @@
#pragma once
#include "SX126xInterface.h"
#include "RadioLibInterface.h"
/**
* Our adapter for SX1262 radios
*/
class SX1262Interface : public SX126xInterface<SX1262>
class SX1262Interface : public RadioLibInterface
{
SX1262 lora;
public:
SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
/// 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();
/// Apply any radio provisioning changes
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure();
/// 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
*/
virtual void disableInterrupt();
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving();
/**
* Start waiting to receive a message
*/
virtual void startReceive();
/**
* We override to turn on transmitter power as needed.
*/
virtual void configHardwareForSend();
/**
* Add SNR data to received messages
*/
virtual void addReceiveMetadata(MeshPacket *mp);
virtual void setStandby();
private:
};

View File

@@ -1,9 +0,0 @@
#include "configuration.h"
#include "SX1268Interface.h"
#include "error.h"
SX1268Interface::SX1268Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi)
: SX126xInterface(cs, irq, rst, busy, spi)
{
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include "SX126xInterface.h"
/**
* Our adapter for SX1268 radios
*/
class SX1268Interface : public SX126xInterface<SX1268>
{
public:
/// override frequency of the SX1268 module regardless of the region (use EU433 value)
virtual float getFreq() { return 433.175f; }
SX1268Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
};

View File

@@ -1,269 +0,0 @@
#include "configuration.h"
#include "SX126xInterface.h"
#include "error.h"
// Particular boards might define a different max power based on what their hardware can do
#ifndef SX126X_MAX_POWER
#define SX126X_MAX_POWER 22
#endif
template<typename T>
SX126xInterface<T>::SX126xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi)
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
{
}
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
template<typename T>
bool SX126xInterface<T>::init()
{
#ifdef SX126X_POWER_EN
digitalWrite(SX126X_POWER_EN, HIGH);
pinMode(SX126X_POWER_EN, OUTPUT);
#endif
#ifdef SX126X_RXEN // set not rx or tx mode
digitalWrite(SX126X_RXEN, LOW); // Set low before becoming an output
pinMode(SX126X_RXEN, OUTPUT);
#endif
#ifdef SX126X_TXEN
digitalWrite(SX126X_TXEN, LOW);
pinMode(SX126X_TXEN, OUTPUT);
#endif
#ifndef SX126X_E22
float tcxoVoltage = 0; // None - we use an XTAL
#else
// 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?
RadioLibInterface::init();
if (power == 0)
power = SX126X_MAX_POWER;
if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
power = SX126X_MAX_POWER;
limitPower();
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x`
DEBUG_MSG("SX126x init result %d\n", res);
// current limit was removed from module' ctor
// override default value (60 mA)
res = lora.setCurrentLimit(currentLimit);
DEBUG_MSG("Current limit set to %f\n", currentLimit);
DEBUG_MSG("Current limit set result %d\n", res);
#ifdef SX126X_TXEN
// lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
if (res == ERR_NONE)
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);
if (res == ERR_NONE)
startReceive(); // start receiving
return res == ERR_NONE;
}
template<typename T>
bool SX126xInterface<T>::reconfigure()
{
RadioLibInterface::reconfigure();
// set mode to standby
setStandby();
// configure publicly accessible settings
int err = lora.setSpreadingFactor(sf);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora.setBandwidth(bw);
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
err = lora.setCodingRate(cr);
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);
assert(err == ERR_NONE);
err = lora.setSyncWord(syncWord);
assert(err == ERR_NONE);
err = lora.setCurrentLimit(currentLimit);
assert(err == ERR_NONE);
err = lora.setPreambleLength(preambleLength);
assert(err == ERR_NONE);
err = lora.setFrequency(getFreq());
if (err != ERR_NONE)
RECORD_CRITICALERROR(CriticalErrorCode_InvalidRadioSetting);
if (power > 22) // This chip has lower power limits than some
power = 22;
err = lora.setOutputPower(power);
assert(err == ERR_NONE);
startReceive(); // restart receiving
return ERR_NONE;
}
template<typename T>
void INTERRUPT_ATTR SX126xInterface<T>::disableInterrupt()
{
lora.clearDio1Action();
}
template<typename T>
void SX126xInterface<T>::setStandby()
{
checkNotification(); // handle any pending interrupts before we force standby
int err = lora.standby();
assert(err == ERR_NONE);
#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn off RX and TX power
digitalWrite(SX126X_RXEN, LOW);
#endif
#ifdef SX126X_TXEN
digitalWrite(SX126X_TXEN, LOW);
#endif
isReceiving = false; // If we were receiving, not any more
disableInterrupt();
completeSending(); // If we were sending, not anymore
}
/**
* Add SNR data to received messages
*/
template<typename T>
void SX126xInterface<T>::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.
*/
template<typename T>
void SX126xInterface<T>::configHardwareForSend()
{
#ifdef SX126X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
digitalWrite(SX126X_TXEN, HIGH);
#endif
RadioLibInterface::configHardwareForSend();
}
// For power draw measurements, helpful to force radio to stay sleeping
// #define SLEEP_ONLY
template<typename T>
void SX126xInterface<T>::startReceive()
{
#ifdef SLEEP_ONLY
sleep();
#else
setStandby();
#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
digitalWrite(SX126X_RXEN, HIGH);
#endif
// int err = lora.startReceive();
int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in
// standby mostly.
assert(err == ERR_NONE);
isReceiving = true;
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrRxLevel0);
#endif
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
template<typename T>
bool SX126xInterface<T>::isActivelyReceiving()
{
// The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
// received and handled the interrupt for reading the packet/handling errors.
// FIXME: it would be better to check for preamble, but we currently have our ISR not set to fire for packets that
// never even get a valid header, so we don't want preamble to get set and stay set due to noise on the network.
uint16_t irq = lora.getIrqStatus();
bool hasPreamble = (irq & SX126X_IRQ_HEADER_VALID);
// this is not correct - often always true - need to add an extra conditional
// size_t bytesPending = lora.getPacketLength();
// if (hasPreamble) DEBUG_MSG("rx hasPreamble\n");
return hasPreamble;
}
template<typename T>
bool SX126xInterface<T>::sleep()
{
// Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
// \todo Display actual typename of the adapter, not just `SX126x`
DEBUG_MSG("sx126x entering sleep mode (FIXME, don't keep config)\n");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
// lora.setTCXO(0);
// put chipset into sleep mode (we've already disabled interrupts by now)
bool keepConfig = true;
lora.sleep(keepConfig); // Note: we do not keep the config, full reinit will be needed
#ifdef SX126X_POWER_EN
digitalWrite(SX126X_POWER_EN, LOW);
#endif
return true;
}

View File

@@ -1,70 +0,0 @@
#pragma once
#include "RadioLibInterface.h"
/**
* \brief Adapter for SX126x radio family. Implements common logic for child classes.
* \tparam T RadioLib module type for SX126x: SX1262, SX1268.
*/
template<class T>
class SX126xInterface : public RadioLibInterface
{
public:
SX126xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
/// 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();
/// Apply any radio provisioning changes
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure();
/// 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:
float currentLimit = 140; // Higher OCP limit for SX126x PA
/**
* Specific module instance
*/
T lora;
/**
* Glue functions called from ISR land
*/
virtual void disableInterrupt();
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
/** are we actively receiving a packet (only called during receiving state) */
virtual bool isActivelyReceiving();
/**
* Start waiting to receive a message
*/
virtual void startReceive();
/**
* We override to turn on transmitter power as needed.
*/
virtual void configHardwareForSend();
/**
* Add SNR data to received messages
*/
virtual void addReceiveMetadata(MeshPacket *mp);
virtual void setStandby();
private:
};

View File

@@ -4,9 +4,9 @@
#ifndef PB_ADMIN_PB_H_INCLUDED
#define PB_ADMIN_PB_H_INCLUDED
#include <pb.h>
#include "channel.pb.h"
#include "mesh.pb.h"
#include "radioconfig.pb.h"
#include "channel.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
@@ -79,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 447
#define AdminMessage_size 397
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -12,7 +12,7 @@ PB_BIND(LegacyRadioConfig, LegacyRadioConfig, AUTO)
PB_BIND(LegacyRadioConfig_LegacyPreferences, LegacyRadioConfig_LegacyPreferences, AUTO)
PB_BIND(DeviceState, DeviceState, 4)
PB_BIND(DeviceState, DeviceState, 2)
PB_BIND(ChannelFile, ChannelFile, 2)

View File

@@ -4,8 +4,8 @@
#ifndef PB_DEVICEONLY_PB_H_INCLUDED
#define PB_DEVICEONLY_PB_H_INCLUDED
#include <pb.h>
#include "channel.pb.h"
#include "mesh.pb.h"
#include "channel.pb.h"
#include "radioconfig.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -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 9594
#define DeviceState_size 5190
#define ChannelFile_size 832
#ifdef __cplusplus

View File

@@ -49,6 +49,3 @@ PB_BIND(ToRadio_PeerInfo, ToRadio_PeerInfo, AUTO)

View File

@@ -29,28 +29,9 @@ typedef enum _HardwareModel {
HardwareModel_GENIEBLOCKS = 35,
HardwareModel_NRF52_UNKNOWN = 36,
HardwareModel_PORTDUINO = 37,
HardwareModel_ANDROID_SIM = 38,
HardwareModel_DIY_V1 = 39
HardwareModel_ANDROID_SIM = 38
} HardwareModel;
typedef enum _Team {
Team_CLEAR = 0,
Team_CYAN = 1,
Team_WHITE = 2,
Team_YELLOW = 3,
Team_ORANGE = 4,
Team_MAGENTA = 5,
Team_RED = 6,
Team_MAROON = 7,
Team_PURPLE = 8,
Team_DARK_BLUE = 9,
Team_BLUE = 10,
Team_TEAL = 11,
Team_GREEN = 12,
Team_DARK_GREEN = 13,
Team_BROWN = 14
} Team;
typedef enum _Constants {
Constants_Unused = 0,
Constants_DATA_PAYLOAD_LEN = 237
@@ -71,21 +52,6 @@ typedef enum _CriticalErrorCode {
CriticalErrorCode_RadioSpiBug = 11
} CriticalErrorCode;
typedef enum _Position_LocSource {
Position_LocSource_LOCSRC_UNSPECIFIED = 0,
Position_LocSource_LOCSRC_MANUAL_ENTRY = 1,
Position_LocSource_LOCSRC_GPS_INTERNAL = 2,
Position_LocSource_LOCSRC_GPS_EXTERNAL = 3
} Position_LocSource;
typedef enum _Position_AltSource {
Position_AltSource_ALTSRC_UNSPECIFIED = 0,
Position_AltSource_ALTSRC_MANUAL_ENTRY = 1,
Position_AltSource_ALTSRC_GPS_INTERNAL = 2,
Position_AltSource_ALTSRC_GPS_EXTERNAL = 3,
Position_AltSource_ALTSRC_BAROMETRIC = 4
} Position_AltSource;
typedef enum _Routing_Error {
Routing_Error_NONE = 0,
Routing_Error_NO_ROUTE = 1,
@@ -160,24 +126,6 @@ typedef struct _Position {
int32_t altitude;
int32_t battery_level;
uint32_t time;
Position_LocSource location_source;
Position_AltSource altitude_source;
uint32_t pos_timestamp;
int32_t pos_time_millis;
int32_t altitude_hae;
int32_t alt_geoid_sep;
uint32_t PDOP;
uint32_t HDOP;
uint32_t VDOP;
uint32_t gps_accuracy;
uint32_t ground_speed;
uint32_t ground_track;
uint32_t fix_quality;
uint32_t fix_type;
uint32_t sats_in_view;
uint32_t sensor_id;
uint32_t pos_next_update;
uint32_t pos_seq_number;
} Position;
typedef struct _RouteDiscovery {
@@ -197,10 +145,6 @@ typedef struct _User {
pb_byte_t macaddr[6];
HardwareModel hw_model;
bool is_licensed;
Team team;
uint32_t tx_power_dbm;
uint32_t ant_gain_dbi;
uint32_t ant_azimuth;
} User;
typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t;
@@ -267,12 +211,8 @@ typedef struct _ToRadio {
/* Helper constants for enums */
#define _HardwareModel_MIN HardwareModel_UNSET
#define _HardwareModel_MAX HardwareModel_DIY_V1
#define _HardwareModel_ARRAYSIZE ((HardwareModel)(HardwareModel_DIY_V1+1))
#define _Team_MIN Team_CLEAR
#define _Team_MAX Team_BROWN
#define _Team_ARRAYSIZE ((Team)(Team_BROWN+1))
#define _HardwareModel_MAX HardwareModel_ANDROID_SIM
#define _HardwareModel_ARRAYSIZE ((HardwareModel)(HardwareModel_ANDROID_SIM+1))
#define _Constants_MIN Constants_Unused
#define _Constants_MAX Constants_DATA_PAYLOAD_LEN
@@ -282,14 +222,6 @@ typedef struct _ToRadio {
#define _CriticalErrorCode_MAX CriticalErrorCode_RadioSpiBug
#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_RadioSpiBug+1))
#define _Position_LocSource_MIN Position_LocSource_LOCSRC_UNSPECIFIED
#define _Position_LocSource_MAX Position_LocSource_LOCSRC_GPS_EXTERNAL
#define _Position_LocSource_ARRAYSIZE ((Position_LocSource)(Position_LocSource_LOCSRC_GPS_EXTERNAL+1))
#define _Position_AltSource_MIN Position_AltSource_ALTSRC_UNSPECIFIED
#define _Position_AltSource_MAX Position_AltSource_ALTSRC_BAROMETRIC
#define _Position_AltSource_ARRAYSIZE ((Position_AltSource)(Position_AltSource_ALTSRC_BAROMETRIC+1))
#define _Routing_Error_MIN Routing_Error_NONE
#define _Routing_Error_MAX Routing_Error_NOT_AUTHORIZED
#define _Routing_Error_ARRAYSIZE ((Routing_Error)(Routing_Error_NOT_AUTHORIZED+1))
@@ -308,8 +240,8 @@ extern "C" {
#endif
/* Initializer values for message structs */
#define Position_init_default {0, 0, 0, 0, 0, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_default {"", "", "", {0}, _HardwareModel_MIN, 0, _Team_MIN, 0, 0, 0}
#define Position_init_default {0, 0, 0, 0, 0}
#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}
@@ -320,8 +252,8 @@ extern "C" {
#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, _Position_LocSource_MIN, _Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define User_init_zero {"", "", "", {0}, _HardwareModel_MIN, 0, _Team_MIN, 0, 0, 0}
#define Position_init_zero {0, 0, 0, 0, 0}
#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}
@@ -362,24 +294,6 @@ extern "C" {
#define Position_altitude_tag 3
#define Position_battery_level_tag 4
#define Position_time_tag 9
#define Position_location_source_tag 10
#define Position_altitude_source_tag 11
#define Position_pos_timestamp_tag 12
#define Position_pos_time_millis_tag 13
#define Position_altitude_hae_tag 14
#define Position_alt_geoid_sep_tag 15
#define Position_PDOP_tag 16
#define Position_HDOP_tag 17
#define Position_VDOP_tag 18
#define Position_gps_accuracy_tag 19
#define Position_ground_speed_tag 20
#define Position_ground_track_tag 21
#define Position_fix_quality_tag 22
#define Position_fix_type_tag 23
#define Position_sats_in_view_tag 24
#define Position_sensor_id_tag 25
#define Position_pos_next_update_tag 40
#define Position_pos_seq_number_tag 41
#define RouteDiscovery_route_tag 2
#define ToRadio_PeerInfo_app_version_tag 1
#define ToRadio_PeerInfo_mqtt_gateway_tag 2
@@ -389,10 +303,6 @@ extern "C" {
#define User_macaddr_tag 4
#define User_hw_model_tag 6
#define User_is_licensed_tag 7
#define User_team_tag 8
#define User_tx_power_dbm_tag 10
#define User_ant_gain_dbi_tag 11
#define User_ant_azimuth_tag 12
#define MeshPacket_from_tag 1
#define MeshPacket_to_tag 2
#define MeshPacket_channel_tag 3
@@ -431,25 +341,7 @@ X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 1) \
X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 2) \
X(a, STATIC, SINGULAR, INT32, altitude, 3) \
X(a, STATIC, SINGULAR, INT32, battery_level, 4) \
X(a, STATIC, SINGULAR, FIXED32, time, 9) \
X(a, STATIC, SINGULAR, UENUM, location_source, 10) \
X(a, STATIC, SINGULAR, UENUM, altitude_source, 11) \
X(a, STATIC, SINGULAR, FIXED32, pos_timestamp, 12) \
X(a, STATIC, SINGULAR, INT32, pos_time_millis, 13) \
X(a, STATIC, SINGULAR, SINT32, altitude_hae, 14) \
X(a, STATIC, SINGULAR, SINT32, alt_geoid_sep, 15) \
X(a, STATIC, SINGULAR, UINT32, PDOP, 16) \
X(a, STATIC, SINGULAR, UINT32, HDOP, 17) \
X(a, STATIC, SINGULAR, UINT32, VDOP, 18) \
X(a, STATIC, SINGULAR, UINT32, gps_accuracy, 19) \
X(a, STATIC, SINGULAR, UINT32, ground_speed, 20) \
X(a, STATIC, SINGULAR, UINT32, ground_track, 21) \
X(a, STATIC, SINGULAR, UINT32, fix_quality, 22) \
X(a, STATIC, SINGULAR, UINT32, fix_type, 23) \
X(a, STATIC, SINGULAR, UINT32, sats_in_view, 24) \
X(a, STATIC, SINGULAR, UINT32, sensor_id, 25) \
X(a, STATIC, SINGULAR, UINT32, pos_next_update, 40) \
X(a, STATIC, SINGULAR, UINT32, pos_seq_number, 41)
X(a, STATIC, SINGULAR, FIXED32, time, 9)
#define Position_CALLBACK NULL
#define Position_DEFAULT NULL
@@ -459,11 +351,7 @@ 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, BOOL, is_licensed, 7) \
X(a, STATIC, SINGULAR, UENUM, team, 8) \
X(a, STATIC, SINGULAR, UINT32, tx_power_dbm, 10) \
X(a, STATIC, SINGULAR, UINT32, ant_gain_dbi, 11) \
X(a, STATIC, SINGULAR, UINT32, ant_azimuth, 12)
X(a, STATIC, SINGULAR, BOOL, is_licensed, 7)
#define User_CALLBACK NULL
#define User_DEFAULT NULL
@@ -603,13 +491,13 @@ extern const pb_msgdesc_t ToRadio_PeerInfo_msg;
#define ToRadio_PeerInfo_fields &ToRadio_PeerInfo_msg
/* Maximum encoded size of messages (where known) */
#define Position_size 153
#define User_size 96
#define Position_size 37
#define User_size 76
#define RouteDiscovery_size 40
#define Routing_size 42
#define Data_size 260
#define MeshPacket_size 309
#define NodeInfo_size 270
#define NodeInfo_size 133
#define MyNodeInfo_size 101
#define LogRecord_size 81
#define FromRadio_size 318

View File

@@ -24,7 +24,6 @@ typedef enum _PortNum {
PortNum_STORE_FORWARD_APP = 65,
PortNum_RANGE_TEST_APP = 66,
PortNum_ENVIRONMENTAL_MEASUREMENT_APP = 67,
PortNum_ZPS_APP = 68,
PortNum_PRIVATE_APP = 256,
PortNum_ATAK_FORWARDER = 257,
PortNum_MAX = 511

View File

@@ -18,5 +18,3 @@ PB_BIND(RadioConfig_UserPreferences, RadioConfig_UserPreferences, 2)

View File

@@ -51,37 +51,14 @@ typedef enum _GpsOperation {
GpsOperation_GpsOpDisabled = 4
} GpsOperation;
typedef enum _GpsCoordinateFormat {
GpsCoordinateFormat_GpsFormatDec = 0,
GpsCoordinateFormat_GpsFormatDMS = 1,
GpsCoordinateFormat_GpsFormatUTM = 2,
GpsCoordinateFormat_GpsFormatMGRS = 3,
GpsCoordinateFormat_GpsFormatOLC = 4,
GpsCoordinateFormat_GpsFormatOSGR = 5
} GpsCoordinateFormat;
typedef enum _LocationSharing {
LocationSharing_LocUnset = 0,
LocationSharing_LocEnabled = 1,
LocationSharing_LocDisabled = 2
} LocationSharing;
typedef enum _PositionFlags {
PositionFlags_POS_UNDEFINED = 0,
PositionFlags_POS_ALTITUDE = 1,
PositionFlags_POS_ALT_MSL = 2,
PositionFlags_POS_GEO_SEP = 4,
PositionFlags_POS_DOP = 8,
PositionFlags_POS_HVDOP = 16,
PositionFlags_POS_BATTERY = 32,
PositionFlags_POS_SATINVIEW = 64,
PositionFlags_POS_SEQ_NOS = 128,
PositionFlags_POS_TIMESTAMP = 256
} PositionFlags;
typedef enum _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType {
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11 = 0,
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20 = 1
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11 = 0
} RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType;
/* Struct definitions */
@@ -101,7 +78,6 @@ typedef struct _RadioConfig_UserPreferences {
bool wifi_ap_mode;
RegionCode region;
ChargeCurrent charge_current;
bool position_broadcast_smart;
LocationSharing location_share;
GpsOperation gps_operation;
uint32_t gps_update_interval;
@@ -113,9 +89,6 @@ typedef struct _RadioConfig_UserPreferences {
float frequency_offset;
char mqtt_server[32];
bool mqtt_disabled;
GpsCoordinateFormat gps_format;
bool gps_accept_2d;
uint32_t gps_max_dop;
bool factory_reset;
bool debug_log_enabled;
pb_size_t ignore_incoming_count;
@@ -136,8 +109,6 @@ typedef struct _RadioConfig_UserPreferences {
uint32_t range_test_plugin_sender;
bool range_test_plugin_save;
uint32_t store_forward_plugin_records;
uint32_t store_forward_plugin_history_return_max;
uint32_t store_forward_plugin_history_return_window;
bool environmental_measurement_plugin_measurement_enabled;
bool environmental_measurement_plugin_screen_enabled;
uint32_t environmental_measurement_plugin_read_error_count_threshold;
@@ -147,10 +118,6 @@ typedef struct _RadioConfig_UserPreferences {
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType environmental_measurement_plugin_sensor_type;
uint32_t environmental_measurement_plugin_sensor_pin;
bool store_forward_plugin_enabled;
bool store_forward_plugin_heartbeat;
uint32_t position_flags;
bool is_always_powered;
uint32_t auto_screen_carousel_secs;
} RadioConfig_UserPreferences;
typedef struct _RadioConfig {
@@ -172,21 +139,13 @@ typedef struct _RadioConfig {
#define _GpsOperation_MAX GpsOperation_GpsOpDisabled
#define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1))
#define _GpsCoordinateFormat_MIN GpsCoordinateFormat_GpsFormatDec
#define _GpsCoordinateFormat_MAX GpsCoordinateFormat_GpsFormatOSGR
#define _GpsCoordinateFormat_ARRAYSIZE ((GpsCoordinateFormat)(GpsCoordinateFormat_GpsFormatOSGR+1))
#define _LocationSharing_MIN LocationSharing_LocUnset
#define _LocationSharing_MAX LocationSharing_LocDisabled
#define _LocationSharing_ARRAYSIZE ((LocationSharing)(LocationSharing_LocDisabled+1))
#define _PositionFlags_MIN PositionFlags_POS_UNDEFINED
#define _PositionFlags_MAX PositionFlags_POS_TIMESTAMP
#define _PositionFlags_ARRAYSIZE ((PositionFlags)(PositionFlags_POS_TIMESTAMP+1))
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MAX RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_ARRAYSIZE ((RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType)(RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20+1))
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MAX RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11
#define _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_ARRAYSIZE ((RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType)(RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11+1))
#ifdef __cplusplus
@@ -195,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, 0, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, _GpsCoordinateFormat_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, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0, 0, 0, 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, 0, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, _GpsCoordinateFormat_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, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0, 0, 0, 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
@@ -215,7 +174,6 @@ extern "C" {
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
#define RadioConfig_UserPreferences_region_tag 15
#define RadioConfig_UserPreferences_charge_current_tag 16
#define RadioConfig_UserPreferences_position_broadcast_smart_tag 17
#define RadioConfig_UserPreferences_location_share_tag 32
#define RadioConfig_UserPreferences_gps_operation_tag 33
#define RadioConfig_UserPreferences_gps_update_interval_tag 34
@@ -227,9 +185,6 @@ extern "C" {
#define RadioConfig_UserPreferences_frequency_offset_tag 41
#define RadioConfig_UserPreferences_mqtt_server_tag 42
#define RadioConfig_UserPreferences_mqtt_disabled_tag 43
#define RadioConfig_UserPreferences_gps_format_tag 44
#define RadioConfig_UserPreferences_gps_accept_2d_tag 45
#define RadioConfig_UserPreferences_gps_max_dop_tag 46
#define RadioConfig_UserPreferences_factory_reset_tag 100
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
@@ -249,8 +204,6 @@ extern "C" {
#define RadioConfig_UserPreferences_range_test_plugin_sender_tag 133
#define RadioConfig_UserPreferences_range_test_plugin_save_tag 134
#define RadioConfig_UserPreferences_store_forward_plugin_records_tag 137
#define RadioConfig_UserPreferences_store_forward_plugin_history_return_max_tag 138
#define RadioConfig_UserPreferences_store_forward_plugin_history_return_window_tag 139
#define RadioConfig_UserPreferences_environmental_measurement_plugin_measurement_enabled_tag 140
#define RadioConfig_UserPreferences_environmental_measurement_plugin_screen_enabled_tag 141
#define RadioConfig_UserPreferences_environmental_measurement_plugin_read_error_count_threshold_tag 142
@@ -260,10 +213,6 @@ extern "C" {
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_type_tag 146
#define RadioConfig_UserPreferences_environmental_measurement_plugin_sensor_pin_tag 147
#define RadioConfig_UserPreferences_store_forward_plugin_enabled_tag 148
#define RadioConfig_UserPreferences_store_forward_plugin_heartbeat_tag 149
#define RadioConfig_UserPreferences_position_flags_tag 150
#define RadioConfig_UserPreferences_is_always_powered_tag 151
#define RadioConfig_UserPreferences_auto_screen_carousel_secs_tag 152
#define RadioConfig_preferences_tag 1
/* Struct field encoding specification for nanopb */
@@ -289,7 +238,6 @@ X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
X(a, STATIC, SINGULAR, UENUM, region, 15) \
X(a, STATIC, SINGULAR, UENUM, charge_current, 16) \
X(a, STATIC, SINGULAR, BOOL, position_broadcast_smart, 17) \
X(a, STATIC, SINGULAR, UENUM, location_share, 32) \
X(a, STATIC, SINGULAR, UENUM, gps_operation, 33) \
X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \
@@ -301,9 +249,6 @@ 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, UENUM, gps_format, 44) \
X(a, STATIC, SINGULAR, BOOL, gps_accept_2d, 45) \
X(a, STATIC, SINGULAR, UINT32, gps_max_dop, 46) \
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) \
@@ -323,8 +268,6 @@ X(a, STATIC, SINGULAR, BOOL, range_test_plugin_enabled, 132) \
X(a, STATIC, SINGULAR, UINT32, range_test_plugin_sender, 133) \
X(a, STATIC, SINGULAR, BOOL, range_test_plugin_save, 134) \
X(a, STATIC, SINGULAR, UINT32, store_forward_plugin_records, 137) \
X(a, STATIC, SINGULAR, UINT32, store_forward_plugin_history_return_max, 138) \
X(a, STATIC, SINGULAR, UINT32, store_forward_plugin_history_return_window, 139) \
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_measurement_enabled, 140) \
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_screen_enabled, 141) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_read_error_count_threshold, 142) \
@@ -333,11 +276,7 @@ X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_recovery_int
X(a, STATIC, SINGULAR, BOOL, environmental_measurement_plugin_display_farenheit, 145) \
X(a, STATIC, SINGULAR, UENUM, environmental_measurement_plugin_sensor_type, 146) \
X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_sensor_pin, 147) \
X(a, STATIC, SINGULAR, BOOL, store_forward_plugin_enabled, 148) \
X(a, STATIC, SINGULAR, BOOL, store_forward_plugin_heartbeat, 149) \
X(a, STATIC, SINGULAR, UINT32, position_flags, 150) \
X(a, STATIC, SINGULAR, BOOL, is_always_powered, 151) \
X(a, STATIC, SINGULAR, UINT32, auto_screen_carousel_secs, 152)
X(a, STATIC, SINGULAR, BOOL, store_forward_plugin_enabled, 148)
#define RadioConfig_UserPreferences_CALLBACK NULL
#define RadioConfig_UserPreferences_DEFAULT NULL
@@ -349,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 444
#define RadioConfig_UserPreferences_size 441
#define RadioConfig_size 394
#define RadioConfig_UserPreferences_size 391
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -1,19 +0,0 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.4 */
#include "storeforward.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(StoreAndForward, StoreAndForward, AUTO)
PB_BIND(StoreAndForward_Statistics, StoreAndForward_Statistics, AUTO)
PB_BIND(StoreAndForward_History, StoreAndForward_History, AUTO)

View File

@@ -1,135 +0,0 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.4 */
#ifndef PB_STOREFORWARD_PB_H_INCLUDED
#define PB_STOREFORWARD_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _StoreAndForward_RequestResponse {
StoreAndForward_RequestResponse_UNSET = 0,
StoreAndForward_RequestResponse_ROUTER_ERROR = 1,
StoreAndForward_RequestResponse_ROUTER_HEARTBEAT = 2,
StoreAndForward_RequestResponse_ROUTER_PING = 3,
StoreAndForward_RequestResponse_ROUTER_PONG = 4,
StoreAndForward_RequestResponse_ROUTER_BUSY = 5,
StoreAndForward_RequestResponse_CLIENT_ERROR = 101,
StoreAndForward_RequestResponse_CLIENT_HISTORY = 102,
StoreAndForward_RequestResponse_CLIENT_STATS = 103,
StoreAndForward_RequestResponse_CLIENT_PING = 104,
StoreAndForward_RequestResponse_CLIENT_PONG = 105
} StoreAndForward_RequestResponse;
/* Struct definitions */
typedef struct _StoreAndForward_History {
uint32_t HistoryMessages;
uint32_t Window;
} StoreAndForward_History;
typedef struct _StoreAndForward_Statistics {
uint32_t MessagesTotal;
uint32_t MessagesSaved;
uint32_t MessagesMax;
uint32_t UpTime;
uint32_t Requests;
uint32_t RequestsHistory;
bool Heartbeat;
uint32_t ReturnMax;
uint32_t ReturnWindow;
} StoreAndForward_Statistics;
typedef struct _StoreAndForward {
StoreAndForward_RequestResponse rr;
bool has_stats;
StoreAndForward_Statistics stats;
bool has_history;
StoreAndForward_History history;
} StoreAndForward;
/* Helper constants for enums */
#define _StoreAndForward_RequestResponse_MIN StoreAndForward_RequestResponse_UNSET
#define _StoreAndForward_RequestResponse_MAX StoreAndForward_RequestResponse_CLIENT_PONG
#define _StoreAndForward_RequestResponse_ARRAYSIZE ((StoreAndForward_RequestResponse)(StoreAndForward_RequestResponse_CLIENT_PONG+1))
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define StoreAndForward_init_default {_StoreAndForward_RequestResponse_MIN, false, StoreAndForward_Statistics_init_default, false, StoreAndForward_History_init_default}
#define StoreAndForward_Statistics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define StoreAndForward_History_init_default {0, 0}
#define StoreAndForward_init_zero {_StoreAndForward_RequestResponse_MIN, false, StoreAndForward_Statistics_init_zero, false, StoreAndForward_History_init_zero}
#define StoreAndForward_Statistics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define StoreAndForward_History_init_zero {0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define StoreAndForward_History_HistoryMessages_tag 1
#define StoreAndForward_History_Window_tag 2
#define StoreAndForward_Statistics_MessagesTotal_tag 1
#define StoreAndForward_Statistics_MessagesSaved_tag 2
#define StoreAndForward_Statistics_MessagesMax_tag 3
#define StoreAndForward_Statistics_UpTime_tag 4
#define StoreAndForward_Statistics_Requests_tag 5
#define StoreAndForward_Statistics_RequestsHistory_tag 6
#define StoreAndForward_Statistics_Heartbeat_tag 7
#define StoreAndForward_Statistics_ReturnMax_tag 8
#define StoreAndForward_Statistics_ReturnWindow_tag 9
#define StoreAndForward_rr_tag 1
#define StoreAndForward_stats_tag 2
#define StoreAndForward_history_tag 3
/* Struct field encoding specification for nanopb */
#define StoreAndForward_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, rr, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, stats, 2) \
X(a, STATIC, OPTIONAL, MESSAGE, history, 3)
#define StoreAndForward_CALLBACK NULL
#define StoreAndForward_DEFAULT NULL
#define StoreAndForward_stats_MSGTYPE StoreAndForward_Statistics
#define StoreAndForward_history_MSGTYPE StoreAndForward_History
#define StoreAndForward_Statistics_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, MessagesTotal, 1) \
X(a, STATIC, SINGULAR, UINT32, MessagesSaved, 2) \
X(a, STATIC, SINGULAR, UINT32, MessagesMax, 3) \
X(a, STATIC, SINGULAR, UINT32, UpTime, 4) \
X(a, STATIC, SINGULAR, UINT32, Requests, 5) \
X(a, STATIC, SINGULAR, UINT32, RequestsHistory, 6) \
X(a, STATIC, SINGULAR, BOOL, Heartbeat, 7) \
X(a, STATIC, SINGULAR, UINT32, ReturnMax, 8) \
X(a, STATIC, SINGULAR, UINT32, ReturnWindow, 9)
#define StoreAndForward_Statistics_CALLBACK NULL
#define StoreAndForward_Statistics_DEFAULT NULL
#define StoreAndForward_History_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, HistoryMessages, 1) \
X(a, STATIC, SINGULAR, UINT32, Window, 2)
#define StoreAndForward_History_CALLBACK NULL
#define StoreAndForward_History_DEFAULT NULL
extern const pb_msgdesc_t StoreAndForward_msg;
extern const pb_msgdesc_t StoreAndForward_Statistics_msg;
extern const pb_msgdesc_t StoreAndForward_History_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define StoreAndForward_fields &StoreAndForward_msg
#define StoreAndForward_Statistics_fields &StoreAndForward_Statistics_msg
#define StoreAndForward_History_fields &StoreAndForward_History_msg
/* Maximum encoded size of messages (where known) */
#define StoreAndForward_size 68
#define StoreAndForward_Statistics_size 50
#define StoreAndForward_History_size 12
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -4,6 +4,7 @@
#include "airtime.h"
#include "main.h"
#include "mesh/http/ContentHelper.h"
#include "mesh/http/ContentStatic.h"
#include "mesh/http/WiFiAPClient.h"
#include "power.h"
#include "sleep.h"
@@ -78,17 +79,19 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
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);
ResourceNode *nodeStaticPOST = new ResourceNode("/static", "POST", &handleStaticPost);
ResourceNode *nodeStatic = new ResourceNode("/static/*", "GET", &handleStatic);
ResourceNode *nodeRestart = new ResourceNode("/restart", "POST", &handleRestart);
ResourceNode *node404 = new ResourceNode("", "GET", &handle404);
ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static", "GET", &handleSpiffsBrowseStatic);
ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic);
ResourceNode *nodeRoot = new ResourceNode("/*", "GET", &handleStatic);
// Secure nodes
secureServer->registerNode(nodeAPIv1ToRadioOptions);
@@ -96,6 +99,11 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeAPIv1FromRadio);
secureServer->registerNode(nodeHotspotApple);
secureServer->registerNode(nodeHotspotAndroid);
secureServer->registerNode(nodeFavicon);
secureServer->registerNode(nodeRoot);
secureServer->registerNode(nodeStaticBrowse);
secureServer->registerNode(nodeStaticPOST);
secureServer->registerNode(nodeStatic);
secureServer->registerNode(nodeRestart);
secureServer->registerNode(nodeFormUpload);
secureServer->registerNode(nodeJsonScanNetworks);
@@ -103,7 +111,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeJsonSpiffsBrowseStatic);
secureServer->registerNode(nodeJsonDelete);
secureServer->registerNode(nodeJsonReport);
secureServer->registerNode(nodeRoot);
secureServer->setDefaultNode(node404);
secureServer->addMiddleware(&middlewareSpeedUp240);
@@ -113,6 +121,11 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
insecureServer->registerNode(nodeAPIv1FromRadio);
insecureServer->registerNode(nodeHotspotApple);
insecureServer->registerNode(nodeHotspotAndroid);
insecureServer->registerNode(nodeFavicon);
insecureServer->registerNode(nodeRoot);
insecureServer->registerNode(nodeStaticBrowse);
insecureServer->registerNode(nodeStaticPOST);
insecureServer->registerNode(nodeStatic);
insecureServer->registerNode(nodeRestart);
insecureServer->registerNode(nodeFormUpload);
insecureServer->registerNode(nodeJsonScanNetworks);
@@ -120,7 +133,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
insecureServer->registerNode(nodeJsonSpiffsBrowseStatic);
insecureServer->registerNode(nodeJsonDelete);
insecureServer->registerNode(nodeJsonReport);
insecureServer->registerNode(nodeRoot);
insecureServer->setDefaultNode(node404);
insecureServer->addMiddleware(&middlewareSpeedUp160);
}
@@ -167,8 +180,11 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
/*
For documentation, see:
https://meshtastic.org/docs/developers/device/http-api
https://meshtastic.org/docs/developers/device/device-api
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
Example:
http://10.10.30.198/api/v1/fromradio
*/
// Get access to the parameters
@@ -217,20 +233,24 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
/*
For documentation, see:
https://meshtastic.org/docs/developers/device/http-api
https://meshtastic.org/docs/developers/device/device-api
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
Example:
http://10.10.30.198/api/v1/toradio
*/
// Status code is 200 OK by default.
res->setHeader("Content-Type", "application/x-protobuf");
res->setHeader("Access-Control-Allow-Headers", "Content-Type");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
// res->print(""); @todo remove
res->print("");
return;
}
@@ -244,8 +264,86 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n");
}
void handleFavicon(HTTPRequest *req, HTTPResponse *res)
{
// Set Content-Type
res->setHeader("Content-Type", "image/vnd.microsoft.icon");
// Write data from header file
res->write(FAVICON_DATA, FAVICON_LENGTH);
}
void handleStaticPost(HTTPRequest *req, HTTPResponse *res)
{
// Assume POST request. Contains submitted data.
res->println("<html><head><title>File Edited</title><meta http-equiv=\"refresh\" content=\"1;url=/static\" "
"/><head><body><h1>File Edited</h1>");
// The form is submitted with the x-www-form-urlencoded content type, so we need the
// HTTPURLEncodedBodyParser to read the fields.
// Note that the content of the file's content comes from a <textarea>, so we
// can use the URL encoding here, since no file upload from an <input type="file"
// is involved.
HTTPURLEncodedBodyParser parser(req);
// The bodyparser will consume the request body. That means you can iterate over the
// fields only ones. For that reason, we need to create variables for all fields that
// we expect. So when parsing is done, you can process the field values from your
// temporary variables.
std::string filename;
bool savedFile = false;
// Iterate over the fields from the request body by calling nextField(). This function
// will update the field name and value of the body parsers. If the last field has been
// reached, it will return false and the while loop stops.
while (parser.nextField()) {
// Get the field name, so that we can decide what the value is for
std::string name = parser.getFieldName();
if (name == "filename") {
// Read the filename from the field's value, add the /public prefix and store it in
// the filename variable.
char buf[512];
size_t readLength = parser.read((byte *)buf, 512);
// filename = std::string("/public/") + std::string(buf, readLength);
filename = std::string(buf, readLength);
} else if (name == "content") {
// Browsers must return the fields in the order that they are placed in
// the HTML form, so if the broweser behaves correctly, this condition will
// never be true. We include it for safety reasons.
if (filename == "") {
res->println("<p>Error: form contained content before filename.</p>");
break;
}
// With parser.read() and parser.endOfField(), we can stream the field content
// into a buffer. That allows handling arbitrarily-sized field contents. Here,
// we use it and write the file contents directly to the SPIFFS:
size_t fieldLength = 0;
File file = SPIFFS.open(filename.c_str(), "w");
savedFile = true;
while (!parser.endOfField()) {
byte buf[512];
size_t readLength = parser.read(buf, 512);
file.write(buf, readLength);
fieldLength += readLength;
}
file.close();
res->printf("<p>Saved %d bytes to %s</p>", int(fieldLength), filename.c_str());
} else {
res->printf("<p>Unexpected field %s</p>", name.c_str());
}
}
if (!savedFile) {
res->println("<p>No file to save...</p>");
}
res->println("</body></html>");
}
void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
{
// jm
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
@@ -324,6 +422,206 @@ void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
}
}
/*
To convert text to c strings:
https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
*/
void handleRoot(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "text/html");
res->setHeader("Set-Cookie",
"mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST");
std::string cookie = req->getHeader("Cookie");
// String cookieString = cookie.c_str();
// uint8_t nameIndex = cookieString.indexOf("mt_session");
// DEBUG_MSG(cookie.c_str());
std::string filename = "/static/index.html";
std::string filenameGzip = "/static/index.html.gz";
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
// Send "404 Not Found" as response, as the file doesn't seem to exist
res->setStatusCode(404);
res->setStatusText("Not found");
res->println("404 Not Found");
res->printf("<p>File not found: %s</p>\n", filename.c_str());
res->printf("<p></p>\n");
res->printf("<p>You have gotten this error because the filesystem for the web server has not been loaded.</p>\n");
res->printf("<p>Please review the 'Common Problems' section of the <a "
"href=https://github.com/meshtastic/Meshtastic-device/wiki/"
"How-to-use-the-Meshtastic-Web-Interface-over-WiFi>web interface</a> documentation.</p>\n");
return;
}
// Try to open the file from SPIFFS
File file;
if (SPIFFS.exists(filename.c_str())) {
file = SPIFFS.open(filename.c_str());
if (!file.available()) {
DEBUG_MSG("File not available - %s\n", filename.c_str());
}
} else if (SPIFFS.exists(filenameGzip.c_str())) {
file = SPIFFS.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
if (!file.available()) {
DEBUG_MSG("File not available\n");
}
}
// Read the file from SPIFFS and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
}
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res)
{
// Get access to the parameters
ResourceParameters *params = req->getParams();
std::string paramValDelete;
std::string paramValEdit;
DEBUG_MSG("Static Browse - Disabling keep-alive\n");
res->setHeader("Connection", "close");
// Set a default content type
res->setHeader("Content-Type", "text/html");
if (params->getQueryParameter("delete", paramValDelete)) {
std::string pathDelete = "/" + paramValDelete;
if (SPIFFS.remove(pathDelete.c_str())) {
Serial.println(pathDelete.c_str());
res->println("<html><head><meta http-equiv=\"refresh\" content=\"1;url=/static\" /><title>File "
"deleted!</title></head><body><h1>File deleted!</h1>");
res->println("<meta http-equiv=\"refresh\" 1;url=/static\" />\n");
res->println("</body></html>");
return;
} else {
Serial.println(pathDelete.c_str());
res->println("<html><head><meta http-equiv=\"refresh\" content=\"1;url=/static\" /><title>Error deleteing "
"file!</title></head><body><h1>Error deleteing file!</h1>");
res->println("Error deleteing file!<br>");
return;
}
}
if (params->getQueryParameter("edit", paramValEdit)) {
std::string pathEdit = "/" + paramValEdit;
res->println("<html><head><title>Edit "
"file</title></head><body><h1>Edit file - ");
res->println(pathEdit.c_str());
res->println("</h1>");
res->println("<form method=post action=/static enctype=application/x-www-form-urlencoded>");
res->printf("<input name=\"filename\" type=\"hidden\" value=\"%s\">", pathEdit.c_str());
res->print("<textarea id=id name=content rows=20 cols=80>");
// Try to open the file from SPIFFS
File file = SPIFFS.open(pathEdit.c_str());
if (file.available()) {
// Read the file from SPIFFS and write it to the HTTP response body
size_t length = 0;
do {
char buffer[256];
length = file.read((uint8_t *)buffer, 256);
std::string bufferString(buffer, length);
// Escape gt and lt
replaceAll(bufferString, "<", "&lt;");
replaceAll(bufferString, ">", "&gt;");
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
} while (length > 0);
} else {
res->println("Error: File not found");
}
res->println("</textarea><br>");
res->println("<input type=submit value=Submit>");
res->println("</form>");
res->println("</body></html>");
return;
}
res->println("<h2>Upload new file</h2>");
res->println("<p>This form allows you to upload files. Keep your filenames small and files under 200k.</p>");
res->println("<form method=\"POST\" action=\"/upload\" enctype=\"multipart/form-data\">");
res->println("file: <input type=\"file\" name=\"file\"><br>");
res->println("<input type=\"submit\" value=\"Upload\">");
res->println("</form>");
res->println("<h2>All Files</h2>");
File root = SPIFFS.open("/");
if (root.isDirectory()) {
res->println("<script type=\"text/javascript\">function confirm_delete() {return confirm('Are you sure?');}</script>");
res->println("<table>");
res->println("<tr>");
res->println("<td>File");
res->println("</td>");
res->println("<td>Size");
res->println("</td>");
res->println("<td colspan=2>Actions");
res->println("</td>");
res->println("</tr>");
File file = root.openNextFile();
while (file) {
String filePath = String(file.name());
if (filePath.indexOf("/static") == 0) {
res->println("<tr>");
res->println("<td>");
if (String(file.name()).substring(1).endsWith(".gz")) {
String modifiedFile = String(file.name()).substring(1);
modifiedFile.remove((modifiedFile.length() - 3), 3);
res->print("<a href=\"" + modifiedFile + "\">" + String(file.name()).substring(1) + "</a>");
} else {
res->print("<a href=\"" + String(file.name()).substring(1) + "\">" + String(file.name()).substring(1) +
"</a>");
}
res->println("</td>");
res->println("<td>");
res->print(String(file.size()));
res->println("</td>");
res->println("<td>");
res->print("<a href=\"/static?delete=" + String(file.name()).substring(1) +
"\" onclick=\"return confirm_delete()\">Delete</a> ");
res->println("</td>");
res->println("<td>");
if (!String(file.name()).substring(1).endsWith(".gz")) {
res->print("<a href=\"/static?edit=" + String(file.name()).substring(1) + "\">Edit</a>");
}
res->println("</td>");
res->println("</tr>");
}
file = root.openNextFile();
}
res->println("</table>");
res->print("<br>");
// res->print("Total : " + String(SPIFFS.totalBytes()) + " Bytes<br>");
res->print("Used : " + String(SPIFFS.usedBytes()) + " Bytes<br>");
res->print("Free : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()) + " Bytes<br>");
}
}
void handleStatic(HTTPRequest *req, HTTPResponse *res)
{
// Get access to the parameters
@@ -336,33 +634,35 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res)
std::string filename = "/static/" + parameter1;
std::string filenameGzip = "/static/" + parameter1 + ".gz";
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
// Send "404 Not Found" as response, as the file doesn't seem to exist
res->setStatusCode(404);
res->setStatusText("Not found");
res->println("404 Not Found");
res->printf("<p>File not found: %s</p>\n", filename.c_str());
return;
}
// Try to open the file from SPIFFS
File file;
bool has_set_content_type = false;
if (SPIFFS.exists(filename.c_str())) {
file = SPIFFS.open(filename.c_str());
if (!file.available()) {
DEBUG_MSG("File not available - %s\n", filename.c_str());
}
} else if (SPIFFS.exists(filenameGzip.c_str())) {
file = SPIFFS.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
if (!file.available()) {
DEBUG_MSG("File not available\n");
}
} else {
has_set_content_type = true;
filenameGzip = "/static/index.html.gz";
file = SPIFFS.open(filenameGzip.c_str());
res->setHeader("Content-Encoding", "gzip");
res->setHeader("Content-Type", "text/html");
}
res->setHeader("Content-Length", httpsserver::intToString(file.size()));
bool has_set_content_type = false;
// Content-Type is guessed using the definition of the contentTypes-table defined above
int cTypeIdx = 0;
do {
@@ -649,6 +949,30 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
res->println("}");
}
// --------
void handle404(HTTPRequest *req, HTTPResponse *res)
{
// Discard request body, if we received any
// We do this, as this is the default node and may also server POST/PUT requests
req->discardRequestBody();
// Set the response status
res->setStatusCode(404);
res->setStatusText("Not Found");
// Set content type of the response
res->setHeader("Content-Type", "text/html");
// Write a tiny HTTP page
res->println("<!DOCTYPE html>");
res->println("<html>");
res->println("<head><title>Not Found</title></head>");
res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>");
res->println("</html>");
}
/*
This supports the Apple Captive Network Assistant (CNA) Portal
*/

View File

@@ -5,18 +5,25 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer);
// Declare some handler functions for the various URLs on the server
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res);
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res);
void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
void handleHotspot(HTTPRequest *req, HTTPResponse *res);
void handleRoot(HTTPRequest *req, HTTPResponse *res);
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
void handleStatic(HTTPRequest *req, HTTPResponse *res);
void handleRestart(HTTPRequest *req, HTTPResponse *res);
void handle404(HTTPRequest *req, HTTPResponse *res);
void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
void handleReport(HTTPRequest *req, HTTPResponse *res);
void handleFavicon(HTTPRequest *req, HTTPResponse *res);
void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
uint32_t getTimeSpeedUp();
void setTimeSpeedUp();

View File

@@ -0,0 +1,119 @@
#include <Arduino.h>
#include <functional>
/*
This file contains static content.
*/
// Length of the binary data
const int FAVICON_LENGTH = 2238;
// Binary data for the favicon
const byte FAVICON_DATA[] = {
0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0xA8, 0x08, 0x00, 0x00, 0x16, 0x00, 0x00,
0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x84,
0xDC, 0x3D, 0x00, 0x84, 0xDC, 0x3C, 0x00, 0x85, 0xDC, 0x3F, 0x00, 0x86, 0xDD, 0x40, 0x00, 0x83, 0xDC, 0x3C, 0x00, 0x85, 0xDC,
0x3E, 0x00, 0x82, 0xDC, 0x3A, 0x00, 0x8B, 0xDE, 0x49, 0x00, 0x84, 0xDB, 0x3E, 0x00, 0x82, 0xD9, 0x3C, 0x00, 0x89, 0xDD, 0x45,
0x00, 0x83, 0xDB, 0x3C, 0x00, 0x83, 0xD8, 0x3D, 0x00, 0x81, 0xD8, 0x3A, 0x00, 0x8D, 0xE0, 0x49, 0x00, 0x88, 0xE4, 0x3F, 0x00,
0x89, 0xE9, 0x3E, 0x00, 0x84, 0xD8, 0x40, 0x00, 0x85, 0xDF, 0x3C, 0x00, 0x8E, 0xF2, 0x40, 0x00, 0x8D, 0xF6, 0x3D, 0x00, 0x90,
0xEA, 0x49, 0x00, 0x82, 0xD5, 0x3E, 0x00, 0x78, 0xC1, 0x3A, 0x00, 0x90, 0xE9, 0x4A, 0x00, 0x8E, 0xF5, 0x3D, 0x00, 0x84, 0xDD,
0x3D, 0x00, 0x91, 0xF7, 0x43, 0x00, 0x87, 0xE5, 0x3D, 0x00, 0x6C, 0xA2, 0x38, 0x00, 0x53, 0x65, 0x31, 0x00, 0x41, 0x39, 0x2E,
0x00, 0x3A, 0x27, 0x2B, 0x00, 0x34, 0x1A, 0x2A, 0x00, 0x41, 0x38, 0x2E, 0x00, 0x82, 0xD8, 0x3D, 0x00, 0x88, 0xE7, 0x3D, 0x00,
0x8E, 0xF3, 0x41, 0x00, 0x69, 0x95, 0x39, 0x00, 0x3E, 0x33, 0x2C, 0x00, 0x31, 0x11, 0x29, 0x00, 0x2E, 0x0A, 0x29, 0x00, 0x2D,
0x0B, 0x27, 0x00, 0x30, 0x10, 0x29, 0x00, 0x34, 0x18, 0x2A, 0x00, 0x3E, 0x31, 0x2C, 0x00, 0x68, 0x95, 0x39, 0x00, 0x88, 0xE7,
0x3E, 0x00, 0x82, 0xD7, 0x3C, 0x00, 0x84, 0xDD, 0x3C, 0x00, 0x8B, 0xEE, 0x3E, 0x00, 0x85, 0xDF, 0x3D, 0x00, 0x47, 0x48, 0x2E,
0x00, 0x30, 0x0F, 0x29, 0x00, 0x31, 0x13, 0x29, 0x00, 0x48, 0x4D, 0x2E, 0x00, 0x61, 0x7F, 0x39, 0x00, 0x6A, 0x9C, 0x38, 0x00,
0x75, 0xB8, 0x39, 0x00, 0x85, 0xDE, 0x3D, 0x00, 0x8C, 0xEF, 0x3E, 0x00, 0x89, 0xDE, 0x44, 0x00, 0x80, 0xD1, 0x3C, 0x00, 0x3A,
0x28, 0x2C, 0x00, 0x32, 0x16, 0x2A, 0x00, 0x33, 0x17, 0x2A, 0x00, 0x4B, 0x50, 0x30, 0x00, 0x76, 0xBA, 0x3A, 0x00, 0x8A, 0xEF,
0x3C, 0x00, 0x9A, 0xFE, 0x4D, 0x00, 0x95, 0xFF, 0x43, 0x00, 0x93, 0xFF, 0x40, 0x00, 0x4B, 0x52, 0x30, 0x00, 0x7E, 0xCE, 0x3C,
0x00, 0x87, 0xD9, 0x44, 0x00, 0x34, 0x1B, 0x2A, 0x00, 0x65, 0x90, 0x36, 0x00, 0x8E, 0xF6, 0x3D, 0x00, 0x8F, 0xF7, 0x40, 0x00,
0x8B, 0xDD, 0x48, 0x00, 0x73, 0xB1, 0x3A, 0x00, 0x66, 0x95, 0x35, 0x00, 0x66, 0x93, 0x35, 0x00, 0x35, 0x1B, 0x2A, 0x00, 0x8D,
0xE8, 0x45, 0x00, 0x82, 0xD9, 0x3B, 0x00, 0x72, 0xAA, 0x3C, 0x00, 0x95, 0xFD, 0x46, 0x00, 0x8D, 0xF0, 0x40, 0x00, 0x57, 0x70,
0x32, 0x00, 0x3C, 0x2D, 0x2C, 0x00, 0x2F, 0x0D, 0x29, 0x00, 0x81, 0xD4, 0x3D, 0x00, 0x8D, 0xF1, 0x40, 0x00, 0x94, 0xFC, 0x46,
0x00, 0x73, 0xAE, 0x3D, 0x00, 0x45, 0x44, 0x2D, 0x00, 0x94, 0xF5, 0x49, 0x00, 0x90, 0xF0, 0x45, 0x00, 0x73, 0xAF, 0x3B, 0x00,
0x38, 0x21, 0x2C, 0x00, 0x30, 0x11, 0x29, 0x00, 0x2F, 0x0F, 0x28, 0x00, 0x72, 0xAC, 0x3B, 0x00, 0x6A, 0x93, 0x3D, 0x00, 0x2E,
0x0D, 0x27, 0x00, 0x35, 0x1C, 0x2B, 0x00, 0x36, 0x20, 0x2A, 0x00, 0x5E, 0x77, 0x39, 0x00, 0x78, 0xBE, 0x3B, 0x00, 0x36, 0x21,
0x2A, 0x00, 0x71, 0xAB, 0x3B, 0x00, 0x4C, 0x54, 0x30, 0x00, 0x3D, 0x31, 0x2B, 0x00, 0x82, 0xD6, 0x3D, 0x00, 0x79, 0xC5, 0x39,
0x00, 0x9A, 0xFF, 0x4D, 0x00, 0x8A, 0xE8, 0x40, 0x00, 0x8A, 0xE7, 0x40, 0x00, 0x7A, 0xC6, 0x39, 0x00, 0x3D, 0x2E, 0x2C, 0x00,
0x81, 0xD5, 0x3D, 0x00, 0x77, 0xBC, 0x3A, 0x00, 0x31, 0x12, 0x2A, 0x00, 0x69, 0x9B, 0x37, 0x00, 0x8E, 0xF3, 0x40, 0x00, 0x83,
0xDC, 0x3B, 0x00, 0x8C, 0xF6, 0x3B, 0x00, 0x88, 0xD9, 0x45, 0x00, 0x86, 0xE1, 0x3D, 0x00, 0x85, 0xE0, 0x3D, 0x00, 0x7B, 0xC8,
0x39, 0x00, 0x36, 0x1F, 0x29, 0x00, 0x55, 0x6B, 0x32, 0x00, 0x8A, 0xEE, 0x3C, 0x00, 0x48, 0x4B, 0x2E, 0x00, 0x51, 0x61, 0x31,
0x00, 0x8C, 0xE0, 0x48, 0x00, 0x8B, 0xDE, 0x47, 0x00, 0x98, 0xEE, 0x55, 0x00, 0x5D, 0x79, 0x36, 0x00, 0x3A, 0x2A, 0x2B, 0x00,
0x3A, 0x29, 0x2B, 0x00, 0x5C, 0x78, 0x36, 0x00, 0x60, 0x7C, 0x3A, 0x00, 0x3D, 0x30, 0x2C, 0x00, 0x99, 0xFD, 0x4C, 0x00, 0x66,
0x8A, 0x3C, 0x00, 0x2D, 0x0C, 0x27, 0x00, 0x42, 0x3C, 0x2E, 0x00, 0x84, 0xDA, 0x3E, 0x00, 0x88, 0xE5, 0x3F, 0x00, 0x37, 0x22,
0x2B, 0x00, 0x2E, 0x0B, 0x28, 0x00, 0x6A, 0x9B, 0x37, 0x00, 0x72, 0xAF, 0x3A, 0x00, 0x32, 0x15, 0x29, 0x00, 0x2A, 0x00, 0x28,
0x00, 0x5B, 0x75, 0x35, 0x00, 0x89, 0xE8, 0x3D, 0x00, 0x78, 0xBF, 0x3A, 0x00, 0x73, 0xB4, 0x38, 0x00, 0x83, 0xDA, 0x3C, 0x00,
0x84, 0xDE, 0x3C, 0x00, 0x85, 0xDD, 0x3E, 0x00, 0x86, 0xDE, 0x40, 0x00, 0x84, 0xDE, 0x3B, 0x00, 0x86, 0xE2, 0x3C, 0x00, 0x85,
0xDD, 0x3F, 0x00, 0x87, 0xE2, 0x3F, 0x00, 0x87, 0xE1, 0x3E, 0x00, 0x85, 0xDE, 0x3E, 0x00, 0x89, 0xE2, 0x41, 0x00, 0x89, 0xE2,
0x43, 0x00, 0x84, 0xDC, 0x3E, 0x00, 0x83, 0xD8, 0x3E, 0x00, 0x90, 0xF6, 0x41, 0x00, 0x2B, 0x04, 0x28, 0x00, 0x8C, 0xE3, 0x47,
0x00, 0x8B, 0xDE, 0x48, 0x00, 0x8A, 0xDC, 0x47, 0x00, 0x8A, 0xDD, 0x47, 0x00, 0x8D, 0xDD, 0x4A, 0x00, 0x8A, 0xDE, 0x47, 0x00,
0x8B, 0xDD, 0x49, 0x00, 0x8B, 0xE0, 0x46, 0x00, 0x9A, 0xF2, 0x55, 0x00, 0x59, 0x70, 0x35, 0x00, 0x8F, 0xDE, 0x4F, 0x00, 0x82,
0xDC, 0x3B, 0x00, 0x82, 0xDB, 0x39, 0x00, 0x7F, 0xD7, 0x38, 0x00, 0x92, 0xF0, 0x48, 0x00, 0x33, 0x19, 0x29, 0x00, 0x87, 0xDD,
0x42, 0x00, 0x87, 0xDD, 0x41, 0x00, 0x92, 0xEC, 0x4B, 0x00, 0x78, 0xBD, 0x3C, 0x00, 0x86, 0xDD, 0x3F, 0x00, 0x81, 0xD9, 0x39,
0x00, 0x7B, 0xC4, 0x3C, 0x00, 0x34, 0x1A, 0x29, 0x00, 0x89, 0xDD, 0x44, 0x00, 0x86, 0xDC, 0x40, 0x00, 0x88, 0xDD, 0x44, 0x00,
0x87, 0xDE, 0x41, 0x00, 0x99, 0xFA, 0x4F, 0x00, 0x7B, 0xC3, 0x3E, 0x00, 0x83, 0xD7, 0x3F, 0x00, 0x8B, 0xED, 0x3E, 0x00, 0x40,
0x33, 0x2F, 0x00, 0x39, 0x27, 0x2B, 0x00, 0x81, 0xD7, 0x3B, 0x00, 0x3B, 0x2C, 0x2A, 0x00, 0x33, 0x18, 0x29, 0x00, 0x38, 0x22,
0x2B, 0x00, 0x85, 0xDA, 0x40, 0x00, 0x89, 0xEA, 0x3D, 0x00, 0x6F, 0xA9, 0x38, 0x00, 0x70, 0xAB, 0x38, 0x00, 0x85, 0xDD, 0x3D,
0x00, 0x88, 0xE1, 0x40, 0x00, 0x36, 0x1F, 0x2B, 0x00, 0x30, 0x13, 0x28, 0x00, 0x68, 0x9A, 0x36, 0x00, 0x90, 0xFB, 0x3F, 0x00,
0x8A, 0xDD, 0x46, 0x00, 0x8D, 0xE9, 0x45, 0x00, 0x5A, 0x71, 0x36, 0x00, 0x27, 0x00, 0x24, 0x00, 0x73, 0x9F, 0x45, 0x00, 0x97,
0xFE, 0x4A, 0x00, 0x86, 0xD8, 0x43, 0x00, 0x73, 0xA1, 0x45, 0x00, 0x8E, 0xDF, 0x4C, 0x00, 0x85, 0xDB, 0x40, 0x00, 0x72, 0xB5,
0x37, 0x00, 0x95, 0xF4, 0x4B, 0x00, 0x73, 0xB6, 0x37, 0x00, 0x88, 0xE9, 0x3C, 0x00, 0x8A, 0xDB, 0x48, 0x00, 0x8C, 0xDE, 0x49,
0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0xAE, 0x06, 0xF1, 0x02, 0x04, 0x04, 0x02, 0xF1, 0x06, 0xAE, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01,
0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0xAE, 0xC7, 0xF1, 0x02, 0x04,
0x04, 0x02, 0xF1, 0xC7, 0xAE, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x02, 0x03, 0x0B, 0xAE, 0xEF, 0xF0, 0x02, 0x01, 0x01, 0x02, 0xB4, 0xEF, 0xAE, 0x0B, 0x03, 0x02, 0x01, 0x00,
0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, 0xEB, 0xA7, 0xD1, 0xEC, 0xED, 0x96,
0x04, 0x04, 0x96, 0xED, 0xEE, 0xD1, 0xA7, 0xEB, 0x05, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0xCA, 0xCA, 0xCA, 0xCA,
0xCA, 0xCA, 0xCA, 0xCC, 0xE2, 0x8A, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE8, 0xE7, 0xE9, 0xE5, 0xBB, 0xE3, 0x8A, 0xE2, 0xCC,
0xCA, 0xCC, 0xEA, 0xCC, 0xCA, 0xCA, 0xCA, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x08, 0xDD, 0x55, 0xDE, 0x2C, 0xDF,
0xE0, 0xE1, 0xE1, 0xE0, 0xDF, 0x2C, 0xDE, 0x55, 0xDD, 0x08, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0xC6, 0xD8, 0xD9, 0xAB, 0x78, 0x6A, 0x28, 0xDA, 0xDB, 0x28, 0x6A, 0x5A, 0xDC, 0xD9, 0xD8, 0xC6,
0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, 0x03, 0xD4, 0xD1, 0x31,
0xD5, 0xD6, 0x98, 0xD7, 0xD6, 0xD5, 0x0B, 0x32, 0xD4, 0x03, 0x05, 0x04, 0x01, 0x04, 0xCA, 0x04, 0x01, 0x01, 0x01, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x05, 0xC3, 0xC2, 0xA4, 0xD0, 0xD1, 0xB3, 0xD2, 0xD3, 0xD3, 0xD2, 0x4F, 0x32, 0xD0, 0xA4, 0xC2,
0xC3, 0x05, 0x02, 0x05, 0xB7, 0x05, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xCB, 0xC2, 0xCC, 0x02, 0xCD,
0x79, 0xCE, 0xCF, 0xC1, 0xC1, 0xCF, 0xCE, 0x79, 0xCD, 0x02, 0xCC, 0xC2, 0xCB, 0x03, 0xCB, 0xB3, 0xCB, 0x03, 0x03, 0x03, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x7E, 0x05, 0xC6, 0x7E, 0x00, 0xC7, 0x15, 0xC8, 0xC9, 0xC9, 0xC8, 0x15, 0xC7, 0x00, 0x7E,
0xC6, 0x05, 0x7E, 0x04, 0x7E, 0xCA, 0x7E, 0x04, 0x04, 0x04, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x00, 0x03, 0xC3, 0x00,
0x05, 0x55, 0xC4, 0xC5, 0xC1, 0xC1, 0xC5, 0xC4, 0x55, 0x05, 0x00, 0xC3, 0x03, 0x00, 0xAE, 0x00, 0x3D, 0x00, 0xAE, 0xAE, 0xAE,
0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0x7E, 0xBE, 0x00, 0x05, 0xBE, 0x7E, 0xBF, 0xC0, 0x5C, 0xC1, 0xC1, 0x5C, 0xC0, 0xBF, 0x7E,
0xBE, 0x05, 0x00, 0xBE, 0x7E, 0xBE, 0xC2, 0x06, 0xBD, 0xBD, 0xBD, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB3, 0xB3, 0xB5, 0xB6, 0xB6,
0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0x6B, 0x6B, 0xBB, 0xBA, 0xB9, 0xB8, 0xB7, 0xB6, 0xB6, 0xB5, 0xB3, 0xB5, 0xBC, 0xB4, 0xB3, 0xB3,
0xB3, 0x02, 0x02, 0xA8, 0xA9, 0xAA, 0xA8, 0x02, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0x8F, 0xB1, 0x71, 0x2D, 0xB1, 0x9E, 0xB0,
0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0x02, 0xA4, 0xB2, 0xAA, 0xA8, 0x02, 0x02, 0x04, 0x31, 0xA3, 0x04, 0x31, 0x82, 0x3B, 0xA3, 0xA4,
0xA5, 0xA6, 0x82, 0xA7, 0x8E, 0x20, 0x78, 0x78, 0x20, 0x8E, 0xA7, 0x82, 0xA6, 0xA5, 0xA4, 0xA3, 0x3B, 0x82, 0x3D, 0x04, 0xA3,
0x31, 0x04, 0x09, 0x9F, 0xA0, 0x21, 0x2C, 0xA1, 0x47, 0x52, 0x5B, 0x5A, 0xA2, 0x1C, 0x81, 0x8D, 0x8E, 0x91, 0x91, 0x8E, 0x8D,
0x81, 0x1C, 0xA2, 0x5A, 0x5B, 0x52, 0x47, 0xA1, 0x2C, 0x21, 0xA0, 0x9F, 0x09, 0x96, 0x97, 0x16, 0x98, 0x99, 0x9A, 0x46, 0x9B,
0x28, 0x9C, 0x6D, 0x76, 0x7D, 0x8C, 0x9D, 0x8E, 0x8E, 0x9D, 0x9E, 0x7D, 0x75, 0x6D, 0x9C, 0x65, 0x9B, 0x46, 0x39, 0x99, 0x98,
0x16, 0x97, 0x96, 0x4F, 0x89, 0x15, 0x1F, 0x69, 0x38, 0x45, 0x8A, 0x78, 0x66, 0x6C, 0x74, 0x4A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x8B, 0x80, 0x45, 0x90, 0x66, 0x91, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x15, 0x89, 0x4F, 0x7E, 0x0D, 0x14, 0x1E, 0x29, 0x37, 0x44,
0x14, 0x59, 0x65, 0x6B, 0x73, 0x7F, 0x80, 0x25, 0x81, 0x82, 0x25, 0x80, 0x7F, 0x83, 0x84, 0x65, 0x85, 0x4D, 0x86, 0x87, 0x29,
0x88, 0x14, 0x0D, 0x7E, 0x05, 0x0C, 0x13, 0x1D, 0x28, 0x2C, 0x43, 0x4E, 0x72, 0x64, 0x53, 0x5A, 0x73, 0x74, 0x75, 0x1C, 0x1C,
0x76, 0x74, 0x77, 0x78, 0x6A, 0x64, 0x79, 0x4E, 0x7A, 0x2C, 0x7B, 0x7C, 0x7D, 0x0C, 0x05, 0x04, 0x0B, 0x12, 0x1C, 0x27, 0x36,
0x42, 0x4D, 0x5D, 0x63, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x1A, 0x1A, 0x6D, 0x6C, 0x6E, 0x53, 0x69, 0x6F, 0x5D, 0x19, 0x70, 0x36,
0x71, 0x24, 0x12, 0x0B, 0x04, 0x03, 0x03, 0x11, 0x1B, 0x2E, 0x35, 0x41, 0x4C, 0x5E, 0x62, 0x63, 0x64, 0x65, 0x66, 0x2C, 0x5A,
0x5A, 0x2C, 0x66, 0x65, 0x64, 0x67, 0x62, 0x5E, 0x52, 0x40, 0x65, 0x68, 0x1B, 0x11, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x25,
0x34, 0x40, 0x4B, 0x56, 0x57, 0x58, 0x16, 0x59, 0x5A, 0x41, 0x5B, 0x5B, 0x41, 0x5A, 0x59, 0x5C, 0x5D, 0x5E, 0x5F, 0x21, 0x41,
0x60, 0x61, 0x05, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x09, 0x24, 0x33, 0x3F, 0x2C, 0x4B, 0x4C, 0x4D, 0x4E, 0x14, 0x4F, 0x50,
0x51, 0x51, 0x50, 0x4F, 0x14, 0x4E, 0x4D, 0x52, 0x53, 0x2C, 0x3F, 0x01, 0x54, 0x55, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x1A,
0x23, 0x32, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x47, 0x46, 0x45, 0x44, 0x43, 0x48, 0x41, 0x40, 0x3F,
0x49, 0x3C, 0x4A, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x30, 0x32, 0x33, 0x34, 0x35, 0x36, 0x2C, 0x37, 0x38,
0x39, 0x3A, 0x3A, 0x39, 0x38, 0x37, 0x2C, 0x36, 0x35, 0x34, 0x3B, 0x3C, 0x30, 0x3D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1A, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x2D, 0x2E, 0x13,
0x2F, 0x30, 0x31, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x09, 0x02, 0x1B, 0x1C, 0x1D, 0x1E,
0x1F, 0x20, 0x21, 0x21, 0x20, 0x22, 0x1E, 0x1D, 0x1C, 0x1B, 0x02, 0x09, 0x1A, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x17, 0x16, 0x18, 0x19, 0x13, 0x12, 0x11,
0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F, 0x10, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04,
0x03, 0x02, 0x01, 0x00, 0x01, 0x0A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

View File

@@ -1,4 +1,3 @@
#include "main.h"
#include "mesh/http/WebServer.h"
#include "NodeDB.h"
#include "mesh/http/WiFiAPClient.h"
@@ -78,7 +77,7 @@ static void taskCreateCert(void *parameter)
{
prefs.begin("MeshtasticHTTPS", false);
// Delete the saved certs (used in debugging)
// Delete the saved certs
if (0) {
DEBUG_MSG("Deleting any saved SSL keys ...\n");
// prefs.clear();
@@ -167,16 +166,11 @@ void createSSLCert()
NULL); /* Task handle. */
DEBUG_MSG("Waiting for SSL Cert to be generated.\n");
int seconds = 0;
while (!isCertReady) {
DEBUG_MSG(".");
delay(1000);
yield();
esp_task_wdt_reset();
seconds++;
if ((seconds == 3) && screen) {
screen->setSSLFrames();
}
}
DEBUG_MSG("SSL Cert Ready!\n");
}

View File

@@ -57,7 +57,7 @@ static int32_t reconnectWiFi()
if (*wifiName) {
needReconnect = false;
DEBUG_MSG("... Reconnecting to WiFi access point\n");
DEBUG_MSG("... Reconnecting to WiFi access point");
WiFi.mode(WIFI_MODE_STA);
WiFi.begin(wifiName, wifiPsw);
}

View File

@@ -557,7 +557,7 @@ void setBluetoothEnable(bool on)
bluetoothOn = on;
if (on) {
if (!initWifi(isSoftAPForced())) // if we are using wifi, don't turn on bluetooth also
if (!initWifi(0)) // if we are using wifi, don't turn on bluetooth also
{
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );

View File

@@ -137,15 +137,10 @@ void AdminPlugin::handleSetOwner(const User &o)
strcpy(owner.id, o.id);
}
if (owner.is_licensed != o.is_licensed) {
changed = 1;
changed = true;
owner.is_licensed = o.is_licensed;
}
if ((!changed || o.team) && (owner.team != o.team)) {
changed = 1;
owner.team = o.team;
}
if (changed) // If nothing really changed, don't broadcast on the network or write to flash
service.reloadOwner();
}

View File

@@ -143,7 +143,7 @@ ExternalNotificationPlugin::ExternalNotificationPlugin()
#endif
}
ProcessMessage ExternalNotificationPlugin::handleReceived(const MeshPacket &mp)
bool ExternalNotificationPlugin::handleReceived(const MeshPacket &mp)
{
#ifndef NO_ESP32
@@ -176,5 +176,5 @@ ProcessMessage ExternalNotificationPlugin::handleReceived(const MeshPacket &mp)
#endif
return ProcessMessage::CONTINUE; // 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
}

View File

@@ -24,9 +24,9 @@ class ExternalNotificationPlugin : public SinglePortPlugin, private concurrency:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp);
virtual bool handleReceived(const MeshPacket &mp);
virtual int32_t runOnce();
};

View File

@@ -5,11 +5,12 @@
#include "plugins/RemoteHardwarePlugin.h"
#include "plugins/ReplyPlugin.h"
#include "plugins/TextMessagePlugin.h"
#include "plugins/SerialPlugin.h"
#include "plugins/TextMessagePlugin.h"
#include "plugins/RoutingPlugin.h"
#include "plugins/AdminPlugin.h"
#ifndef NO_ESP32
#include "plugins/esp32/SerialPlugin.h"
#include "plugins/SerialPlugin.h"
#include "plugins/esp32/EnvironmentalMeasurementPlugin.h"
#include "plugins/esp32/RangeTestPlugin.h"
#include "plugins/esp32/StoreForwardPlugin.h"

View File

@@ -1,10 +1,9 @@
#include "configuration.h"
#include "PositionPlugin.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
PositionPlugin *positionPlugin;
@@ -19,24 +18,6 @@ bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, Position *pptr
{
auto p = *pptr;
// If inbound message is a replay (or spoof!) of our own messages, we shouldn't process
// (why use second-hand sources for our own data?)
// FIXME this can in fact happen with packets sent from EUD (src=RX_SRC_USER)
// to set fixed location, EUD-GPS location or just the time (see also issue #900)
if (nodeDB.getNodeNum() == getFrom(&mp)) {
DEBUG_MSG("Incoming update from MYSELF\n");
// DEBUG_MSG("Ignored an incoming update from MYSELF\n");
// return false;
}
// Log packet size and list of fields
DEBUG_MSG("POSITION node=%08x l=%d %s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", getFrom(&mp), mp.decoded.payload.size,
p.latitude_i ? "LAT " : "", p.longitude_i ? "LON " : "", p.altitude ? "MSL " : "", p.altitude_hae ? "HAE " : "",
p.alt_geoid_sep ? "GEO " : "", p.PDOP ? "PDOP " : "", p.HDOP ? "HDOP " : "", p.VDOP ? "VDOP " : "",
p.sats_in_view ? "SIV " : "", p.fix_quality ? "FXQ " : "", p.fix_type ? "FXT " : "", p.pos_timestamp ? "PTS " : "",
p.time ? "TIME " : "", p.battery_level ? "BAT " : "");
if (p.time) {
struct timeval tv;
uint32_t secs = p.time;
@@ -57,44 +38,7 @@ MeshPacket *PositionPlugin::allocReply()
NodeInfo *node = service.refreshMyNodeInfo(); // should guarantee there is now a position
assert(node->has_position);
// configuration of POSITION packet
// consider making this a function argument?
uint32_t pos_flags = radioConfig.preferences.position_flags;
// Populate a Position struct with ONLY the requested fields
Position p = Position_init_default; // Start with an empty structure
// lat/lon are unconditionally included - IF AVAILABLE!
p.latitude_i = node->position.latitude_i;
p.longitude_i = node->position.longitude_i;
p.time = node->position.time;
if (pos_flags & PositionFlags_POS_BATTERY)
p.battery_level = node->position.battery_level;
if (pos_flags & PositionFlags_POS_ALTITUDE) {
if (pos_flags & PositionFlags_POS_ALT_MSL)
p.altitude = node->position.altitude;
else
p.altitude_hae = node->position.altitude_hae;
if (pos_flags & PositionFlags_POS_GEO_SEP)
p.alt_geoid_sep = node->position.alt_geoid_sep;
}
if (pos_flags & PositionFlags_POS_DOP) {
if (pos_flags & PositionFlags_POS_HVDOP) {
p.HDOP = node->position.HDOP;
p.VDOP = node->position.VDOP;
} else
p.PDOP = node->position.PDOP;
}
if (pos_flags & PositionFlags_POS_SATINVIEW)
p.sats_in_view = node->position.sats_in_view;
if (pos_flags & PositionFlags_POS_TIMESTAMP)
p.pos_timestamp = node->position.pos_timestamp;
Position p = node->position;
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
// nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
@@ -125,58 +69,18 @@ void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies)
int32_t PositionPlugin::runOnce()
{
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
// radioConfig.preferences.position_broadcast_smart = true;
// We limit our GPS broadcasts to a max rate
uint32_t now = millis();
if (lastGpsSend == 0 || now - lastGpsSend >= getPref_position_broadcast_secs() * 1000) {
lastGpsSend = now;
lastGpsLatitude = node->position.latitude_i;
lastGpsLongitude = node->position.longitude_i;
// If we changed channels, ask everyone else for their latest info
bool requestReplies = currentGeneration != radioGeneration;
currentGeneration = radioGeneration;
DEBUG_MSG("Sending pos@%x:6 to mesh (wantReplies=%d)\n", node->position.pos_timestamp, requestReplies);
DEBUG_MSG("Sending position to mesh (wantReplies=%d)\n", requestReplies);
sendOurPosition(NODENUM_BROADCAST, requestReplies);
} else if (radioConfig.preferences.position_broadcast_smart == true) {
NodeInfo *node = service.refreshMyNodeInfo(); // should guarantee there is now a position
if (node->has_position && (node->position.latitude_i != 0 || node->position.longitude_i != 0)) {
float distance = GeoCoord::latLongToMeter(lastGpsLatitude * 1e-7, lastGpsLongitude * 1e-7,
node->position.latitude_i * 1e-7, node->position.longitude_i * 1e-7);
/* Please don't change these values. This accomodates for possible poor positioning
in the event the GPS has a poor satelite lock.
*/
const uint8_t distanceTravel = 150;
/* Minimum time between position updates.
Note: At an average walking speed of 3.5mph, it takes 90 seconds to travel 150 meters.
*/
const uint8_t timeTravel = 60;
// If the distance traveled since the last update is greater than 100 meters
// and it's been at least 60 seconds since the last update
if ((abs(distance) >= distanceTravel) &&
(lastGpsSend == 0 || now - timeTravel >= getPref_position_broadcast_secs() * 1000)) {
bool requestReplies = currentGeneration != radioGeneration;
currentGeneration = radioGeneration;
DEBUG_MSG("Sending smart pos@%x:6 to mesh (wantReplies=%d)\n", node->position.pos_timestamp, requestReplies);
sendOurPosition(NODENUM_BROADCAST, requestReplies);
/* Update lastGpsSend to now. This means if the device is stationary, then
getPref_position_broadcast_secs will still apply.
*/
lastGpsSend = now;
}
}
}
return 5000; // to save power only wake for our callback occasionally

View File

@@ -13,10 +13,6 @@ class PositionPlugin : public ProtobufPlugin<Position>, private concurrency::OST
/// We limit our GPS broadcasts to a max rate
uint32_t lastGpsSend = 0;
// Store the latest good lat / long
uint32_t lastGpsLatitude = 0;
uint32_t lastGpsLongitude = 0;
/// We force a rebroadcast if the radio settings change
uint32_t currentGeneration = 0;

View File

@@ -157,7 +157,7 @@ void SerialPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
service.sendToMesh(p);
}
ProcessMessage SerialPluginRadio::handleReceived(const MeshPacket &mp)
bool SerialPluginRadio::handleReceived(const MeshPacket &mp)
{
#ifndef NO_ESP32
@@ -207,5 +207,5 @@ ProcessMessage SerialPluginRadio::handleReceived(const MeshPacket &mp)
#endif
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
return true; // Let others look at this message also if they want
}

View File

@@ -45,9 +45,9 @@ class SerialPluginRadio : public SinglePortPlugin
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp);
virtual bool handleReceived(const MeshPacket &mp);
};
extern SerialPluginRadio *serialPluginRadio;
extern SerialPluginRadio *serialPluginRadio;

View File

@@ -5,7 +5,7 @@
TextMessagePlugin *textMessagePlugin;
ProcessMessage TextMessagePlugin::handleReceived(const MeshPacket &mp)
bool TextMessagePlugin::handleReceived(const MeshPacket &mp)
{
auto &p = mp.decoded;
DEBUG_MSG("Received text msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
@@ -18,5 +18,5 @@ ProcessMessage TextMessagePlugin::handleReceived(const MeshPacket &mp)
powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG);
notifyObservers(&mp);
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
return false; // Let others look at this message also if they want
}

View File

@@ -17,9 +17,9 @@ class TextMessagePlugin : public SinglePortPlugin, public Observable<const MeshP
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp);
virtual bool handleReceived(const MeshPacket &mp);
};
extern TextMessagePlugin *textMessagePlugin;

View File

@@ -1,23 +1,20 @@
#include "EnvironmentalMeasurementPlugin.h"
#include "../mesh/generated/environmental_measurement.pb.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "main.h"
#include "../mesh/generated/environmental_measurement.pb.h"
#include <DHT.h>
#include <DS18B20.h>
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
#include <OneWire.h>
#define DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
#define DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
#define DS18B20_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
#define DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000 // Some sensors (the DHT11) have a minimum required duration between read attempts
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
#ifdef HAS_EINK
// The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16
@@ -34,136 +31,123 @@
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
int32_t EnvironmentalMeasurementPlugin::runOnce()
{
int32_t EnvironmentalMeasurementPlugin::runOnce() {
#ifndef NO_ESP32 // this only works on ESP32 devices
/*
Uncomment the preferences below if you want to use the plugin
without having to configure it from the PythonAPI or WebUI.
*/
/*radioConfig.preferences.environmental_measurement_plugin_measurement_enabled = 1;
radioConfig.preferences.environmental_measurement_plugin_screen_enabled = 1;
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold = 5;
radioConfig.preferences.environmental_measurement_plugin_update_interval = 600;
radioConfig.preferences.environmental_measurement_plugin_update_interval = 30;
radioConfig.preferences.environmental_measurement_plugin_recovery_interval = 60;
radioConfig.preferences.environmental_measurement_plugin_display_farenheit = false;
radioConfig.preferences.environmental_measurement_plugin_display_farenheit = true;
radioConfig.preferences.environmental_measurement_plugin_sensor_pin = 13;
radioConfig.preferences.environmental_measurement_plugin_sensor_type =
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType::
RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20;
*/
radioConfig.preferences.environmental_measurement_plugin_sensor_type = RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType::RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11;*/
if (!(radioConfig.preferences.environmental_measurement_plugin_measurement_enabled ||
radioConfig.preferences.environmental_measurement_plugin_screen_enabled)) {
if (! (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){
// If this plugin is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
return (INT32_MAX);
}
if (firstTime) {
// This is the first time the OSThread library has called this function, so do some setup
firstTime = 0;
if (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled) {
if (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled)
{
DEBUG_MSG("EnvironmentalMeasurement: Initializing\n");
// it's possible to have this plugin enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
switch (radioConfig.preferences.environmental_measurement_plugin_sensor_type) {
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11:
dht = new DHT(radioConfig.preferences.environmental_measurement_plugin_sensor_pin, DHT11);
this->dht->begin();
this->dht->read();
DEBUG_MSG("EnvironmentalMeasurement: Opened DHT11 on pin: %d\n",
radioConfig.preferences.environmental_measurement_plugin_sensor_pin);
return (DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20:
oneWire = new OneWire(radioConfig.preferences.environmental_measurement_plugin_sensor_pin);
ds18b20 = new DS18B20(oneWire);
this->ds18b20->begin();
this->ds18b20->setResolution(12);
this->ds18b20->requestTemperatures();
DEBUG_MSG("EnvironmentalMeasurement: Opened DS18B20 on pin: %d\n",
radioConfig.preferences.environmental_measurement_plugin_sensor_pin);
return (DS18B20_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
default:
DEBUG_MSG("EnvironmentalMeasurement: Invalid sensor type selected; Disabling plugin");
return (INT32_MAX);
switch(radioConfig.preferences.environmental_measurement_plugin_sensor_type) {
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11:
dht = new DHT(radioConfig.preferences.environmental_measurement_plugin_sensor_pin,DHT11);
this->dht->begin();
this->dht->read();
DEBUG_MSG("EnvironmentalMeasurement: Opened DHT11 on pin: %d\n",radioConfig.preferences.environmental_measurement_plugin_sensor_pin);
break;
default:
DEBUG_MSG("EnvironmentalMeasurement: Invalid sensor type selected; Disabling plugin");
return (INT32_MAX);
break;
}
// begin reading measurements from the sensor
// DHT have a max read-rate of 1HZ, so we should wait at least 1 second
// after initializing the sensor before we try to read from it.
// returning the interval here means that the next time OSThread
// calls our plugin, we'll run the other branch of this if statement
// and actually do a "sendOurEnvironmentalMeasurement()"
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
}
return (INT32_MAX);
} else {
if (!radioConfig.preferences.environmental_measurement_plugin_measurement_enabled) {
}
else {
if (!radioConfig.preferences.environmental_measurement_plugin_measurement_enabled)
{
// if we somehow got to a second run of this plugin with measurement disabled, then just wait forever
// I can't imagine we'd ever get here though.
return (INT32_MAX);
}
// this is not the first time OSThread library has called this function
// so just do what we intend to do on the interval
if (sensor_read_error_count > radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold) {
if (radioConfig.preferences.environmental_measurement_plugin_recovery_interval > 0) {
DEBUG_MSG("EnvironmentalMeasurement: TEMPORARILY DISABLED; The "
"environmental_measurement_plugin_read_error_count_threshold has been exceed: %d. Will retry reads in "
"%d seconds\n",
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold,
radioConfig.preferences.environmental_measurement_plugin_recovery_interval);
sensor_read_error_count = 0;
return (radioConfig.preferences.environmental_measurement_plugin_recovery_interval * 1000);
if(sensor_read_error_count > radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold)
{
if (radioConfig.preferences.environmental_measurement_plugin_recovery_interval > 0 ) {
DEBUG_MSG(
"EnvironmentalMeasurement: TEMPORARILY DISABLED; The environmental_measurement_plugin_read_error_count_threshold has been exceed: %d. Will retry reads in %d seconds\n",
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold,
radioConfig.preferences.environmental_measurement_plugin_recovery_interval);
sensor_read_error_count = 0;
return(radioConfig.preferences.environmental_measurement_plugin_recovery_interval*1000);
}
DEBUG_MSG("EnvironmentalMeasurement: DISABLED; The environmental_measurement_plugin_read_error_count_threshold has "
"been exceed: %d. Reads will not be retried until after device reset\n",
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold);
return (INT32_MAX);
DEBUG_MSG(
"EnvironmentalMeasurement: DISABLED; The environmental_measurement_plugin_read_error_count_threshold has been exceed: %d. Reads will not be retried until after device reset\n",
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold);
return(INT32_MAX);
} else if (sensor_read_error_count > 0) {
DEBUG_MSG("EnvironmentalMeasurement: There have been %d sensor read failures. Will retry %d more times\n",
sensor_read_error_count, sensor_read_error_count, sensor_read_error_count,
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold -
sensor_read_error_count);
}
if (!sendOurEnvironmentalMeasurement()) {
// if we failed to read the sensor, then try again
else if (sensor_read_error_count > 0){
DEBUG_MSG("EnvironmentalMeasurement: There have been %d sensor read failures. Will retry %d more times\n",
sensor_read_error_count,
radioConfig.preferences.environmental_measurement_plugin_read_error_count_threshold-sensor_read_error_count);
}
if (!sendOurEnvironmentalMeasurement() ){
// if we failed to read the sensor, then try again
// as soon as we can according to the maximum polling frequency
// return (DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
switch (radioConfig.preferences.environmental_measurement_plugin_sensor_type) {
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11:
return (DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20:
return (DS18B20_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
default:
return (DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
}
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
}
}
// The return of runOnce is an int32 representing the desired number of
// miliseconds until the function should be called again by the
// The return of runOnce is an int32 representing the desired number of
// miliseconds until the function should be called again by the
// OSThread library. Multiply the preference value by 1000 to convert seconds to miliseconds
return (radioConfig.preferences.environmental_measurement_plugin_update_interval * 1000);
return(radioConfig.preferences.environmental_measurement_plugin_update_interval * 1000);
#endif
}
bool EnvironmentalMeasurementPlugin::wantUIFrame()
{
bool EnvironmentalMeasurementPlugin::wantUIFrame() {
return radioConfig.preferences.environmental_measurement_plugin_screen_enabled;
}
String GetSenderName(const MeshPacket &mp)
{
String GetSenderName(const MeshPacket &mp) {
String sender;
auto node = nodeDB.getNode(getFrom(&mp));
if (node) {
if (node){
sender = node->user.short_name;
} else {
}
else {
sender = "UNK";
}
return sender;
}
uint32_t GetTimeSinceMeshPacket(const MeshPacket *mp)
{
uint32_t GetTimeSinceMeshPacket(const MeshPacket *mp) {
uint32_t now = getTime();
uint32_t last_seen = mp->rx_time;
@@ -172,13 +156,15 @@ uint32_t GetTimeSinceMeshPacket(const MeshPacket *mp)
delta = 0;
return delta;
}
float EnvironmentalMeasurementPlugin::CelsiusToFarenheit(float c)
{
return (c * 9) / 5 + 32;
float EnvironmentalMeasurementPlugin::CelsiusToFarenheit(float c) {
return (c*9)/5 + 32;
}
void EnvironmentalMeasurementPlugin::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -187,16 +173,21 @@ void EnvironmentalMeasurementPlugin::drawFrame(OLEDDisplay *display, OLEDDisplay
if (lastMeasurementPacket == nullptr) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement");
//DEBUG_MSG("EnvironmentalMeasurement: No previous measurement; not drawing frame\n");
return;
}
EnvironmentalMeasurement lastMeasurement;
uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket);
String lastSender = GetSenderName(*lastMeasurementPacket);
auto &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, EnvironmentalMeasurement_fields, &lastMeasurement)) {
if (!pb_decode_from_bytes(p.payload.bytes,
p.payload.size,
EnvironmentalMeasurement_fields,
&lastMeasurement)) {
display->setFont(FONT_SMALL);
display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
DEBUG_MSG("EnvironmentalMeasurement: unable to decode last packet");
@@ -204,23 +195,20 @@ void EnvironmentalMeasurementPlugin::drawFrame(OLEDDisplay *display, OLEDDisplay
}
display->setFont(FONT_SMALL);
String last_temp = String(lastMeasurement.temperature, 0) + "°C";
if (radioConfig.preferences.environmental_measurement_plugin_display_farenheit) {
last_temp = String(CelsiusToFarenheit(lastMeasurement.temperature), 0) + "°F";
;
String last_temp = String(lastMeasurement.temperature,0) +"°C";
if (radioConfig.preferences.environmental_measurement_plugin_display_farenheit){
last_temp = String(CelsiusToFarenheit(lastMeasurement.temperature),0) +"°F";;
}
display->drawString(x, y += fontHeight(FONT_MEDIUM),
lastSender + ": " + last_temp + "/" + String(lastMeasurement.relative_humidity, 0) + "%(" +
String(agoSecs) + "s)");
display->drawString(x, y += fontHeight(FONT_MEDIUM), lastSender+": "+last_temp +"/"+ String(lastMeasurement.relative_humidity,0) + "%("+String(agoSecs)+"s)");
}
bool EnvironmentalMeasurementPlugin::handleReceivedProtobuf(const MeshPacket &mp, EnvironmentalMeasurement *p)
{
if (!(radioConfig.preferences.environmental_measurement_plugin_measurement_enabled ||
radioConfig.preferences.environmental_measurement_plugin_screen_enabled)) {
// If this plugin is not enabled in any capacity, don't handle the packet, and allow other plugins to consume
return false;
if (!(radioConfig.preferences.environmental_measurement_plugin_measurement_enabled || radioConfig.preferences.environmental_measurement_plugin_screen_enabled)){
// If this plugin is not enabled in any capacity, don't handle the packet, and allow other plugins to consume
return false;
}
String sender = GetSenderName(mp);
@@ -242,32 +230,13 @@ bool EnvironmentalMeasurementPlugin::sendOurEnvironmentalMeasurement(NodeNum des
DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("EnvironmentalMeasurement: Read data\n");
switch (radioConfig.preferences.environmental_measurement_plugin_sensor_type) {
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DHT11:
if (!this->dht->read(true)) {
sensor_read_error_count++;
DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n");
return false;
}
m.relative_humidity = this->dht->readHumidity();
m.temperature = this->dht->readTemperature();
break;
case RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_DS18B20:
if (this->ds18b20->isConversionComplete()) {
m.temperature = this->ds18b20->getTempC();
m.relative_humidity = 0; // This sensor is temperature only
this->ds18b20->requestTemperatures();
break;
} else {
sensor_read_error_count++;
DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n");
return false;
}
default:
DEBUG_MSG("EnvironmentalMeasurement: Invalid sensor type selected; Disabling plugin");
if (!this->dht->read(true)){
sensor_read_error_count++;
DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n");
return false;
}
m.relative_humidity = this->dht->readHumidity();
m.temperature = this->dht->readTemperature();
DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", m.relative_humidity);
DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", m.temperature);
@@ -281,3 +250,4 @@ bool EnvironmentalMeasurementPlugin::sendOurEnvironmentalMeasurement(NodeNum des
service.sendToMesh(p);
return true;
}

View File

@@ -1,20 +1,15 @@
#pragma once
#include "../mesh/generated/environmental_measurement.pb.h"
#include "ProtobufPlugin.h"
#include <DHT.h>
#include <DS18B20.h>
#include "../mesh/generated/environmental_measurement.pb.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
#include <OneWire.h>
#include <DHT.h>
class EnvironmentalMeasurementPlugin : private concurrency::OSThread, public ProtobufPlugin<EnvironmentalMeasurement>
{
public:
EnvironmentalMeasurementPlugin()
: concurrency::OSThread("EnvironmentalMeasurementPlugin"),
ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg)
{
lastMeasurementPacket = nullptr;
EnvironmentalMeasurementPlugin(): concurrency::OSThread("EnvironmentalMeasurementPlugin"), ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg) {
lastMeasurementPacket = nullptr;
}
virtual bool wantUIFrame();
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
@@ -29,13 +24,11 @@ class EnvironmentalMeasurementPlugin : private concurrency::OSThread, public Pro
* Send our EnvironmentalMeasurement into the mesh
*/
bool sendOurEnvironmentalMeasurement(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
private:
float CelsiusToFarenheit(float c);
bool firstTime = 1;
DHT *dht;
OneWire *oneWire;
DS18B20 *ds18b20;
DHT* dht;
const MeshPacket *lastMeasurementPacket;
uint32_t sensor_read_error_count = 0;
};

View File

@@ -5,7 +5,6 @@
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include <Arduino.h>
#include <SPIFFS.h>
//#include <assert.h>
@@ -124,7 +123,7 @@ void RangeTestPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
}
ProcessMessage RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
bool RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
{
#ifndef NO_ESP32
@@ -151,7 +150,6 @@ ProcessMessage RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
appendFile(mp);
}
/*
DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("p.payload.bytes \"%s\"\n", p.payload.bytes);
DEBUG_MSG("p.payload.size %d\n", p.payload.size);
@@ -175,7 +173,6 @@ ProcessMessage RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
DEBUG_MSG("gpsStatus->getHasLock() %d\n", gpsStatus->getHasLock());
DEBUG_MSG("gpsStatus->getDOP() %d\n", gpsStatus->getDOP());
DEBUG_MSG("-----------------------------------------\n");
*/
}
} else {
@@ -184,7 +181,28 @@ ProcessMessage RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
#endif
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
return true; // Let others look at this message also if they want
}
/// Ported from my old java code, returns distance in meters along the globe
/// surface (by magic?)
float RangeTestPluginRadio::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
double pk = (180 / 3.14169);
double a1 = lat_a / pk;
double a2 = lng_a / pk;
double b1 = lat_b / pk;
double b2 = lng_b / pk;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
double t3 = sin(a1) * sin(b1);
double tt = acos(t1 + t2 + t3);
if (isnan(tt))
tt = 0.0; // Must have been the same point?
return (float)(6366000 * tt);
}
bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
@@ -239,7 +257,7 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
// Print the CSV header
if (fileToWrite.println(
"time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,payload")) {
"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");
@@ -285,7 +303,7 @@ bool RangeTestPluginRadio::appendFile(const MeshPacket &mp)
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
if (n->position.latitude_i && n->position.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) {
float distance = GeoCoord::latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7,
float distance = latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7,
gpsStatus->getLatitude() * 1e-7, gpsStatus->getLongitude() * 1e-7);
fileToAppend.printf("%f,", distance); // Distance in meters
} else {

View File

@@ -50,9 +50,9 @@ class RangeTestPluginRadio : public SinglePortPlugin
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual ProcessMessage handleReceived(const MeshPacket &mp);
virtual bool handleReceived(const MeshPacket &mp);
};
extern RangeTestPluginRadio *rangeTestPluginRadio;

View File

@@ -5,12 +5,14 @@
#include "Router.h"
#include "configuration.h"
#include "mesh-pb-constants.h"
#include "mesh/generated/storeforward.pb.h"
#include "plugins/PluginDev.h"
#include <Arduino.h>
#include <iterator>
#include <map>
#define STOREFORWARD_MAX_PACKETS 0
#define STOREFORWARD_SEND_HISTORY_PERIOD 10 * 60
#define STOREFORWARD_SEND_HISTORY_MAX 0
StoreForwardPlugin *storeForwardPlugin;
int32_t StoreForwardPlugin::runOnce()
@@ -21,31 +23,22 @@ int32_t StoreForwardPlugin::runOnce()
if (radioConfig.preferences.store_forward_plugin_enabled) {
if (radioConfig.preferences.is_router) {
if (this->busy) {
// Send out the message queue.
// DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index);
storeForwardPlugin->sendPayload(this->busyTo, this->packetHistoryTXQueue_index);
if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) {
strcpy(this->routerMessage, "** S&F - Done");
storeForwardPlugin->sendMessage(this->busyTo, this->routerMessage);
// DEBUG_MSG("--- --- --- In busy loop - Done \n");
this->packetHistoryTXQueue_index = 0;
this->busy = false;
} else {
this->packetHistoryTXQueue_index++;
}
}
// TODO: Dynamicly adjust the time this returns in the loop based on the size of the packets being actually
// transmitted.
return (this->packetTimeMax);
// Maybe some cleanup functions?
this->historyReport();
return (60 * 1000);
} else {
DEBUG_MSG("Store & Forward Plugin - Disabled (is_router = false)\n");
/*
* 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.
*/
return (INT32_MAX);
DEBUG_MSG("Store & Forward Plugin - Sending heartbeat\n");
storeForwardPlugin->sendPayload();
return (4 * 60 * 1000);
}
} else {
@@ -58,9 +51,6 @@ int32_t StoreForwardPlugin::runOnce()
return (INT32_MAX);
}
/*
Create our data structure in the PSRAM.
*/
void StoreForwardPlugin::populatePSRAM()
{
/*
@@ -68,8 +58,6 @@ void StoreForwardPlugin::populatePSRAM()
https://learn.upesy.com/en/programmation/psram.html#psram-tab
*/
uint32_t store_forward_plugin_replay_max_records = 250;
DEBUG_MSG("Before PSRAM initilization:\n");
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
@@ -77,14 +65,12 @@ void StoreForwardPlugin::populatePSRAM()
DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize());
DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram());
// Use a maximum of 2/3 the available PSRAM unless otherwise specified.
// Use a maximum of half the available PSRAM unless otherwise specified.
uint32_t numberOfPackets =
(radioConfig.preferences.store_forward_plugin_records ? radioConfig.preferences.store_forward_plugin_records
: (((ESP.getFreePsram() / 3) * 2) / sizeof(PacketHistoryStruct)));
STOREFORWARD_MAX_PACKETS ? STOREFORWARD_MAX_PACKETS : ((ESP.getPsramSize() / 2) / sizeof(PacketHistoryStruct));
// this->packetHistory = (PacketHistoryStruct *)ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct));
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)));
this->packetHistoryTXQueue =
static_cast<PacketHistoryStruct *>(ps_calloc(store_forward_plugin_replay_max_records, sizeof(PacketHistoryStruct)));
DEBUG_MSG("After PSRAM initilization:\n");
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
@@ -92,13 +78,34 @@ void StoreForwardPlugin::populatePSRAM()
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 for packetHistory - %u\n", numberOfPackets);
DEBUG_MSG(" numberOfPackets - %u\n", numberOfPackets);
}
// We saw a node.
void StoreForwardPlugin::sawNode(uint32_t whoWeSaw, uint32_t sawSecAgo)
{
if (radioConfig.preferences.is_router) {
// 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);
}
}
}
void StoreForwardPlugin::historyReport()
{
DEBUG_MSG("Iterating through the message history...\n");
DEBUG_MSG("Message history contains %u records\n", this->packetHistoryCurrent);
uint32_t startTimer = millis();
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);
}
}
DEBUG_MSG("StoreForwardPlugin::historyReport runtime - %u ms\n", millis() - startTimer);
}
/*
@@ -106,75 +113,31 @@ void StoreForwardPlugin::historyReport()
*/
void StoreForwardPlugin::historySend(uint32_t msAgo, uint32_t to)
{
uint32_t packetsSent = 0;
uint32_t queueSize = storeForwardPlugin->historyQueueCreate(msAgo, to);
if (queueSize) {
snprintf(this->routerMessage, 80, "** S&F - Sending %d message(s)", queueSize);
storeForwardPlugin->sendMessage(to, this->routerMessage);
this->busy = true; // runOnce() will pickup the next steps once busy = true.
this->busyTo = to;
} else {
strcpy(this->routerMessage, "** S&F - No history to send");
storeForwardPlugin->sendMessage(to, this->routerMessage);
}
}
uint32_t StoreForwardPlugin::historyQueueCreate(uint32_t msAgo, uint32_t to)
{
// uint32_t packetHistoryTXQueueIndex = 0;
this->packetHistoryTXQueue_size = 0;
// Send "Welcome back"
this->sendPayloadWelcome(to, false);
for (int i = 0; i < this->packetHistoryCurrent; i++) {
/*
DEBUG_MSG("SF historyQueueCreate\n");
DEBUG_MSG("SF historyQueueCreate - time %d\n", this->packetHistory[i].time);
DEBUG_MSG("SF historyQueueCreate - millis %d\n", millis());
DEBUG_MSG("SF historyQueueCreate - math %d\n", (millis() - msAgo));
*/
if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) {
DEBUG_MSG("SF historyQueueCreate - Time matches - ok\n");
/*
Copy the messages that were received by the router in the last msAgo
to the packetHistoryTXQueue structure.
TODO: The condition (this->packetHistory[i].to & 0xffffffff) == to) is not tested since
I don't have an easy way to target a specific user. Will need to do this soon.
*/
if ((this->packetHistory[i].to & 0xffffffff) == 0xffffffff || ((this->packetHistory[i].to & 0xffffffff) == to)) {
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from;
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size = this->packetHistory[i].payload_size;
memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload,
Constants_DATA_PAYLOAD_LEN);
this->packetHistoryTXQueue_size++;
DEBUG_MSG("PacketHistoryStruct time=%d\n", this->packetHistory[i].time);
DEBUG_MSG("PacketHistoryStruct msg=%.*s\n", this->packetHistory[i].payload);
// DEBUG_MSG("PacketHistoryStruct msg=%.*s\n", this->packetHistoryTXQueue[packetHistoryTXQueueIndex].payload);
}
if (this->packetHistory[i].time) {
// DEBUG_MSG("... time-%u to-0x%08x\n", this->packetHistory[i].time, this->packetHistory[i].to & 0xffffffff);
}
}
return this->packetHistoryTXQueue_size;
}
void StoreForwardPlugin::historyAdd(const MeshPacket &mp)
void StoreForwardPlugin::historyAdd(const MeshPacket *mp)
{
auto &p = mp.decoded;
auto &p = mp;
static uint8_t bytes[MAX_RHPACKETLEN];
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
assert(numbytes <= MAX_RHPACKETLEN);
DEBUG_MSG("MP numbytes %u\n", numbytes);
// destination, source, bytes
// memcpy(p->encrypted.bytes, bytes, numbytes);
memcpy(this->packetHistory[this->packetHistoryCurrent].bytes, bytes, MAX_RHPACKETLEN);
this->packetHistory[this->packetHistoryCurrent].time = millis();
this->packetHistory[this->packetHistoryCurrent].to = mp.to;
this->packetHistory[this->packetHistoryCurrent].from = mp.from;
this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size;
memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, Constants_DATA_PAYLOAD_LEN);
this->packetHistory[this->packetHistoryCurrent].to = mp->to;
this->packetHistoryCurrent++;
}
@@ -184,87 +147,64 @@ MeshPacket *StoreForwardPlugin::allocReply()
return reply;
}
void StoreForwardPlugin::sendPayload(NodeNum dest, uint32_t packetHistory_index)
void StoreForwardPlugin::sendPayload(NodeNum dest, bool wantReplies)
{
DEBUG_MSG("Sending S&F Payload\n");
MeshPacket *p = allocReply();
p->to = dest;
p->from = this->packetHistoryTXQueue[packetHistory_index].from;
p->decoded.want_response = wantReplies;
// Let's assume that if the router received the S&F request that the client is in range.
// TODO: Make this configurable.
p->want_ack = false;
p->want_ack = true;
p->decoded.payload.size =
this->packetHistoryTXQueue[packetHistory_index].payload_size; // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, this->packetHistoryTXQueue[packetHistory_index].payload,
this->packetHistoryTXQueue[packetHistory_index].payload_size);
static char heartbeatString[20];
snprintf(heartbeatString, sizeof(heartbeatString), "1");
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);
}
void StoreForwardPlugin::sendMessage(NodeNum dest, char *str)
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;
// Let's assume that if the router received the S&F request that the client is in range.
// TODO: Make this configurable.
p->want_ack = false;
p->want_ack = true;
p->decoded.payload.size = strlen(str); // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, str, strlen(str));
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);
// HardwareMessage_init_default
}
ProcessMessage StoreForwardPlugin::handleReceived(const MeshPacket &mp)
bool StoreForwardPlugin::handleReceived(const MeshPacket &mp)
{
#ifndef NO_ESP32
if (radioConfig.preferences.store_forward_plugin_enabled) {
DEBUG_MSG("--- S&F Received something\n");
/*
StoreAndForwardMessage sfm = StoreAndForwardMessage_init_default;
switch (sfm.rr) {
}
*/
auto &p = mp.decoded;
// The router node should not be sending messages as a client.
if (getFrom(&mp) != nodeDB.getNodeNum()) {
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);
DEBUG_MSG(" -------------- ");
if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n");
if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 0x00)) {
DEBUG_MSG("--- --- --- Request to send\n");
// Send the last 60 minutes of messages.
if (this->busy) {
strcpy(this->routerMessage, "** S&F - Busy. Try again shortly.");
storeForwardPlugin->sendMessage(getFrom(&mp), this->routerMessage);
} else {
storeForwardPlugin->historySend(1000 * 60, getFrom(&mp));
}
} else if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 'm') &&
(p.payload.bytes[3] == 0x00)) {
strcpy(this->routerMessage, "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
"01234567890123456789012345678901234567890123456789012345678901234567890123456789"
"01234567890123456789012345678901234567890123456789012345678901234567890123456");
storeForwardPlugin->sendMessage(getFrom(&mp), this->routerMessage);
} else {
storeForwardPlugin->historyAdd(mp);
}
} else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) {
storeForwardPlugin->historyAdd(&mp);
} else {
DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum);
@@ -277,86 +217,25 @@ ProcessMessage StoreForwardPlugin::handleReceived(const MeshPacket &mp)
#endif
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
ProcessMessage StoreForwardPlugin::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p)
{
if (!radioConfig.preferences.store_forward_plugin_enabled) {
// If this plugin is not enabled in any capacity, don't handle the packet, and allow other plugins to consume
return ProcessMessage::CONTINUE;
}
//auto sfp = *p;
//auto p = *p;
// Advance states as needed
switch (p->rr) {
case StoreAndForward_RequestResponse_CLIENT_ERROR:
// Do nothing
break;
case StoreAndForward_RequestResponse_CLIENT_HISTORY:
// Do nothing
break;
case StoreAndForward_RequestResponse_CLIENT_PING:
// Do nothing
break;
case StoreAndForward_RequestResponse_CLIENT_PONG:
// Do nothing
break;
case StoreAndForward_RequestResponse_CLIENT_STATS:
// Do nothing
break;
case StoreAndForward_RequestResponse_ROUTER_BUSY:
// Do nothing
break;
case StoreAndForward_RequestResponse_ROUTER_ERROR:
// Do nothing
break;
case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT:
// Do nothing
break;
case StoreAndForward_RequestResponse_ROUTER_PING:
// Do nothing
break;
case StoreAndForward_RequestResponse_ROUTER_PONG:
// Do nothing
break;
default:
assert(0); // unexpected state - FIXME, make an error code and reboot
}
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
return true; // Let others look at this message also if they want
}
StoreForwardPlugin::StoreForwardPlugin()
: SinglePortPlugin("StoreForwardPlugin", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("StoreForwardPlugin")
: SinglePortPlugin("StoreForwardPlugin", PortNum_STORE_FORWARD_APP), concurrency::OSThread("StoreForwardPlugin")
{
#ifndef NO_ESP32
isPromiscuous = true; // Brown chicken brown cow
if (StoreForward_Dev) {
/*
Uncomment the preferences below if you want to use the plugin
without having to configure it from the PythonAPI or WebUI.
*/
/*
Uncomment the preferences below if you want to use the plugin
without having to configure it from the PythonAPI or WebUI.
*/
// radioConfig.preferences.store_forward_plugin_enabled = 1;
// radioConfig.preferences.is_router = 1;
// radioConfig.preferences.is_always_powered = 1;
if (StoreForward_Dev) {
radioConfig.preferences.store_forward_plugin_enabled = 1;
radioConfig.preferences.is_router = 1;
}
if (radioConfig.preferences.store_forward_plugin_enabled) {
@@ -369,11 +248,7 @@ StoreForwardPlugin::StoreForwardPlugin()
// Do the startup here
// Popupate PSRAM with our data structures.
this->populatePSRAM();
this->packetTimeMax = 2000;
} else {
DEBUG_MSG("Device has less than 1M of PSRAM free. Aborting startup.\n");
DEBUG_MSG("Store & Forward Plugin - Aborting Startup.\n");
@@ -390,4 +265,4 @@ StoreForwardPlugin::StoreForwardPlugin()
}
}
#endif
}
}

Some files were not shown because too many files have changed in this diff Show More