diff --git a/docs/README.md b/docs/README.md
index e268de0d7..4a124156f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -41,7 +41,7 @@ For an detailed walk-through aimed at beginners, we recommend [meshtastic.letsta
### Related Groups
-Telegram group for **Italy**-based users [t.me/meshtastic_italia](http://t.me/meshtastic_italia) (Italian language, unofficial).
+Telegram group for **Italy**-based users [t.me/meshtastic_italia](http://t.me/meshtastic_italia) (Italian language, unofficial).
Telegram group for **Russian**-based users [t.me/meshtastic_russia](https://t.me/meshtastic_russia) (Russian language, unofficial).
# Updates
diff --git a/proto b/proto
index 7a5875d96..f2d342de0 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 7a5875d9639a0682bd36d7e118bf26d7b4d733be
+Subproject commit f2d342de0ca74e9202c79876afc5aec675150e49
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index a2c74ba7e..d04b9960b 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -65,6 +65,10 @@ uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
// Threshold values for the GPS lock accuracy bar display
uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
+// At some point, we're going to ask all of the plugins if they would like to display a screen frame
+// we'll need to hold onto pointers for the plugins that can draw a frame.
+std::vector pluginFrames;
+
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
@@ -145,6 +149,30 @@ static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int
drawIconScreen("Sleeping...", display, state, x, y);
}
+static void drawPluginFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ uint8_t plugin_frame;
+ // there's a little but in the UI transition code
+ // where it invokes the function at the correct offset
+ // in the array of "drawScreen" functions; however,
+ // the passed-state doesn't quite reflect the "current"
+ // screen, so we have to detect it.
+ if (state->frameState == IN_TRANSITION && state->transitionFrameRelationship == INCOMING) {
+ // if we're transitioning from the end of the frame list back around to the first
+ // frame, then we want this to be `0`
+ plugin_frame = state->transitionFrameTarget;
+ }
+ else {
+ // otherwise, just display the plugin frame that's aligned with the current frame
+ plugin_frame = state->currentFrame;
+ //DEBUG_MSG("Screen is not in transition. Frame: %d\n\n", plugin_frame);
+ }
+ //DEBUG_MSG("Drawing Plugin Frame %d\n\n", plugin_frame);
+ MeshPlugin &pi = *pluginFrames.at(plugin_frame);
+ pi.drawFrame(display,state,x,y);
+
+}
+
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_CENTER);
@@ -884,6 +912,11 @@ void Screen::setFrames()
DEBUG_MSG("showing standard frames\n");
showingNormalScreen = true;
+ pluginFrames = MeshPlugin::GetMeshPluginsWithUIFrames();
+ DEBUG_MSG("Showing %d plugin frames\n", pluginFrames.size());
+ int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + pluginFrames.size();
+ DEBUG_MSG("Total frame count: %d\n", totalFrameCount);
+
// We don't show the node info our our node (if we have it yet - we should)
size_t numnodes = nodeStatus->getNumTotal();
if (numnodes > 0)
@@ -891,6 +924,18 @@ void Screen::setFrames()
size_t numframes = 0;
+ // put all of the plugin frames first.
+ // this is a little bit of a dirty hack; since we're going to call
+ // the same drawPluginFrame handler here for all of these plugin frames
+ // and then we'll just assume that the state->currentFrame value
+ // is the same offset into the pluginFrames vector
+ // so that we can invoke the plugin's callback
+ for (auto i = pluginFrames.begin(); i != pluginFrames.end(); ++i) {
+ normalFrames[numframes++] = drawPluginFrame;
+ }
+
+ DEBUG_MSG("Added plugins. numframes: %d\n", numframes);
+
// If we have a critical fault, show it first
if (myNodeInfo.error_code)
normalFrames[numframes++] = drawCriticalFaultFrame;
@@ -919,6 +964,8 @@ void Screen::setFrames()
}
#endif
+ DEBUG_MSG("Finished building frames. numframes: %d\n", numframes);
+
ui.setFrames(normalFrames, numframes);
ui.enableAllIndicators();
diff --git a/src/mesh/MeshPlugin.cpp b/src/mesh/MeshPlugin.cpp
index a1fdfd2e2..2d6b907c5 100644
--- a/src/mesh/MeshPlugin.cpp
+++ b/src/mesh/MeshPlugin.cpp
@@ -91,4 +91,18 @@ void MeshPlugin::sendResponse(const MeshPacket &req) {
void setReplyTo(MeshPacket *p, const MeshPacket &to) {
p->to = to.from;
p->want_ack = to.want_ack;
-}
\ No newline at end of file
+}
+
+std::vector MeshPlugin::GetMeshPluginsWithUIFrames() {
+
+ std::vector pluginsWithUIFrames;
+ for (auto i = plugins->begin(); i != plugins->end(); ++i) {
+ auto &pi = **i;
+ if ( pi.wantUIFrame()) {
+ DEBUG_MSG("Plugin wants a UI Frame\n");
+ pluginsWithUIFrames.push_back(&pi);
+ }
+ }
+ return pluginsWithUIFrames;
+
+}
diff --git a/src/mesh/MeshPlugin.h b/src/mesh/MeshPlugin.h
index 11f579fa9..7220a198b 100644
--- a/src/mesh/MeshPlugin.h
+++ b/src/mesh/MeshPlugin.h
@@ -2,6 +2,8 @@
#include "mesh/MeshTypes.h"
#include
+#include
+#include
/** A baseclass for any mesh "plugin".
*
* A plugin allows you to add new features to meshtastic device code, without needing to know messaging details.
@@ -14,7 +16,7 @@
*/
class MeshPlugin
{
- static std::vector *plugins;
+ static std::vector *plugins;
public:
/** Constructor
@@ -28,6 +30,10 @@ class MeshPlugin
*/
static void callPlugins(const MeshPacket &mp);
+ static std::vector GetMeshPluginsWithUIFrames();
+
+ virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
+
protected:
const char *name;
@@ -67,6 +73,13 @@ class MeshPlugin
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply() { return NULL; }
+ /***
+ * @return true if you want to be alloced a UI screen frame
+ */
+ virtual bool wantUIFrame() { return false; }
+
+
+
private:
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
diff --git a/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp b/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp
index 78ffa51cf..b332efd6e 100644
--- a/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp
+++ b/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp
@@ -7,6 +7,8 @@
#include "main.h"
#include "../mesh/generated/environmental_measurement.pb.h"
#include
+#include
+#include
EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin;
EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio;
@@ -16,19 +18,51 @@ EnvironmentalMeasurementPlugin::EnvironmentalMeasurementPlugin() : concurrency::
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
-
+#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
DHT dht(DHT_11_GPIO_PIN,DHT11);
+
+#ifdef HAS_EINK
+// The screen is bigger so use bigger fonts
+#define FONT_SMALL ArialMT_Plain_16
+#define FONT_MEDIUM ArialMT_Plain_24
+#define FONT_LARGE ArialMT_Plain_24
+#else
+#define FONT_SMALL ArialMT_Plain_10
+#define FONT_MEDIUM ArialMT_Plain_16
+#define FONT_LARGE ArialMT_Plain_24
+#endif
+
+#define fontHeight(font) ((font)[1] + 1) // height is position 1
+
+#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
+#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
+
+
int32_t EnvironmentalMeasurementPlugin::runOnce() {
-#ifndef NO_ESP32
+#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 = 30;
+ radioConfig.preferences.environmental_measurement_plugin_recovery_interval = 600;*/
+
+ 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
- DEBUG_MSG("Initializing Environmental Measurement Plugin -- Sender\n");
+ DEBUG_MSG("EnvironmentalMeasurement: Initializing\n");
environmentalMeasurementPluginRadio = new EnvironmentalMeasurementPluginRadio();
firstTime = 0;
// begin reading measurements from the sensor
@@ -37,19 +71,44 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
// 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);
+ if (radioConfig.preferences.environmental_measurement_plugin_measurement_enabled)
+ {
+ // 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
+ dht.begin();
+ return(DHT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS);
+ }
+ return (INT32_MAX);
}
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 > SENSOR_READ_ERROR_COUNT_THRESHOLD)
+ if(sensor_read_error_count > radioConfig.preferences.environmental_measurement_plugin_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);
+ 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);
+ 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);
+
+
}
else if (sensor_read_error_count > 0){
- DEBUG_MSG("Environmental Measurement Plugin: There have been %d sensor read failures.\n",sensor_read_error_count);
+ 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 (! environmentalMeasurementPluginRadio->sendOurEnvironmentalMeasurement() ){
// if we failed to read the sensor, then try again
@@ -59,14 +118,64 @@ int32_t EnvironmentalMeasurementPlugin::runOnce() {
}
// 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);
+ // OSThread library. Multiply the preference value by 1000 to convert seconds to miliseconds
+ return(radioConfig.preferences.environmental_measurement_plugin_update_interval * 1000);
#endif
}
-bool EnvironmentalMeasurementPluginRadio::handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *p)
+bool EnvironmentalMeasurementPluginRadio::wantUIFrame() {
+ return radioConfig.preferences.environmental_measurement_plugin_screen_enabled;
+}
+
+void EnvironmentalMeasurementPluginRadio::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
- // This plugin doesn't really do anything with the messages it receives.
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+ display->setFont(FONT_MEDIUM);
+ display->drawString(x, y, "Environment");
+ display->setFont(FONT_SMALL);
+ display->drawString(x, y += fontHeight(FONT_MEDIUM), lastSender+": T:"+ String(lastMeasurement.temperature,2) + " H:" + String(lastMeasurement.relative_humidity,2));
+
+}
+
+String GetSenderName(const MeshPacket &mp) {
+ String sender;
+
+ if (nodeDB.getNode(mp.from)){
+ sender = nodeDB.getNode(mp.from)->user.short_name;
+ }
+ else {
+ sender = "UNK";
+ }
+ return sender;
+}
+
+bool EnvironmentalMeasurementPluginRadio::handleReceivedProtobuf(const MeshPacket &mp, const EnvironmentalMeasurement *pptr)
+{
+ const EnvironmentalMeasurement &p = *pptr;
+
+ 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;
+ }
+ bool wasBroadcast = mp.to == NODENUM_BROADCAST;
+
+ String sender = GetSenderName(mp);
+
+ // Show new nodes on LCD screen
+ if (DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN && wasBroadcast) {
+ String lcd = String("Env Measured: ") +sender + "\n" +
+ "T: " + p.temperature + "\n" +
+ "H: " + p.relative_humidity + "\n";
+ screen->print(lcd.c_str());
+ }
+ DEBUG_MSG("-----------------------------------------\n");
+
+ DEBUG_MSG("EnvironmentalMeasurement: Received data from %s\n", sender);
+ DEBUG_MSG("EnvironmentalMeasurement->relative_humidity: %f\n", p.relative_humidity);
+ DEBUG_MSG("EnvironmentalMeasurement->temperature: %f\n", p.temperature);
+
+ lastMeasurement = p;
+ lastSender = sender;
return false; // Let others look at this message also if they want
}
@@ -80,13 +189,13 @@ bool EnvironmentalMeasurementPluginRadio::sendOurEnvironmentalMeasurement(NodeNu
DEBUG_MSG("-----------------------------------------\n");
- DEBUG_MSG("Environmental Measurement Plugin: Read data\n");
+ DEBUG_MSG("EnvironmentalMeasurement: 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");
+ DEBUG_MSG("EnvironmentalMeasurement: FAILED TO READ DATA\n");
return false;
}
diff --git a/src/plugins/esp32/EnvironmentalMeasurementPlugin.h b/src/plugins/esp32/EnvironmentalMeasurementPlugin.h
index 5838e6e85..e29ea983e 100644
--- a/src/plugins/esp32/EnvironmentalMeasurementPlugin.h
+++ b/src/plugins/esp32/EnvironmentalMeasurementPlugin.h
@@ -1,6 +1,8 @@
#pragma once
#include "ProtobufPlugin.h"
#include "../mesh/generated/environmental_measurement.pb.h"
+#include
+#include
class EnvironmentalMeasurementPlugin : private concurrency::OSThread
@@ -25,12 +27,19 @@ class EnvironmentalMeasurementPluginRadio : public ProtobufPlugin