From 3da1b74a103df8cdead31f671eb46ac2ac0acf2a Mon Sep 17 00:00:00 2001 From: Andre K Date: Sun, 10 Mar 2024 05:39:37 -0300 Subject: [PATCH 01/18] refactor: always send range tests with zero hops --- src/modules/RangeTestModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index ecf4b70c79..b45068b45b 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -113,7 +113,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) meshtastic_MeshPacket *p = allocDataPacket(); p->to = dest; p->decoded.want_response = wantReplies; - + p->hop_limit = 0; p->want_ack = true; packetSequence++; @@ -295,4 +295,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp) #endif return 1; -} \ No newline at end of file +} From af9d14c370699c8a8cc618763052ecf12eccb461 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 14:52:37 +0100 Subject: [PATCH 02/18] Periodic reporting of device information to a map via MQTT --- src/mesh/Channels.cpp | 20 +++++++ src/mesh/Channels.h | 3 + src/mesh/NodeDB.cpp | 7 ++- src/mesh/NodeDB.h | 8 ++- src/mesh/RadioInterface.cpp | 6 ++ src/mesh/RadioInterface.h | 3 + src/mqtt/MQTT.cpp | 107 +++++++++++++++++++++++++++++++----- src/mqtt/MQTT.h | 17 ++++-- 8 files changed, 149 insertions(+), 22 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 2d27c737de..b50ecf6ca4 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -2,6 +2,7 @@ #include "CryptoEngine.h" #include "DisplayFormatters.h" #include "NodeDB.h" +#include "RadioInterface.h" #include "configuration.h" #include @@ -254,6 +255,25 @@ const char *Channels::getName(size_t chIndex) return channelName; } +bool Channels::hasDefaultChannel() +{ + // If we don't use a preset or we override the frequency, we don't have a default channel + if (!config.lora.use_preset || config.lora.override_frequency) + return false; + // Check if any of the channels are using the default name and PSK + for (size_t i = 0; i < getNumChannels(); i++) { + const auto &ch = getByIndex(i); + if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { + const char *name = getName(i); + const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + // Check if the name is the default derived from the modem preset and we use the default frequency slot + if (strcmp(name, presetName) == 0 && RadioInterface::uses_default_frequency_slot) + return true; + } + } + return false; +} + /** * Generate a short suffix used to disambiguate channels that might have the same "name" entered by the human but different PSKs. * The ideas is that the PSK changing should be visible to the user so that they see they probably messed up and that's why they diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h index 87a72e07b1..0e11605c49 100644 --- a/src/mesh/Channels.h +++ b/src/mesh/Channels.h @@ -102,6 +102,9 @@ class Channels */ int16_t setActiveByIndex(ChannelIndex channelIndex); + // Returns true if we can be reached via a channel with the default settings given a region and modem preset + bool hasDefaultChannel(); + private: /** Given a channel index, change to use the crypto key specified by that index * diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 787c16a797..9d7647138f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -744,14 +744,17 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p) #define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline -size_t NodeDB::getNumOnlineMeshNodes() +size_t NodeDB::getNumOnlineMeshNodes(bool localOnly) { size_t numseen = 0; // FIXME this implementation is kinda expensive - for (int i = 0; i < *numMeshNodes; i++) + for (int i = 0; i < *numMeshNodes; i++) { + if (localOnly && meshNodes[i].via_mqtt) + continue; if (sinceLastSeen(&meshNodes[i]) < NUM_ONLINE_SECS) numseen++; + } return numseen; } diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e24a971c16..8545b08d67 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -108,8 +108,10 @@ class NodeDB // get channel channel index we heard a nodeNum on, defaults to 0 if not found uint8_t getMeshNodeChannel(NodeNum n); - /// Return the number of nodes we've heard from recently (within the last 2 hrs?) - size_t getNumOnlineMeshNodes(); + /* Return the number of nodes we've heard from recently (within the last 2 hrs?) + * @param localOnly if true, ignore nodes heard via MQTT + */ + size_t getNumOnlineMeshNodes(bool localOnly = false); void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum); @@ -246,4 +248,4 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index c10eb26f6a..7a27112519 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,5 +1,6 @@ #include "RadioInterface.h" #include "Channels.h" +#include "DisplayFormatters.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -143,6 +144,7 @@ const RegionInfo regions[] = { }; const RegionInfo *myRegion; +bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_RHPACKETLEN]; @@ -486,6 +488,10 @@ void RadioInterface::applyModemConfig() // channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1) int channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels; + // Check if we use the default frequency slot + RadioInterface::uses_default_frequency_slot = + channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels; + // Old frequency selection formula // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index f85b3bfa54..ee4726d745 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -175,6 +175,9 @@ class RadioInterface /// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers virtual bool isIRQPending() { return false; } + // Whether we use the default frequency slot given our LoRa config (region and modem preset) + static bool uses_default_frequency_slot; + protected: int8_t power = 17; // Set by applyModemConfig() diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 898607ecab..426934be8b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -77,8 +77,7 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length) if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) { memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length()); p->decoded.payload.size = jsonPayloadStr.length(); - meshtastic_MeshPacket *packet = packetPool.allocCopy(*p); - service.sendToMesh(packet, RX_SRC_LOCAL); + service.sendToMesh(p, RX_SRC_LOCAL); } else { LOG_WARN("Received MQTT json payload too long, dropping\n"); } @@ -192,6 +191,11 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) jsonTopic = "msh" + jsonTopic; } + if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { + map_position_precision = moduleConfig.mqtt.map_report_settings.position_precision; + map_publish_interval_secs = moduleConfig.mqtt.map_report_settings.publish_interval_secs; + } + #ifdef HAS_NETWORKING if (!moduleConfig.mqtt.proxy_to_client_enabled) pubSub.setCallback(mqttCallback); @@ -365,27 +369,30 @@ void MQTT::sendSubscriptions() bool MQTT::wantsLink() const { - bool hasChannel = false; + bool hasChannelorMapReport = false; if (moduleConfig.mqtt.enabled) { - // No need for link if no channel needed it - size_t numChan = channels.getNumChannels(); - for (size_t i = 0; i < numChan; i++) { - const auto &ch = channels.getByIndex(i); - if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { - hasChannel = true; - break; + hasChannelorMapReport = moduleConfig.mqtt.map_reporting_enabled; + if (!hasChannelorMapReport) { + // No need for link if no channel needed it + size_t numChan = channels.getNumChannels(); + for (size_t i = 0; i < numChan; i++) { + const auto &ch = channels.getByIndex(i); + if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) { + hasChannelorMapReport = true; + break; + } } } } - if (hasChannel && moduleConfig.mqtt.proxy_to_client_enabled) + if (hasChannelorMapReport && moduleConfig.mqtt.proxy_to_client_enabled) return true; #if HAS_WIFI - return hasChannel && WiFi.isConnected(); + return hasChannelorMapReport && WiFi.isConnected(); #endif #if HAS_ETHERNET - return hasChannel && Ethernet.linkStatus() == LinkON; + return hasChannelorMapReport && Ethernet.linkStatus() == LinkON; #endif return false; } @@ -397,6 +404,8 @@ int32_t MQTT::runOnce() bool wantConnection = wantsLink(); + perhapsReportToMap(); + // If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server if (moduleConfig.mqtt.proxy_to_client_enabled) { publishQueuedMessages(); @@ -536,6 +545,78 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket & } } +void MQTT::perhapsReportToMap() +{ + if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) + return; + + if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) { + LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n"); + return; + } + + if (millis() - last_report_to_map < map_publish_interval_secs * 1000) { + return; + } else { + // Allocate ServiceEnvelope and fill it + meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed(); + se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id + se->gateway_id = owner.id; + + // Allocate MeshPacket and fill it + meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; + mp->from = nodeDB.getNodeNum(); + mp->to = NODENUM_BROADCAST; + mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP; + + // Fill MapReport message + meshtastic_MapReport mapReport = meshtastic_MapReport_init_default; + memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name)); + memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name)); + mapReport.role = config.device.role; + mapReport.hw_model = owner.hw_model; + strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version)); + mapReport.region = config.lora.region; + mapReport.modem_preset = config.lora.modem_preset; + mapReport.has_default_channel = channels.hasDefaultChannel(); + + // Set position with precision (same as in PositionModule) + if (map_position_precision < 32 && map_position_precision > 0) { + mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision)); + mapReport.latitude_i += (1 << (31 - map_position_precision)); + mapReport.longitude_i += (1 << (31 - map_position_precision)); + } else { + mapReport.latitude_i = localPosition.latitude_i; + mapReport.longitude_i = localPosition.longitude_i; + } + mapReport.altitude = localPosition.altitude; + mapReport.position_precision = map_position_precision; + + mapReport.num_online_local_nodes = nodeDB.getNumOnlineMeshNodes(true); + + // Encode MapReport message and set it to MeshPacket in ServiceEnvelope + mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), + &meshtastic_MapReport_msg, &mapReport); + se->packet = mp; + + // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets + static uint8_t bytes[meshtastic_MeshPacket_size + 64]; + size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); + + LOG_INFO("MQTT Publish map report to %s\n", statusTopic.c_str()); + publish(statusTopic.c_str(), bytes, numBytes, false); + + // Release the allocated memory for ServiceEnvelope and MeshPacket + mqttPool.release(se); + packetPool.release(mp); + + // Update the last report time + last_report_to_map = millis(); + } +} + // converts a downstream packet into a json message std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp) { diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index 2b803e3fcd..1599c7ae89 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -79,10 +79,16 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; - std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID - std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID - /** return true if we have a channel that wants uplink/downlink + std::string statusTopic = "/2/stat/"; // For "online"/"offline" message and MapReport + std::string cryptTopic = "/2/c/"; // msh/2/c/CHANNELID/NODEID + std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + + // For map reporting (only applies when enabled) + uint32_t last_report_to_map = 0; + uint32_t map_position_precision = 32; // default to full precision + uint32_t map_publish_interval_secs = 60 * 15; // default to 15 minutes + + /** return true if we have a channel that wants uplink/downlink or map reporting is enabled */ bool wantsLink() const; @@ -102,6 +108,9 @@ class MQTT : private concurrency::OSThread void publishStatus(); void publishQueuedMessages(); + // Check if we should report unencrypted information about our node for consumption by a map + void perhapsReportToMap(); + // returns true if this is a valid JSON envelope which we accept on downlink bool isValidJsonEnvelope(JSONObject &json); From 69dcc948b9631e798f232c3669e114a8da0ab9a7 Mon Sep 17 00:00:00 2001 From: caveman99 Date: Sun, 10 Mar 2024 14:39:40 +0000 Subject: [PATCH 03/18] [create-pull-request] automated change --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- .../generated/meshtastic/module_config.pb.c | 5 +- .../generated/meshtastic/module_config.pb.h | 42 ++++++++++-- src/mesh/generated/meshtastic/mqtt.pb.c | 3 + src/mesh/generated/meshtastic/mqtt.pb.h | 68 +++++++++++++++++++ src/mesh/generated/meshtastic/portnums.pb.h | 2 + 8 files changed, 117 insertions(+), 9 deletions(-) diff --git a/protobufs b/protobufs index 5a97acb175..00332412b2 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5a97acb17543a10e114675a205e3274a83e721af +Subproject commit 00332412b238fe559175a6e83fdf8d31fa5e209a diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index ca4b2176bb..556821e1c6 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -324,7 +324,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg; #define meshtastic_DeviceState_size 17571 #define meshtastic_NodeInfoLite_size 158 #define meshtastic_NodeRemoteHardwarePin_size 29 -#define meshtastic_OEMStore_size 3246 +#define meshtastic_OEMStore_size 3262 #define meshtastic_PositionLite_size 28 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 7d39da01f4..2e22cb1e4e 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -181,7 +181,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define meshtastic_LocalConfig_size 469 -#define meshtastic_LocalModuleConfig_size 631 +#define meshtastic_LocalModuleConfig_size 647 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.c b/src/mesh/generated/meshtastic/module_config.pb.c index 38965f3e25..a75c3fb594 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.c +++ b/src/mesh/generated/meshtastic/module_config.pb.c @@ -6,12 +6,15 @@ #error Regenerate this file with the current version of nanopb generator. #endif -PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig, meshtastic_ModuleConfig, 2) PB_BIND(meshtastic_ModuleConfig_MQTTConfig, meshtastic_ModuleConfig_MQTTConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_MapReportSettings, meshtastic_ModuleConfig_MapReportSettings, AUTO) + + PB_BIND(meshtastic_ModuleConfig_RemoteHardwareConfig, meshtastic_ModuleConfig_RemoteHardwareConfig, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index edfd56e4ca..2e1c25c7ff 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -84,6 +84,14 @@ typedef enum _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar { } meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar; /* Struct definitions */ +/* Settings for reporting unencrypted information about our node to a map via MQTT */ +typedef struct _meshtastic_ModuleConfig_MapReportSettings { + /* How often we should report our info to the map (in seconds) */ + uint32_t publish_interval_secs; + /* Bits of precision for the location sent (default of 32 is full precision). */ + uint32_t position_precision; +} meshtastic_ModuleConfig_MapReportSettings; + /* MQTT Client Config */ typedef struct _meshtastic_ModuleConfig_MQTTConfig { /* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as @@ -114,6 +122,11 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig { char root[16]; /* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection */ bool proxy_to_client_enabled; + /* If true, we will periodically report unencrypted information about our node to a map via MQTT */ + bool map_reporting_enabled; + /* Settings for reporting information about our node to a map via MQTT */ + bool has_map_report_settings; + meshtastic_ModuleConfig_MapReportSettings map_report_settings; } meshtastic_ModuleConfig_MQTTConfig; /* NeighborInfoModule Config */ @@ -427,6 +440,7 @@ extern "C" { + #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud @@ -447,7 +461,8 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ModuleConfig_init_default {0, {meshtastic_ModuleConfig_MQTTConfig_init_default}} -#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_default} +#define meshtastic_ModuleConfig_MapReportSettings_init_default {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_default {0, 0, 0, {meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default, meshtastic_RemoteHardwarePin_init_default}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0} @@ -462,7 +477,8 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} -#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0} +#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} +#define meshtastic_ModuleConfig_MapReportSettings_init_zero {0, 0} #define meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero {0, 0, 0, {meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero, meshtastic_RemoteHardwarePin_init_zero}} #define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0} #define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0} @@ -478,6 +494,8 @@ extern "C" { #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ +#define meshtastic_ModuleConfig_MapReportSettings_publish_interval_secs_tag 1 +#define meshtastic_ModuleConfig_MapReportSettings_position_precision_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_enabled_tag 1 #define meshtastic_ModuleConfig_MQTTConfig_address_tag 2 #define meshtastic_ModuleConfig_MQTTConfig_username_tag 3 @@ -487,6 +505,8 @@ extern "C" { #define meshtastic_ModuleConfig_MQTTConfig_tls_enabled_tag 7 #define meshtastic_ModuleConfig_MQTTConfig_root_tag 8 #define meshtastic_ModuleConfig_MQTTConfig_proxy_to_client_enabled_tag 9 +#define meshtastic_ModuleConfig_MQTTConfig_map_reporting_enabled_tag 10 +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_tag 11 #define meshtastic_ModuleConfig_NeighborInfoConfig_enabled_tag 1 #define meshtastic_ModuleConfig_NeighborInfoConfig_update_interval_tag 2 #define meshtastic_ModuleConfig_DetectionSensorConfig_enabled_tag 1 @@ -623,9 +643,18 @@ X(a, STATIC, SINGULAR, BOOL, encryption_enabled, 5) \ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) \ X(a, STATIC, SINGULAR, BOOL, tls_enabled, 7) \ X(a, STATIC, SINGULAR, STRING, root, 8) \ -X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) +X(a, STATIC, SINGULAR, BOOL, proxy_to_client_enabled, 9) \ +X(a, STATIC, SINGULAR, BOOL, map_reporting_enabled, 10) \ +X(a, STATIC, OPTIONAL, MESSAGE, map_report_settings, 11) #define meshtastic_ModuleConfig_MQTTConfig_CALLBACK NULL #define meshtastic_ModuleConfig_MQTTConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_MQTTConfig_map_report_settings_MSGTYPE meshtastic_ModuleConfig_MapReportSettings + +#define meshtastic_ModuleConfig_MapReportSettings_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, publish_interval_secs, 1) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 2) +#define meshtastic_ModuleConfig_MapReportSettings_CALLBACK NULL +#define meshtastic_ModuleConfig_MapReportSettings_DEFAULT NULL #define meshtastic_ModuleConfig_RemoteHardwareConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -764,6 +793,7 @@ X(a, STATIC, SINGULAR, UENUM, type, 3) extern const pb_msgdesc_t meshtastic_ModuleConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_MQTTConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_MapReportSettings_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_RemoteHardwareConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg; @@ -781,6 +811,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ModuleConfig_fields &meshtastic_ModuleConfig_msg #define meshtastic_ModuleConfig_MQTTConfig_fields &meshtastic_ModuleConfig_MQTTConfig_msg +#define meshtastic_ModuleConfig_MapReportSettings_fields &meshtastic_ModuleConfig_MapReportSettings_msg #define meshtastic_ModuleConfig_RemoteHardwareConfig_fields &meshtastic_ModuleConfig_RemoteHardwareConfig_msg #define meshtastic_ModuleConfig_NeighborInfoConfig_fields &meshtastic_ModuleConfig_NeighborInfoConfig_msg #define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg @@ -801,7 +832,8 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_CannedMessageConfig_size 49 #define meshtastic_ModuleConfig_DetectionSensorConfig_size 44 #define meshtastic_ModuleConfig_ExternalNotificationConfig_size 42 -#define meshtastic_ModuleConfig_MQTTConfig_size 222 +#define meshtastic_ModuleConfig_MQTTConfig_size 238 +#define meshtastic_ModuleConfig_MapReportSettings_size 12 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 8 #define meshtastic_ModuleConfig_PaxcounterConfig_size 8 #define meshtastic_ModuleConfig_RangeTestConfig_size 10 @@ -809,7 +841,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 22 #define meshtastic_ModuleConfig_TelemetryConfig_size 36 -#define meshtastic_ModuleConfig_size 225 +#define meshtastic_ModuleConfig_size 241 #define meshtastic_RemoteHardwarePin_size 21 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/mqtt.pb.c b/src/mesh/generated/meshtastic/mqtt.pb.c index 3046e6109f..a43f364e14 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.c +++ b/src/mesh/generated/meshtastic/mqtt.pb.c @@ -9,4 +9,7 @@ PB_BIND(meshtastic_ServiceEnvelope, meshtastic_ServiceEnvelope, AUTO) +PB_BIND(meshtastic_MapReport, meshtastic_MapReport, AUTO) + + diff --git a/src/mesh/generated/meshtastic/mqtt.pb.h b/src/mesh/generated/meshtastic/mqtt.pb.h index 12e83c7245..8ca570d786 100644 --- a/src/mesh/generated/meshtastic/mqtt.pb.h +++ b/src/mesh/generated/meshtastic/mqtt.pb.h @@ -5,6 +5,7 @@ #define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED #include #include "meshtastic/mesh.pb.h" +#include "meshtastic/config.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -23,6 +24,38 @@ typedef struct _meshtastic_ServiceEnvelope { char *gateway_id; } meshtastic_ServiceEnvelope; +/* Information about a node intended to be reported unencrypted to a map using MQTT. */ +typedef struct _meshtastic_MapReport { + /* A full name for this user, i.e. "Kevin Hester" */ + char long_name[40]; + /* A VERY short name, ideally two characters. + Suitable for a tiny OLED screen */ + char short_name[5]; + /* Role of the node that applies specific settings for a particular use-case */ + meshtastic_Config_DeviceConfig_Role role; + /* Hardware model of the node, i.e. T-Beam, Heltec V3, etc... */ + meshtastic_HardwareModel hw_model; + /* Device firmware version string */ + char firmware_version[18]; + /* The region code for the radio (US, CN, EU433, etc...) */ + meshtastic_Config_LoRaConfig_RegionCode region; + /* Modem preset used by the radio (LongFast, MediumSlow, etc...) */ + meshtastic_Config_LoRaConfig_ModemPreset modem_preset; + /* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...) + and it uses the default frequency slot given the region and modem preset. */ + bool has_default_channel; + /* Latitude: multiply by 1e-7 to get degrees in floating point */ + int32_t latitude_i; + /* Longitude: multiply by 1e-7 to get degrees in floating point */ + int32_t longitude_i; + /* Altitude in meters above MSL */ + int32_t altitude; + /* Indicates the bits of precision for latitude and longitude set by the sending node */ + uint32_t position_precision; + /* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT) */ + uint16_t num_online_local_nodes; +} meshtastic_MapReport; + #ifdef __cplusplus extern "C" { @@ -30,12 +63,27 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_ServiceEnvelope_init_default {NULL, NULL, NULL} +#define meshtastic_MapReport_init_default {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} #define meshtastic_ServiceEnvelope_init_zero {NULL, NULL, NULL} +#define meshtastic_MapReport_init_zero {"", "", _meshtastic_Config_DeviceConfig_Role_MIN, _meshtastic_HardwareModel_MIN, "", _meshtastic_Config_LoRaConfig_RegionCode_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, 0, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_ServiceEnvelope_packet_tag 1 #define meshtastic_ServiceEnvelope_channel_id_tag 2 #define meshtastic_ServiceEnvelope_gateway_id_tag 3 +#define meshtastic_MapReport_long_name_tag 1 +#define meshtastic_MapReport_short_name_tag 2 +#define meshtastic_MapReport_role_tag 3 +#define meshtastic_MapReport_hw_model_tag 4 +#define meshtastic_MapReport_firmware_version_tag 5 +#define meshtastic_MapReport_region_tag 6 +#define meshtastic_MapReport_modem_preset_tag 7 +#define meshtastic_MapReport_has_default_channel_tag 8 +#define meshtastic_MapReport_latitude_i_tag 9 +#define meshtastic_MapReport_longitude_i_tag 10 +#define meshtastic_MapReport_altitude_tag 11 +#define meshtastic_MapReport_position_precision_tag 12 +#define meshtastic_MapReport_num_online_local_nodes_tag 13 /* Struct field encoding specification for nanopb */ #define meshtastic_ServiceEnvelope_FIELDLIST(X, a) \ @@ -46,13 +94,33 @@ X(a, POINTER, SINGULAR, STRING, gateway_id, 3) #define meshtastic_ServiceEnvelope_DEFAULT NULL #define meshtastic_ServiceEnvelope_packet_MSGTYPE meshtastic_MeshPacket +#define meshtastic_MapReport_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, long_name, 1) \ +X(a, STATIC, SINGULAR, STRING, short_name, 2) \ +X(a, STATIC, SINGULAR, UENUM, role, 3) \ +X(a, STATIC, SINGULAR, UENUM, hw_model, 4) \ +X(a, STATIC, SINGULAR, STRING, firmware_version, 5) \ +X(a, STATIC, SINGULAR, UENUM, region, 6) \ +X(a, STATIC, SINGULAR, UENUM, modem_preset, 7) \ +X(a, STATIC, SINGULAR, BOOL, has_default_channel, 8) \ +X(a, STATIC, SINGULAR, SFIXED32, latitude_i, 9) \ +X(a, STATIC, SINGULAR, SFIXED32, longitude_i, 10) \ +X(a, STATIC, SINGULAR, INT32, altitude, 11) \ +X(a, STATIC, SINGULAR, UINT32, position_precision, 12) \ +X(a, STATIC, SINGULAR, UINT32, num_online_local_nodes, 13) +#define meshtastic_MapReport_CALLBACK NULL +#define meshtastic_MapReport_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_ServiceEnvelope_msg; +extern const pb_msgdesc_t meshtastic_MapReport_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_ServiceEnvelope_fields &meshtastic_ServiceEnvelope_msg +#define meshtastic_MapReport_fields &meshtastic_MapReport_msg /* Maximum encoded size of messages (where known) */ /* meshtastic_ServiceEnvelope_size depends on runtime parameters */ +#define meshtastic_MapReport_size 108 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h index 3f3e9aaee8..f576c78939 100644 --- a/src/mesh/generated/meshtastic/portnums.pb.h +++ b/src/mesh/generated/meshtastic/portnums.pb.h @@ -122,6 +122,8 @@ typedef enum _meshtastic_PortNum { /* ATAK Plugin Portnum for payloads from the official Meshtastic ATAK plugin */ meshtastic_PortNum_ATAK_PLUGIN = 72, + /* Provides unencrypted information about a node for consumption by a map via MQTT */ + meshtastic_PortNum_MAP_REPORT_APP = 73, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */ From b45a912409c067b9ea6c0c41799a1128800cab4b Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 15:56:00 +0100 Subject: [PATCH 04/18] Use dedicated `map` topic --- src/mqtt/MQTT.cpp | 6 ++++-- src/mqtt/MQTT.h | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index b33132aa40..2de35971ad 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -185,10 +185,12 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) statusTopic = moduleConfig.mqtt.root + statusTopic; cryptTopic = moduleConfig.mqtt.root + cryptTopic; jsonTopic = moduleConfig.mqtt.root + jsonTopic; + mapTopic = moduleConfig.mqtt.root + jsonTopic; } else { statusTopic = "msh" + statusTopic; cryptTopic = "msh" + cryptTopic; jsonTopic = "msh" + jsonTopic; + mapTopic = "msh" + mapTopic; } if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) { @@ -605,8 +607,8 @@ void MQTT::perhapsReportToMap() static uint8_t bytes[meshtastic_MeshPacket_size + 64]; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se); - LOG_INFO("MQTT Publish map report to %s\n", statusTopic.c_str()); - publish(statusTopic.c_str(), bytes, numBytes, false); + LOG_INFO("MQTT Publish map report to %s\n", mapTopic.c_str()); + publish(mapTopic.c_str(), bytes, numBytes, false); // Release the allocated memory for ServiceEnvelope and MeshPacket mqttPool.release(se); diff --git a/src/mqtt/MQTT.h b/src/mqtt/MQTT.h index b665a6efce..eeeb00d92e 100644 --- a/src/mqtt/MQTT.h +++ b/src/mqtt/MQTT.h @@ -79,9 +79,10 @@ class MQTT : private concurrency::OSThread virtual int32_t runOnce() override; private: - std::string statusTopic = "/2/stat/"; // For "online"/"offline" message and MapReport + std::string statusTopic = "/2/stat/"; // For "online"/"offline" message std::string cryptTopic = "/2/e/"; // msh/2/e/CHANNELID/NODEID std::string jsonTopic = "/2/json/"; // msh/2/json/CHANNELID/NODEID + std::string mapTopic = "/2/map/"; // For protobuf-encoded MapReport messages // For map reporting (only applies when enabled) uint32_t last_report_to_map = 0; From cb7407e06ba88f8c9d1aec45e0ccae2101cc5d99 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 10 Mar 2024 16:04:59 +0100 Subject: [PATCH 05/18] Don't need to check all channels if not using default frequency slot --- src/mesh/Channels.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index b50ecf6ca4..3e9c782419 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -257,8 +257,8 @@ const char *Channels::getName(size_t chIndex) bool Channels::hasDefaultChannel() { - // If we don't use a preset or we override the frequency, we don't have a default channel - if (!config.lora.use_preset || config.lora.override_frequency) + // If we don't use a preset or the default frequency slot, or we override the frequency, we don't have a default channel + if (!config.lora.use_preset || !RadioInterface::uses_default_frequency_slot || config.lora.override_frequency) return false; // Check if any of the channels are using the default name and PSK for (size_t i = 0; i < getNumChannels(); i++) { @@ -266,8 +266,8 @@ bool Channels::hasDefaultChannel() if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { const char *name = getName(i); const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); - // Check if the name is the default derived from the modem preset and we use the default frequency slot - if (strcmp(name, presetName) == 0 && RadioInterface::uses_default_frequency_slot) + // Check if the name is the default derived from the modem preset + if (strcmp(name, presetName) == 0) return true; } } From fb4faf790ba28fd09344c61a713728e647f6c711 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 5 Mar 2024 23:39:43 +0100 Subject: [PATCH 06/18] split query of paxcounter data from sending funcionality; don't cummulate (count mode != 1); use flag to signal changed count data --- src/modules/esp32/PaxcounterModule.cpp | 24 ++++++++++++++++++------ src/modules/esp32/PaxcounterModule.h | 3 +++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 2182ed1241..29edb069d9 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -7,8 +7,6 @@ PaxcounterModule *paxcounterModule; -void NullFunc(){}; - // paxcounterModule->sendInfo(NODENUM_BROADCAST); PaxcounterModule::PaxcounterModule() @@ -19,10 +17,14 @@ PaxcounterModule::PaxcounterModule() bool PaxcounterModule::sendInfo(NodeNum dest) { - libpax_counter_count(&count_from_libpax); - LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%d\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, + if (paxcounterModule->reportedDataSent) + return false; + + LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = true; + meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; pl.ble = count_from_libpax.ble_count; @@ -55,6 +57,14 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() return allocDataProtobuf(pl); } +void PaxcounterModule::handlePaxCounterReportRequest() +{ + // libpax_counter_count(&paxcounterModule->count_from_libpax); + LOG_INFO("(Reading): libPax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, + paxcounterModule->count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = false; +} + int32_t PaxcounterModule::runOnce() { if (isActive()) { @@ -76,12 +86,14 @@ int32_t PaxcounterModule::runOnce() libpax_update_config(&configuration); // internal processing initialization - libpax_counter_init(NullFunc, &count_from_libpax, UINT16_MAX, 1); + libpax_counter_init(handlePaxCounterReportRequest, &count_from_libpax, + moduleConfig.paxcounter.paxcounter_update_interval, 0); libpax_counter_start(); } else { sendInfo(NODENUM_BROADCAST); } - return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs); + // we check every second if the counter had new data to send + return 1000; } else { return disable(); } diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index e72f874505..67d47be560 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -13,10 +13,13 @@ class PaxcounterModule : private concurrency::OSThread, public ProtobufModule { bool firstTime = true; + bool reportedDataSent = true; public: PaxcounterModule(); + static void handlePaxCounterReportRequest(); + protected: struct count_payload_t count_from_libpax = {0, 0, 0}; virtual int32_t runOnce() override; From 73c77b663c0bff89aa28817d27faf667201088f6 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Tue, 5 Mar 2024 23:48:52 +0100 Subject: [PATCH 07/18] fix typo --- src/modules/esp32/PaxcounterModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 29edb069d9..94fcca36fd 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -77,7 +77,7 @@ int32_t PaxcounterModule::runOnce() libpax_default_config(&configuration); configuration.blecounter = 1; - configuration.blescantime = 0; // infinit + configuration.blescantime = 0; // infinite configuration.wificounter = 1; configuration.wifi_channel_map = WIFI_CHANNEL_ALL; configuration.wifi_channel_switch_interval = 50; From 09e08e0091dc2c4ef2dfc2debdf7bda7b4f6f5b2 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Wed, 6 Mar 2024 19:15:04 +0100 Subject: [PATCH 08/18] add some documentation, cleanup --- src/modules/esp32/PaxcounterModule.cpp | 39 +++++++++++++++++--------- src/modules/esp32/PaxcounterModule.h | 7 +++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index 94fcca36fd..e718d62613 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -7,7 +7,18 @@ PaxcounterModule *paxcounterModule; -// paxcounterModule->sendInfo(NODENUM_BROADCAST); +/** + * Callback function for libpax. + * We only clear our sent flag here, since this function is called from another thread, so we + * cannot send to the mesh directly. + */ +void PaxcounterModule::handlePaxCounterReportRequest() +{ + // The libpax library already updated our data structure, just before invoking this callback. + LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n", + paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000); + paxcounterModule->reportedDataSent = false; +} PaxcounterModule::PaxcounterModule() : concurrency::OSThread("PaxcounterModule"), @@ -15,15 +26,20 @@ PaxcounterModule::PaxcounterModule() { } +/** + * Send the Pax information to the mesh if we got new data from libpax. + * This is called periodically from our runOnce() method and will actually send the data to the mesh + * if libpax updated it since the last transmission through the callback. + * @param dest - destination node (usually NODENUM_BROADCAST) + * @return false if sending is unnecessary, true if information was sent + */ bool PaxcounterModule::sendInfo(NodeNum dest) { if (paxcounterModule->reportedDataSent) return false; - LOG_INFO("(Sending): pax: wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, count_from_libpax.ble_count, - millis() / 1000); - - paxcounterModule->reportedDataSent = true; + LOG_INFO("PaxcounterModule: sending pax info wifi=%d; ble=%d; uptime=%lu\n", count_from_libpax.wifi_count, + count_from_libpax.ble_count, millis() / 1000); meshtastic_Paxcount pl = meshtastic_Paxcount_init_default; pl.wifi = count_from_libpax.wifi_count; @@ -33,9 +49,12 @@ bool PaxcounterModule::sendInfo(NodeNum dest) meshtastic_MeshPacket *p = allocDataProtobuf(pl); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_MIN; + p->priority = meshtastic_MeshPacket_Priority_DEFAULT; service.sendToMesh(p, RX_SRC_LOCAL, true); + + paxcounterModule->reportedDataSent = true; + return true; } @@ -57,14 +76,6 @@ meshtastic_MeshPacket *PaxcounterModule::allocReply() return allocDataProtobuf(pl); } -void PaxcounterModule::handlePaxCounterReportRequest() -{ - // libpax_counter_count(&paxcounterModule->count_from_libpax); - LOG_INFO("(Reading): libPax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, - paxcounterModule->count_from_libpax.ble_count, millis() / 1000); - paxcounterModule->reportedDataSent = false; -} - int32_t PaxcounterModule::runOnce() { if (isActive()) { diff --git a/src/modules/esp32/PaxcounterModule.h b/src/modules/esp32/PaxcounterModule.h index 67d47be560..ebd6e71919 100644 --- a/src/modules/esp32/PaxcounterModule.h +++ b/src/modules/esp32/PaxcounterModule.h @@ -8,18 +8,19 @@ #include /** - * A simple example module that just replies with "Message received" to any message it receives. + * Wrapper module for the estimate passenger (PAX) count library (https://github.com/dbinfrago/libpax) which + * implements the core functionality of the ESP32 Paxcounter project (https://github.com/cyberman54/ESP32-Paxcounter) */ class PaxcounterModule : private concurrency::OSThread, public ProtobufModule { bool firstTime = true; bool reportedDataSent = true; + static void handlePaxCounterReportRequest(); + public: PaxcounterModule(); - static void handlePaxCounterReportRequest(); - protected: struct count_payload_t count_from_libpax = {0, 0, 0}; virtual int32_t runOnce() override; From 26691c0be7145b726eafad236def154cad3122d5 Mon Sep 17 00:00:00 2001 From: Thomas Herrmann Date: Fri, 8 Mar 2024 23:48:56 +0100 Subject: [PATCH 09/18] include requested change and suggestions on PR from @caveman99 --- src/modules/esp32/PaxcounterModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp index e718d62613..580fc46e19 100644 --- a/src/modules/esp32/PaxcounterModule.cpp +++ b/src/modules/esp32/PaxcounterModule.cpp @@ -18,6 +18,7 @@ void PaxcounterModule::handlePaxCounterReportRequest() LOG_INFO("PaxcounterModule: libpax reported new data: wifi=%d; ble=%d; uptime=%lu\n", paxcounterModule->count_from_libpax.wifi_count, paxcounterModule->count_from_libpax.ble_count, millis() / 1000); paxcounterModule->reportedDataSent = false; + paxcounterModule->setIntervalFromNow(0); } PaxcounterModule::PaxcounterModule() @@ -49,7 +50,7 @@ bool PaxcounterModule::sendInfo(NodeNum dest) meshtastic_MeshPacket *p = allocDataProtobuf(pl); p->to = dest; p->decoded.want_response = false; - p->priority = meshtastic_MeshPacket_Priority_DEFAULT; + p->priority = meshtastic_MeshPacket_Priority_MIN; service.sendToMesh(p, RX_SRC_LOCAL, true); @@ -103,8 +104,7 @@ int32_t PaxcounterModule::runOnce() } else { sendInfo(NODENUM_BROADCAST); } - // we check every second if the counter had new data to send - return 1000; + return getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval, default_broadcast_interval_secs); } else { return disable(); } From 766beefbc5796f700e5d6d94f7f9d7682cc0a4c8 Mon Sep 17 00:00:00 2001 From: Kevin Cai Date: Sun, 10 Mar 2024 18:24:32 -0400 Subject: [PATCH 10/18] Update AccelerometerThread.h to work with T-Watch S3 --- src/AccelerometerThread.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h index 744f0ad647..9898f4d49d 100644 --- a/src/AccelerometerThread.h +++ b/src/AccelerometerThread.h @@ -103,12 +103,21 @@ class AccelerometerThread : public concurrency::OSThread #endif struct bma423_axes_remap remap_data; +#ifdef T_WATCH_S3 + remap_data.x_axis = 1; + remap_data.x_axis_sign = 0; + remap_data.y_axis = 0; + remap_data.y_axis_sign = 0; + remap_data.z_axis = 2; + remap_data.z_axis_sign = 1; +#else remap_data.x_axis = 0; remap_data.x_axis_sign = 1; remap_data.y_axis = 1; remap_data.y_axis_sign = 0; remap_data.z_axis = 2; remap_data.z_axis_sign = 1; +#endif // Need to raise the wrist function, need to set the correct axis bmaSensor.setRemapAxes(&remap_data); // sensor.enableFeature(BMA423_STEP_CNTR, true); @@ -171,4 +180,4 @@ class AccelerometerThread : public concurrency::OSThread Adafruit_LIS3DH lis; }; -} // namespace concurrency \ No newline at end of file +} // namespace concurrency From b65b9e5d659b6bd8f4eb3314d8cf24f4892e47f4 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Thu, 7 Mar 2024 16:03:01 +0200 Subject: [PATCH 11/18] Include esp32c3 build step --- .github/workflows/build_esp32.yml | 1 + .github/workflows/build_esp32_c3.yml | 60 ++++++++++++++++++++++++++++ .github/workflows/build_esp32_s3.yml | 1 + .github/workflows/main_matrix.yml | 14 ++++++- 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build_esp32_c3.yml diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml index c9664152eb..31f0dd5a00 100644 --- a/.github/workflows/build_esp32.yml +++ b/.github/workflows/build_esp32.yml @@ -35,6 +35,7 @@ jobs: sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - name: Build ESP32 run: bin/build-esp32.sh ${{ inputs.board }} diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml new file mode 100644 index 0000000000..f9164b96a7 --- /dev/null +++ b/.github/workflows/build_esp32_c3.yml @@ -0,0 +1,60 @@ +name: Build ESP32-C3 + +on: + workflow_call: + inputs: + board: + required: true + type: string + +jobs: + build-esp32-c3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build base + id: base + uses: ./.github/actions/setup-base + + - name: Pull web ui + uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + with: + repo: 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 + - name: Remove debug flags for release + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini + - name: Build ESP32 + run: bin/build-esp32.sh ${{ inputs.board }} + + - name: Pull OTA Firmware + uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4 + with: + repo: meshtastic/firmware-ota + file: firmware-c3.bin + target: release/bleota-c3.bin + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release version string + shell: bash + run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT + id: version + + - name: Store binaries as an artifact + uses: actions/upload-artifact@v3 + with: + name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip + path: | + release/*.bin + release/*.elf diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml index 9611dd5b81..f603a6a31a 100644 --- a/.github/workflows/build_esp32_s3.yml +++ b/.github/workflows/build_esp32_s3.yml @@ -34,6 +34,7 @@ jobs: sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini + sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini - name: Build ESP32 run: bin/build-esp32.sh ${{ inputs.board }} diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index e77b4a261c..03d47f18e6 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -67,7 +67,6 @@ jobs: - board: tlora-v2-1-1_6-tcxo - board: tlora-v2-1-1_8 - board: tbeam - - board: heltec-ht62-esp32c3-sx1262 - board: heltec-v2_0 - board: heltec-v2_1 - board: tbeam0_7 @@ -105,6 +104,16 @@ jobs: with: board: ${{ matrix.board }} + build-esp32-c3: + strategy: + fail-fast: false + matrix: + include: + - board: heltec-ht62-esp32c3-sx1262 + uses: ./.github/workflows/build_esp32_c3.yml + with: + board: ${{ matrix.board }} + build-nrf52: strategy: fail-fast: false @@ -226,6 +235,7 @@ jobs: [ build-esp32, build-esp32-s3, + build-esp32-c3, build-nrf52, build-raspbian, build-native, @@ -251,7 +261,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml + run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml - name: Repackage in single firmware zip uses: actions/upload-artifact@v3 From b3ec3c20fbd80081328e44e7d4ce084afd42fa58 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Thu, 7 Mar 2024 16:31:20 +0200 Subject: [PATCH 12/18] Update device-install.sh files to account for bleota-c3.bin file --- bin/device-install.bat | 8 ++++++-- bin/device-install.sh | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index c7d8a10cf2..cb652346f1 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% ( %PYTHON% -m esptool --baud 115200 erase_flash %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% - @REM Account for S3 board's different OTA partition + @REM Account for S3 and C3 board's different OTA partition IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% ( - %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin + IF x%FILENAME:esp32c3=%==x%FILENAME% ( + %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin + ) else ( + %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin + ) ) else ( %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin ) diff --git a/bin/device-install.sh b/bin/device-install.sh index 35d99286d7..52a27309aa 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -51,7 +51,11 @@ if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then - "$PYTHON" -m esptool write_flash 0x260000 bleota.bin + if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then + "$PYTHON" -m esptool write_flash 0x260000 bleota.bin + else + "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin + fi else "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin fi From a493ab526f82979890d97e9354a2753eec8c06a7 Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 10:50:03 +0200 Subject: [PATCH 13/18] Trunk fmt to correct failing PR check for device-install.sh --- bin/device-install.sh | 56 ++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/bin/device-install.sh b/bin/device-install.sh index 52a27309aa..a4ee20c9c2 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -1,12 +1,12 @@ #!/bin/sh -PYTHON=${PYTHON:-$(which python3 python|head -n 1)} +PYTHON=${PYTHON:-$(which python3 python | head -n 1)} set -e # Usage info show_help() { -cat << EOF + cat <&2 - exit 1 - ;; - esac + case "${opt}" in + h) + show_help + exit 0 + ;; + p) + export ESPTOOL_PORT=${OPTARG} + ;; + P) + PYTHON=${OPTARG} + ;; + f) + FILENAME=${OPTARG} + ;; + *) + echo "Invalid flag." + show_help >&2 + exit 1 + ;; + esac done -shift "$((OPTIND-1))" +shift "$((OPTIND - 1))" [ -z "$FILENAME" -a -n "$1" ] && { - FILENAME=$1 - shift + FILENAME=$1 + shift } if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - "$PYTHON" -m esptool erase_flash - "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} + "$PYTHON" -m esptool erase_flash + "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then @@ -57,9 +59,9 @@ if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin fi else - "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin + "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin fi - "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin + "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin else show_help From f09e5c96fcad715a7e3b3fe4bb285f1348d6c29d Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 11:03:03 +0200 Subject: [PATCH 14/18] Add permission: read-all to silence CKV_GHA_1 check --- .github/workflows/build_esp32_c3.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml index f9164b96a7..a30cf33f10 100644 --- a/.github/workflows/build_esp32_c3.yml +++ b/.github/workflows/build_esp32_c3.yml @@ -7,6 +7,8 @@ on: required: true type: string +permissions: read-all + jobs: build-esp32-c3: runs-on: ubuntu-latest From 3a8f623f8adfc73e0c3b36453d8392394377713a Mon Sep 17 00:00:00 2001 From: David Ellefsen Date: Fri, 8 Mar 2024 11:27:31 +0200 Subject: [PATCH 15/18] Change '! -z' to '-n' to addresss shellcheck/SC2236 --- bin/device-install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/device-install.sh b/bin/device-install.sh index a4ee20c9c2..0e7bd8ada2 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -47,13 +47,13 @@ shift "$((OPTIND - 1))" shift } -if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then +if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" "$PYTHON" -m esptool erase_flash "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} # Account for S3 board's different OTA partition - if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then - if [ ! -z "${FILENAME##*"esp32c3"*}" ]; then + if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ]; then + if [ -n "${FILENAME##*"esp32c3"*}" ]; then "$PYTHON" -m esptool write_flash 0x260000 bleota.bin else "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin From 658ed6fd2874f8e7e73007a492ff81e40af1093b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 11 Mar 2024 13:51:26 +0100 Subject: [PATCH 16/18] tryfix SHT31 sensor on secondary bus --- src/mesh/NodeDB.h | 2 +- src/modules/Telemetry/Sensor/SHT31Sensor.cpp | 2 +- src/modules/Telemetry/Sensor/SHT31Sensor.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index 8545b08d67..e472f7151a 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -248,4 +248,4 @@ extern uint32_t error_address; #define Module_Config_size \ (ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \ ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \ - ModuleConfig_TelemetryConfig_size + ModuleConfig_size) \ No newline at end of file + ModuleConfig_TelemetryConfig_size + ModuleConfig_size) diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp index 7ffb68254d..7f2b7691ef 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp @@ -12,7 +12,7 @@ int32_t SHT31Sensor::runOnce() if (!hasSensor()) { return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = sht31.begin(); + status = sht31.begin(nodeTelemetrySensorsMap[sensorType].first); return initI2CSensor(); } diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h index 940361325a..9700bdf2c7 100644 --- a/src/modules/Telemetry/Sensor/SHT31Sensor.h +++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h @@ -5,7 +5,7 @@ class SHT31Sensor : public TelemetrySensor { private: - Adafruit_SHT31 sht31 = Adafruit_SHT31(); + Adafruit_SHT31 sht31 = Adafruit_SHT31(nodeTelemetrySensorsMap[sensorType].second); protected: virtual void setup() override; From 892223a297c23b788ae879b3a6dab92a10e391ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 11 Mar 2024 13:52:46 +0100 Subject: [PATCH 17/18] fix typos and add 2 missing modules to the equasion (#3370) --- src/configuration.h | 2 ++ src/modules/Modules.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 03170c1c7a..ac8f9435a7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -239,4 +239,6 @@ along with this program. If not, see . #define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1 #define MESHTASTIC_EXCLUDE_TRACEROUTE 1 #define MESHTASTIC_EXCLUDE_WAYPOINT 1 +#define MESHTASTIC_EXCLUDE_INPUTBROKER 1 +#define MESHTASTIC_EXCLUDE_SERIAL 1 #endif diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 4f0b8f2b0a..97ed90cf15 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -39,11 +39,11 @@ #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" #endif -#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif #ifdef ARCH_ESP32 @@ -138,13 +138,13 @@ void setupModules() #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) new DeviceTelemetryModule(); #endif -#if HAS_SENSOR && !EXCLUDE_ENVIRONMENTAL_SENSOR +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { new AirQualityTelemetryModule(); } #endif -#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !EXCLUDE_POWER_TELEMETRY +#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY new PowerTelemetryModule(); #endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ From cf4753f7fd32ce43e6f11b5760f1e0366f7c07a5 Mon Sep 17 00:00:00 2001 From: todd-herbert Date: Tue, 12 Mar 2024 01:56:55 +1300 Subject: [PATCH 18/18] Async full-refresh for EInkDynamicDisplay (#3339) * Move Wireless Paper V1.1 custom hibernate behavior to GxEPD2 * Async full-refresh for EInkDynamicDisplay * initial config for T-Echo * formatting responds to https://github.com/meshtastic/firmware/pull/3339#discussion_r1518175434 * increase fast-refresh limit for T-Echo https://github.com/meshtastic/firmware/pull/3339#issuecomment-1986245727 * change dependency from private repo to meshtastic/GxEPD2 --------- Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 25 +++--- src/graphics/EInkDisplay2.h | 7 ++ src/graphics/EInkDynamicDisplay.cpp | 90 +++++++++++++++++-- src/graphics/EInkDynamicDisplay.h | 15 +++- variants/heltec_wireless_paper/platformio.ini | 2 +- variants/heltec_wireless_paper/variant.h | 1 - variants/t-echo/platformio.ini | 8 +- 7 files changed, 121 insertions(+), 27 deletions(-) diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 6ee4245b31..6f7885b45b 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -71,28 +71,24 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) } } + // Trigger the refresh in GxEPD2 LOG_DEBUG("Updating E-Paper... "); - -#if false - // Currently unused; rescued from commented-out line during a refactor - // Use a meaningful macro here if variant doesn't want fast refresh - - // Full update mode (slow) - adafruitDisplay->display(false) -#else - // Fast update mode adafruitDisplay->nextPage(); -#endif -#ifndef EINK_NO_HIBERNATE // Only hibernate if controller IC will preserve image memory - // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display) - adafruitDisplay->hibernate(); -#endif + // End the update process + endUpdate(); LOG_DEBUG("done\n"); return true; } +// End the update process - virtual method, overriden in derived class +void EInkDisplay::endUpdate() +{ + // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep) + adafruitDisplay->hibernate(); +} + // Write the buffer to the display memory void EInkDisplay::display(void) { @@ -188,6 +184,7 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); + adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh) } #elif defined(PCA10059) { diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 75770a3bcf..f744164949 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -45,6 +45,13 @@ class EInkDisplay : public OLEDDisplay */ virtual bool forceDisplay(uint32_t msecLimit = 1000); + /** + * Run any code needed to complete an update, after the physical refresh has completed. + * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class. + * + */ + virtual void endUpdate(); + /** * shim to make the abstraction happy * diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index ae1e30fe1e..75db0e33fe 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -94,19 +94,29 @@ void EInkDynamicDisplay::adjustRefreshCounters() // Trigger the display update by calling base class bool EInkDynamicDisplay::update() { + // Detemine the refresh mode to use, and start the update bool refreshApproved = determineMode(); if (refreshApproved) EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system - return refreshApproved; // (Unutilized) Base class promises to return true if update ran + +#if defined(HAS_EINK_ASYNCFULL) + if (refreshApproved) + endOrDetach(); // Either endUpdate() right now (fast refresh), or set the async flag (full refresh) +#endif + + return refreshApproved; // (Unutilized) Base class promises to return true if update ran } // Assess situation, pick a refresh type bool EInkDynamicDisplay::determineMode() { - checkWasFlooded(); + checkForPromotion(); +#if defined(HAS_EINK_ASYNCFULL) + checkAsyncFullRefresh(); +#endif checkRateLimiting(); - // If too soon for a new time, abort here + // If too soon for a new frame, or display busy, abort early if (refresh == SKIPPED) { storeAndReset(); return false; // No refresh @@ -116,7 +126,7 @@ bool EInkDynamicDisplay::determineMode() resetRateLimiting(); // Once determineMode() ends, will have to wait again hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check - LOG_DEBUG("EInkDynamicDisplay: "); // Begin log entry + LOG_DEBUG("determineMode(): "); // Begin log entry // Once mode determined, any remaining checks will bypass checkCosmetic(); @@ -151,13 +161,25 @@ bool EInkDynamicDisplay::determineMode() } } -// Did RESPONSIVE frames previously exceed the rate-limit for fast refresh? -void EInkDynamicDisplay::checkWasFlooded() +// Was a frame skipped (rate, display busy) that should have been a FAST refresh? +void EInkDynamicDisplay::checkForPromotion() { - if (previousReason == EXCEEDED_RATELIMIT_FAST) { - // If so, allow a BACKGROUND frame to draw as RESPONSIVE - // Because we DID want a RESPONSIVE frame last time, we just didn't get it + // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame + // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it + + switch (previousReason) { + case ASYNC_REFRESH_BLOCKED_DEMANDFAST: + setFrameFlag(DEMAND_FAST); + break; + case ASYNC_REFRESH_BLOCKED_COSMETIC: + setFrameFlag(COSMETIC); + break; + case ASYNC_REFRESH_BLOCKED_RESPONSIVE: + case EXCEEDED_RATELIMIT_FAST: setFrameFlag(RESPONSIVE); + break; + default: + break; } } @@ -381,4 +403,54 @@ void EInkDynamicDisplay::resetGhostPixelTracking() } #endif // EINK_LIMIT_GHOSTING_PX +#ifdef HAS_EINK_ASYNCFULL +// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready +void EInkDynamicDisplay::checkAsyncFullRefresh() +{ + // No refresh taking place, continue with determineMode() + if (!asyncRefreshRunning) + return; + + // Full refresh still running + if (adafruitDisplay->epd2.isBusy()) { + // No refresh + refresh = SKIPPED; + + // Set the reason, marking what type of frame we're skipping + if (frameFlags & DEMAND_FAST) + reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST; + else if (frameFlags & COSMETIC) + reason = ASYNC_REFRESH_BLOCKED_COSMETIC; + else if (frameFlags & RESPONSIVE) + reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE; + else + reason = ASYNC_REFRESH_BLOCKED_BACKGROUND; + + return; + } + + // If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done + adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code + EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override) + asyncRefreshRunning = false; // Unset the flag + LOG_DEBUG("Async full-refresh complete\n"); + + // Note: this code only works because of a modification to meshtastic/GxEPD2. + // It is only equipped to intercept calls to nextPage() +} + +// Figure out who runs the post-update code +void EInkDynamicDisplay::endOrDetach() +{ + if (previousRefresh == FULL) { // Note: previousRefresh is the refresh from this loop. + asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. + LOG_DEBUG("Async full-refresh begins\n"); + } + + // Fast Refresh + else + EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves. +} +#endif // HAS_EINK_ASYNCFULL + #endif // USE_EINK_DYNAMICDISPLAY \ No newline at end of file diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index 2880c716b0..3dc00ba7c8 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -44,6 +44,11 @@ class EInkDynamicDisplay : public EInkDisplay }; enum reasonTypes : uint8_t { // How was the decision reached NO_OBJECTIONS, + ASYNC_REFRESH_BLOCKED_DEMANDFAST, + ASYNC_REFRESH_BLOCKED_COSMETIC, + ASYNC_REFRESH_BLOCKED_RESPONSIVE, + ASYNC_REFRESH_BLOCKED_BACKGROUND, + DISPLAY_NOT_READY_FOR_FULL, EXCEEDED_RATELIMIT_FAST, EXCEEDED_RATELIMIT_FULL, FLAGGED_COSMETIC, @@ -64,7 +69,7 @@ class EInkDynamicDisplay : public EInkDisplay bool update(); // Trigger the display update - determine mode, then call base class // Checks as part of determineMode() - void checkWasFlooded(); // Was the previous frame skipped for exceeding EINK_LIMIT_RATE_RESPONSIVE_SEC? + void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh? void checkRateLimiting(); // Is this frame too soon? void checkCosmetic(); // Was the COSMETIC flag set? void checkDemandingFast(); // Was the DEMAND_FAST flag set? @@ -99,6 +104,14 @@ class EInkDynamicDisplay : public EInkDisplay uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use #endif + + // Conditional - async full refresh - only with modified meshtastic/GxEPD2 +#if defined(HAS_EINK_ASYNCFULL) + void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready + void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() + void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() + bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() +#endif }; #endif \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 0abbe085e8..14275830a2 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -16,7 +16,7 @@ build_flags = -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2 + https://github.com/meshtastic/GxEPD2/ adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 upload_speed = 115200 \ No newline at end of file diff --git a/variants/heltec_wireless_paper/variant.h b/variants/heltec_wireless_paper/variant.h index 28bc8628a5..29b8bbbd14 100644 --- a/variants/heltec_wireless_paper/variant.h +++ b/variants/heltec_wireless_paper/variant.h @@ -5,7 +5,6 @@ #define I2C_SCL SCL #define USE_EINK -#define EINK_NO_HIBERNATE /* * eink display pins diff --git a/variants/t-echo/platformio.ini b/variants/t-echo/platformio.ini index 49ba3bb34b..c97341a3b0 100644 --- a/variants/t-echo/platformio.ini +++ b/variants/t-echo/platformio.ini @@ -11,10 +11,16 @@ build_flags = ${nrf52840_base.build_flags} -Ivariants/t-echo -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 -DEINK_WIDTH=200 -DEINK_HEIGHT=200 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=20 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates + -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/t-echo> lib_deps = ${nrf52840_base.lib_deps} - https://github.com/meshtastic/GxEPD2#afce87a97dda1ac31d8a28dc8fa7c6f55dc96a61 + https://github.com/meshtastic/GxEPD2 adafruit/Adafruit BusIO@^1.13.2 lewisxhe/PCF8563_Library@^1.0.1 ;upload_protocol = fs