diff --git a/docs/README.md b/docs/README.md index 982bb4f67..e268de0d7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,6 +42,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 **Russian**-based users [t.me/meshtastic_russia](https://t.me/meshtastic_russia) (Russian language, unofficial). # Updates diff --git a/platformio.ini b/platformio.ini index 89cf5a1ac..39b57dd9c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -104,6 +104,8 @@ build_flags = lib_deps = ${arduino_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git + adafruit/DHT sensor library@^1.4.1 + adafruit/Adafruit Unified Sensor@^1.1.4 # Hmm - this doesn't work yet # board_build.ldscript = linker/esp32.extram.bss.ld lib_ignore = segger_rtt diff --git a/proto b/proto index 0cadaed39..d70f6f6f6 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 0cadaed3953f66cf1edc99d0fa53e4fd5ebf56d6 +Subproject commit d70f6f6f669df79c9423795caf34adbd28967e19 diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 40ea25237..8f974a4c0 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -64,6 +64,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]; @@ -144,6 +148,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); @@ -887,6 +915,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) @@ -894,6 +927,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; @@ -922,6 +967,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 ebaee49a6..006ce571b 100644 --- a/src/mesh/MeshPlugin.cpp +++ b/src/mesh/MeshPlugin.cpp @@ -75,4 +75,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 01af58114..a5fa48445 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; @@ -61,6 +67,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/mesh/generated/deviceonly.pb.h b/src/mesh/generated/deviceonly.pb.h index 99d838d39..0e933b968 100644 --- a/src/mesh/generated/deviceonly.pb.h +++ b/src/mesh/generated/deviceonly.pb.h @@ -80,10 +80,10 @@ extern const pb_msgdesc_t DeviceState_msg; #define DeviceState_fields &DeviceState_msg /* Maximum encoded size of messages (where known) */ -#define DeviceState_size 6266 +#define DeviceState_size 6293 #ifdef __cplusplus } /* extern "C" */ #endif -#endif \ No newline at end of file +#endif diff --git a/src/mesh/generated/environmental_measurement.pb.c b/src/mesh/generated/environmental_measurement.pb.c new file mode 100644 index 000000000..b55d14a0a --- /dev/null +++ b/src/mesh/generated/environmental_measurement.pb.c @@ -0,0 +1,12 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.4 */ + +#include "environmental_measurement.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(EnvironmentalMeasurement, EnvironmentalMeasurement, AUTO) + + + diff --git a/src/mesh/generated/environmental_measurement.pb.h b/src/mesh/generated/environmental_measurement.pb.h new file mode 100644 index 000000000..8adf5774a --- /dev/null +++ b/src/mesh/generated/environmental_measurement.pb.h @@ -0,0 +1,53 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.4 */ + +#ifndef PB_ENVIRONMENTAL_MEASUREMENT_PB_H_INCLUDED +#define PB_ENVIRONMENTAL_MEASUREMENT_PB_H_INCLUDED +#include + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Struct definitions */ +typedef struct _EnvironmentalMeasurement { + float temperature; + float relative_humidity; + float barometric_pressure; +} EnvironmentalMeasurement; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initializer values for message structs */ +#define EnvironmentalMeasurement_init_default {0, 0, 0} +#define EnvironmentalMeasurement_init_zero {0, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define EnvironmentalMeasurement_temperature_tag 1 +#define EnvironmentalMeasurement_relative_humidity_tag 2 +#define EnvironmentalMeasurement_barometric_pressure_tag 3 + +/* Struct field encoding specification for nanopb */ +#define EnvironmentalMeasurement_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, FLOAT, temperature, 1) \ +X(a, STATIC, SINGULAR, FLOAT, relative_humidity, 2) \ +X(a, STATIC, SINGULAR, FLOAT, barometric_pressure, 3) +#define EnvironmentalMeasurement_CALLBACK NULL +#define EnvironmentalMeasurement_DEFAULT NULL + +extern const pb_msgdesc_t EnvironmentalMeasurement_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define EnvironmentalMeasurement_fields &EnvironmentalMeasurement_msg + +/* Maximum encoded size of messages (where known) */ +#define EnvironmentalMeasurement_size 15 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/mesh/generated/mesh.pb.h b/src/mesh/generated/mesh.pb.h index 397a313ea..edc74a908 100644 --- a/src/mesh/generated/mesh.pb.h +++ b/src/mesh/generated/mesh.pb.h @@ -208,6 +208,11 @@ typedef struct _RadioConfig_UserPreferences { bool range_test_plugin_save; bool store_forward_plugin_enabled; uint32_t store_forward_plugin_records; + bool environmental_measurement_plugin_measurement_enabled; + bool environmental_measurement_plugin_screen_enabled; + uint32_t environmental_measurement_plugin_read_error_count_threshold; + uint32_t environmental_measurement_plugin_update_interval; + uint32_t environmental_measurement_plugin_recovery_interval; } RadioConfig_UserPreferences; typedef struct _RouteDiscovery { @@ -360,7 +365,7 @@ extern "C" { #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} -#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#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} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0} #define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN} @@ -374,7 +379,7 @@ extern "C" { #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0, 0, _MeshPacket_Priority_MIN} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} -#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#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} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0} #define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN} @@ -460,6 +465,11 @@ extern "C" { #define RadioConfig_UserPreferences_range_test_plugin_save_tag 134 #define RadioConfig_UserPreferences_store_forward_plugin_enabled_tag 136 #define RadioConfig_UserPreferences_store_forward_plugin_records_tag 137 +#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 +#define RadioConfig_UserPreferences_environmental_measurement_plugin_update_interval_tag 143 +#define RadioConfig_UserPreferences_environmental_measurement_plugin_recovery_interval_tag 144 #define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 @@ -641,7 +651,12 @@ 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, BOOL, store_forward_plugin_enabled, 136) \ -X(a, STATIC, SINGULAR, UINT32, store_forward_plugin_records, 137) +X(a, STATIC, SINGULAR, UINT32, store_forward_plugin_records, 137) \ +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) \ +X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_update_interval, 143) \ +X(a, STATIC, SINGULAR, UINT32, environmental_measurement_plugin_recovery_interval, 144) #define RadioConfig_UserPreferences_CALLBACK NULL #define RadioConfig_UserPreferences_DEFAULT NULL @@ -753,13 +768,13 @@ extern const pb_msgdesc_t ToRadio_msg; #define SubPacket_size 275 #define MeshPacket_size 322 #define ChannelSettings_size 95 -#define RadioConfig_size 405 -#define RadioConfig_UserPreferences_size 305 +#define RadioConfig_size 432 +#define RadioConfig_UserPreferences_size 332 #define NodeInfo_size 132 #define MyNodeInfo_size 106 #define LogRecord_size 81 -#define FromRadio_size 414 -#define ToRadio_size 409 +#define FromRadio_size 441 +#define ToRadio_size 436 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/portnums.pb.h b/src/mesh/generated/portnums.pb.h index 2133ecaa7..496324a86 100644 --- a/src/mesh/generated/portnums.pb.h +++ b/src/mesh/generated/portnums.pb.h @@ -21,6 +21,7 @@ typedef enum _PortNum { PortNum_SERIAL_APP = 64, PortNum_STORE_FORWARD_APP = 65, PortNum_RANGE_TEST_APP = 66, + PortNum_ENVIRONMENTAL_MEASUREMENT_APP = 67, PortNum_PRIVATE_APP = 256, PortNum_ATAK_FORWARDER = 257 } PortNum; diff --git a/src/plugins/Plugins.cpp b/src/plugins/Plugins.cpp index c2dfd73ca..cfcc21d54 100644 --- a/src/plugins/Plugins.cpp +++ b/src/plugins/Plugins.cpp @@ -6,6 +6,7 @@ #include "plugins/TextMessagePlugin.h" #ifndef NO_ESP32 +#include "plugins/esp32/EnvironmentalMeasurementPlugin.h" #include "plugins/esp32/RangeTestPlugin.h" #include "plugins/SerialPlugin.h" #include "plugins/StoreForwardPlugin.h" @@ -40,5 +41,6 @@ void setupPlugins() new RangeTestPlugin(); // new StoreForwardPlugin(); + new EnvironmentalMeasurementPlugin(); #endif } \ No newline at end of file diff --git a/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp b/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp new file mode 100644 index 000000000..2d706150d --- /dev/null +++ b/src/plugins/esp32/EnvironmentalMeasurementPlugin.cpp @@ -0,0 +1,209 @@ +#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 +#include +#include + +EnvironmentalMeasurementPlugin *environmentalMeasurementPlugin; +EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio; + +EnvironmentalMeasurementPlugin::EnvironmentalMeasurementPlugin() : concurrency::OSThread("EnvironmentalMeasurementPlugin") {} + +uint32_t sensor_read_error_count = 0; + +#define DHT_11_GPIO_PIN 13 +#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 // 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("EnvironmentalMeasurement: Initializing\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()" + 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 > 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); + 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("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 + // 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. Multiply the preference value by 1000 to convert seconds to miliseconds + return(radioConfig.preferences.environmental_measurement_plugin_update_interval * 1000); +#endif +} + +bool EnvironmentalMeasurementPluginRadio::wantUIFrame() { + return radioConfig.preferences.environmental_measurement_plugin_screen_enabled; +} + +void EnvironmentalMeasurementPluginRadio::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + 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 &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; + } + 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 +} + +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("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("EnvironmentalMeasurement: 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; +} + diff --git a/src/plugins/esp32/EnvironmentalMeasurementPlugin.h b/src/plugins/esp32/EnvironmentalMeasurementPlugin.h new file mode 100644 index 000000000..19d73914b --- /dev/null +++ b/src/plugins/esp32/EnvironmentalMeasurementPlugin.h @@ -0,0 +1,63 @@ +#pragma once +#include "ProtobufPlugin.h" +#include "../mesh/generated/environmental_measurement.pb.h" +#include +#include + + +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 +{ + public: + /** Constructor + * name is for debugging output + */ + EnvironmentalMeasurementPluginRadio() : ProtobufPlugin("EnvironmentalMeasurement", PortNum_ENVIRONMENTAL_MEASUREMENT_APP, &EnvironmentalMeasurement_msg) { + lastMeasurement.barometric_pressure = nanf(""); + lastMeasurement.relative_humidity = nanf(""); + lastMeasurement.temperature = nanf(""); + lastSender = "N/A"; + } + + /** + * Send our EnvironmentalMeasurement into the mesh + */ + bool sendOurEnvironmentalMeasurement(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + + virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + + 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); + + virtual bool wantUIFrame(); + + + private: + + EnvironmentalMeasurement lastMeasurement; + + String lastSender; + +}; + +extern EnvironmentalMeasurementPluginRadio *environmentalMeasurementPluginRadio; \ No newline at end of file