Stay up-to-date wrt master (cc @mc-hamster)

# Conflicts:
#	src/plugins/Plugins.cpp
#	src/plugins/RangeTestPlugin.cpp
#	src/plugins/StoreForwardPlugin.cpp
This commit is contained in:
Kevin Hester
2021-02-22 10:39:46 +08:00
19 changed files with 845 additions and 278 deletions

View File

@@ -0,0 +1,102 @@
#include "EnvironmentalMeasurementPlugin.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>
EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin;
EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio;
EnvironmentalMeasurementPlugin::EnvironmentalMeasurementPlugin() : concurrency::OSThread("EnvironmentalMeasurementPlugin") {}
uint32_t sensor_read_error_count = 0;
#define DHT_11_GPIO_PIN 13
//TODO: Make a related radioconfig preference to allow less-frequent reads
#define DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
#define SENSOR_READ_ERROR_COUNT_THRESHOLD 5
#define SENSOR_READ_MULTIPLIER 3
DHT dht(DHT_11_GPIO_PIN,DHT11);
int32_t EnvironmentalMeasurementPlugin::runOnce() {
#ifndef NO_ESP32
if (firstTime) {
// This is the first time the OSThread library has called this function, so do some setup
DEBUG_MSG("Initializing Environmental Measurement Plugin -- Sender\n");
environmentalMeasurementPluginRadio = new EnvironmentalMeasurementPluginRadio();
firstTime = 0;
// 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()"
dht.begin();
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
}
else {
// 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 > SENSOR_READ_ERROR_COUNT_THRESHOLD)
{
DEBUG_MSG("Environmental Measurement Plugin: DISABLED; The SENSOR_READ_ERROR_COUNT_THRESHOLD has been exceed: %d\n",SENSOR_READ_ERROR_COUNT_THRESHOLD);
return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
}
else if (sensor_read_error_count > 0){
DEBUG_MSG("Environmental Measurement Plugin: There have been %d sensor read failures.\n",sensor_read_error_count);
}
if (! environmentalMeasurementPluginRadio->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);
}
}
// The return of runOnce is an int32 representing the desired number of
// miliseconds until the function should be called again by the
// OSThread library.
return(SENSOR_READ_MULTIPLIER * DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
#endif
}
bool EnvironmentalMeasurementPluginRadio::handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *p)
{
// This plugin doesn't really do anything with the messages it receives.
return false; // Let others look at this message also if they want
}
bool EnvironmentalMeasurementPluginRadio::sendOurEnvironmentalMeasurement(NodeNum dest, bool wantReplies)
{
EnvironmentalMeasurement m;
m.barometric_pressure = 0; // TODO: Add support for barometric sensors
m.relative_humidity = dht.readHumidity();
m.temperature = dht.readTemperature();;
DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("Environmental Measurement Plugin: Read data\n");
DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", m.relative_humidity);
DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", m.temperature);
if (isnan(m.relative_humidity) || isnan(m.temperature) ){
sensor_read_error_count++;
DEBUG_MSG("Environmental Measurement Plugin: FAILED TO READ DATA\n");
return false;
}
sensor_read_error_count = 0;
MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = wantReplies;
service.sendToMesh(p);
return true;
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "ProtobufPlugin.h"
#include "../mesh/generated/environmental_measurement.pb.h"
class EnvironmentalMeasurementPlugin : private concurrency::OSThread
{
bool firstTime = 1;
public:
EnvironmentalMeasurementPlugin();
protected:
virtual int32_t runOnce();
};
extern EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin;
/**
* EnvironmentalMeasurementPluginRadio plugin for sending/receiving environmental measurements to/from the mesh
*/
class EnvironmentalMeasurementPluginRadio : public ProtobufPlugin<EnvironmentalMeasurement>
{
public:
/** Constructor
* name is for debugging output
*/
EnvironmentalMeasurementPluginRadio() : ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg) {}
/**
* Send our EnvironmentalMeasurement into the mesh
*/
bool sendOurEnvironmentalMeasurement(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
protected:
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *p);
};
extern EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio;

View File

