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