@@ -0,0 +1,315 @@
#include "RangeTestPlugin.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "configuration.h"
#include <Arduino.h>
#include <SPIFFS.h>
//#include <assert.h>
/*
As a sender, I can send packets every n-seonds. These packets include an incramented PacketID.
As a receiver, I can receive packets from multiple senders. These packets can be saved to the spiffs.
*/
RangeTestPlugin *rangeTestPlugin;
RangeTestPluginRadio *rangeTestPluginRadio;
RangeTestPlugin::RangeTestPlugin() : concurrency::OSThread("RangeTestPlugin") {}
uint32_t packetSequence = 0;
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
int32_t RangeTestPlugin::runOnce()
{
#ifndef NO_ESP32
/*
Uncomment the preferences below if you want to use the plugin
without having to configure it from the PythonAPI or WebUI.
*/
//radioConfig.preferences.range_test_plugin_enabled = 1;
//radioConfig.preferences.range_test_plugin_sender = 45;
//radioConfig.preferences.range_test_plugin_save = 1;
// Fixed position is useful when testing indoors.
// radioConfig.preferences.fixed_position = 1;
uint32_t senderHeartbeat = radioConfig.preferences.range_test_plugin_sender * 1000;
if (radioConfig.preferences.range_test_plugin_enabled) {
if (firstTime) {
// Interface with the serial peripheral from in here.
rangeTestPluginRadio = new RangeTestPluginRadio();
firstTime = 0;
if (radioConfig.preferences.range_test_plugin_sender) {
DEBUG_MSG("Initializing Range Test Plugin -- Sender\n");
return (5000); // Sending first message 5 seconds after initilization.
} else {
DEBUG_MSG("Initializing Range Test Plugin -- Receiver\n");
return (500);
}
} else {
if (radioConfig.preferences.range_test_plugin_sender) {
// If sender
DEBUG_MSG("Range Test Plugin - Sending heartbeat every %d ms\n", (senderHeartbeat));
DEBUG_MSG("gpsStatus->getLatitude() %d\n", gpsStatus->getLatitude());
DEBUG_MSG("gpsStatus->getLongitude() %d\n", gpsStatus->getLongitude());
DEBUG_MSG("gpsStatus->getHasLock() %d\n", gpsStatus->getHasLock());
DEBUG_MSG("gpsStatus->getDOP() %d\n", gpsStatus->getDOP());
DEBUG_MSG("gpsStatus->getHasLock() %d\n", gpsStatus->getHasLock());
DEBUG_MSG("pref.fixed_position() %d\n", radioConfig.preferences.fixed_position);
rangeTestPluginRadio->sendPayload();
return (senderHeartbeat);
} else {
// Otherwise, we're a receiver.
return (500);
}
// TBD
}
} else {
DEBUG_MSG("Range Test Plugin - Disabled\n");
}
return (INT32_MAX);
#endif
}
MeshPacket *RangeTestPluginRadio::allocReply()
{
auto reply = allocDataPacket(); // Allocate a packet for sending
return reply;
}
void RangeTestPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
{
MeshPacket *p = allocReply();
p->to = dest;
p->decoded.want_response = wantReplies;
p->want_ack = true;
packetSequence++;
static char heartbeatString[20];
snprintf(heartbeatString, sizeof(heartbeatString), "seq %d", packetSequence);
p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
memcpy(p->decoded.payload.bytes, heartbeatString, p->decoded.payload.size);
service.sendToMesh(p);
// TODO: Handle this better. We want to keep the phone awake otherwise it stops sending.
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
}
bool RangeTestPluginRadio::handleReceived(const MeshPacket &mp)
{
#ifndef NO_ESP32
if (radioConfig.preferences.range_test_plugin_enabled) {
auto &p = mp.decoded;
// DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
// nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
if (mp.from != nodeDB.getNodeNum()) {
// DEBUG_MSG("* * Message came from the mesh\n");
// Serial2.println("* * Message came from the mesh");
// Serial2.printf("%s", p.payload.bytes);
/*
*/
NodeInfo *n = nodeDB.getNode(mp.from);
if (radioConfig.preferences.range_test_plugin_save) {
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);
DEBUG_MSG("---- Received Packet:\n");
DEBUG_MSG("mp.from %d\n", mp.from);
DEBUG_MSG("mp.rx_snr %f\n", mp.rx_snr);
DEBUG_MSG("mp.hop_limit %d\n", mp.hop_limit);
// DEBUG_MSG("mp.decoded.position.latitude_i %d\n", mp.decoded.position.latitude_i); // Depricated
// DEBUG_MSG("mp.decoded.position.longitude_i %d\n", mp.decoded.position.longitude_i); // Depricated
DEBUG_MSG("---- Node Information of Received Packet (mp.from):\n");
DEBUG_MSG("n->user.long_name %s\n", n->user.long_name);
DEBUG_MSG("n->user.short_name %s\n", n->user.short_name);
// DEBUG_MSG("n->user.macaddr %X\n", n->user.macaddr);
DEBUG_MSG("n->has_position %d\n", n->has_position);
DEBUG_MSG("n->position.latitude_i %d\n", n->position.latitude_i);
DEBUG_MSG("n->position.longitude_i %d\n", n->position.longitude_i);
DEBUG_MSG("n->position.battery_level %d\n", n->position.battery_level);
DEBUG_MSG("---- Current device location information:\n");
DEBUG_MSG("gpsStatus->getLatitude() %d\n", gpsStatus->getLatitude());
DEBUG_MSG("gpsStatus->getLongitude() %d\n", gpsStatus->getLongitude());
DEBUG_MSG("gpsStatus->getHasLock() %d\n", gpsStatus->getHasLock());
DEBUG_MSG("gpsStatus->getDOP() %d\n", gpsStatus->getDOP());
DEBUG_MSG("-----------------------------------------\n");
}
} else {
DEBUG_MSG("Range Test Plugin Disabled\n");
}
#endif
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)
{
auto &p = mp.decoded;
NodeInfo *n = nodeDB.getNode(mp.from);
/*
DEBUG_MSG("-----------------------------------------\n");
DEBUG_MSG("p.payload.bytes \"%s\"\n", p.payload.bytes);
DEBUG_MSG("p.payload.size %d\n", p.payload.size);
DEBUG_MSG("---- Received Packet:\n");
DEBUG_MSG("mp.from %d\n", mp.from);
DEBUG_MSG("mp.rx_snr %f\n", mp.rx_snr);
DEBUG_MSG("mp.hop_limit %d\n", mp.hop_limit);
// DEBUG_MSG("mp.decoded.position.latitude_i %d\n", mp.decoded.position.latitude_i); // Depricated
// DEBUG_MSG("mp.decoded.position.longitude_i %d\n", mp.decoded.position.longitude_i); // Depricated
DEBUG_MSG("---- Node Information of Received Packet (mp.from):\n");
DEBUG_MSG("n->user.long_name %s\n", n->user.long_name);
DEBUG_MSG("n->user.short_name %s\n", n->user.short_name);
DEBUG_MSG("n->user.macaddr %X\n", n->user.macaddr);
DEBUG_MSG("n->has_position %d\n", n->has_position);
DEBUG_MSG("n->position.latitude_i %d\n", n->position.latitude_i);
DEBUG_MSG("n->position.longitude_i %d\n", n->position.longitude_i);
DEBUG_MSG("n->position.battery_level %d\n", n->position.battery_level);
DEBUG_MSG("---- Current device location information:\n");
DEBUG_MSG("gpsStatus->getLatitude() %d\n", gpsStatus->getLatitude());
DEBUG_MSG("gpsStatus->getLongitude() %d\n", gpsStatus->getLongitude());
DEBUG_MSG("gpsStatus->getHasLock() %d\n", gpsStatus->getHasLock());
DEBUG_MSG("gpsStatus->getDOP() %d\n", gpsStatus->getDOP());
DEBUG_MSG("-----------------------------------------\n");
*/
if (!SPIFFS.begin(true)) {
DEBUG_MSG("An Error has occurred while mounting SPIFFS\n");
return 0;
}
if (SPIFFS.totalBytes() - SPIFFS.usedBytes() < 51200) {
DEBUG_MSG("SPIFFS doesn't have enough free space. Abourting write.\n");
return 0;
}
// If the file doesn't exist, write the header.
if (!SPIFFS.exists("/static/rangetest.csv")) {
//--------- Write to file
File fileToWrite = SPIFFS.open("/static/rangetest.csv", FILE_WRITE);
if (!fileToWrite) {
DEBUG_MSG("There was an error opening the file for writing\n");
return 0;
}
if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx snr,distance,payload")) {
DEBUG_MSG("File was written\n");
} else {
DEBUG_MSG("File write failed\n");
}
fileToWrite.close();
}
//--------- Apend content to file
File fileToAppend = SPIFFS.open("/static/rangetest.csv", FILE_APPEND);
if (!fileToAppend) {
DEBUG_MSG("There was an error opening the file for appending\n");
return 0;
}
struct timeval tv;
if (!gettimeofday(&tv, NULL)) {
long hms = tv.tv_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
fileToAppend.printf("%02d:%02d:%02d,", hour, min, sec); // Time
} else {
fileToAppend.printf("??:??:??,"); // Time
}
fileToAppend.printf("%d,", mp.from); // From
fileToAppend.printf("%s,", n->user.long_name); // Long Name
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
if (n->position.latitude_i && n->position.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) {
float distance = latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7,
gpsStatus->getLatitude() * 1e-7, gpsStatus->getLongitude() * 1e-7);
fileToAppend.printf("%f,", distance); // Distance in meters
} else {
fileToAppend.printf("0,");
}
// TODO: If quotes are found in the payload, it has to be escaped.
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
fileToAppend.close();
return 1;
}

View File

@@ -0,0 +1,58 @@
#pragma once
#include "SinglePortPlugin.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include <Arduino.h>
#include <functional>
class RangeTestPlugin : private concurrency::OSThread
{
bool firstTime = 1;
public:
RangeTestPlugin();
protected:
virtual int32_t runOnce();
};
extern RangeTestPlugin *rangeTestPlugin;
/*
* Radio interface for RangeTestPlugin
*
*/
class RangeTestPluginRadio : public SinglePortPlugin
{
uint32_t lastRxID;
public:
RangeTestPluginRadio() : SinglePortPlugin("RangeTestPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
/**
* Send our payload into the mesh
*/
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
/**
* Append range test data to the file on the spiffs
*/
bool appendFile(const MeshPacket &mp);
/**
* Kevin's magical calculation of two points to meters.
*/
float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b);
protected:
virtual MeshPacket *allocReply();
/** Called to handle a particular incoming message
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
*/
virtual bool handleReceived(const MeshPacket &mp);
};
extern RangeTestPluginRadio *rangeTestPluginRadio;