mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-23 11:10:52 +00:00
Merge branch 'master' into wio-lr1110-refresh
This commit is contained in:
@@ -137,7 +137,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
|
||||
if (BleOta::getOtaAppVersion().isEmpty()) {
|
||||
LOG_INFO("No OTA firmware available, scheduling regular reboot in %d seconds\n", s);
|
||||
screen->startRebootScreen();
|
||||
screen->startAlert("Rebooting...");
|
||||
} else {
|
||||
screen->startFirmwareUpdateScreen();
|
||||
BleOta::switchToOtaApp();
|
||||
@@ -145,7 +145,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
}
|
||||
#else
|
||||
LOG_INFO("Not on ESP32, scheduling regular reboot in %d seconds\n", s);
|
||||
screen->startRebootScreen();
|
||||
screen->startAlert("Rebooting...");
|
||||
#endif
|
||||
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
|
||||
break;
|
||||
@@ -200,6 +200,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
case meshtastic_AdminMessage_remove_by_nodenum_tag: {
|
||||
LOG_INFO("Client is receiving a remove_nodenum command.\n");
|
||||
nodeDB->removeNodeByNum(r->remove_by_nodenum);
|
||||
this->notifyObservers(r); // Observed by screen
|
||||
break;
|
||||
}
|
||||
case meshtastic_AdminMessage_set_favorite_node_tag: {
|
||||
@@ -232,9 +233,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
if (gps != nullptr)
|
||||
gps->enable();
|
||||
#endif
|
||||
// Send our new fixed position to the mesh for good measure
|
||||
positionModule->sendOurPosition();
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -299,8 +300,8 @@ void AdminModule::handleGetModuleConfigResponse(const meshtastic_MeshPacket &mp,
|
||||
{
|
||||
// Skip if it's disabled or no pins are exposed
|
||||
if (!r->get_module_config_response.payload_variant.remote_hardware.enabled ||
|
||||
!r->get_module_config_response.payload_variant.remote_hardware.available_pins) {
|
||||
LOG_DEBUG("Remote hardware module disabled or no vailable_pins. Skipping...\n");
|
||||
r->get_module_config_response.payload_variant.remote_hardware.available_pins_count == 0) {
|
||||
LOG_DEBUG("Remote hardware module disabled or no available_pins. Skipping...\n");
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < devicestate.node_remote_hardware_pins_count; i++) {
|
||||
@@ -388,6 +389,15 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d\n", min_node_info_broadcast_secs);
|
||||
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
|
||||
}
|
||||
// Router Client is deprecated; Set it to client
|
||||
if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) {
|
||||
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
|
||||
moduleConfig.store_forward.is_server = true;
|
||||
changes |= SEGMENT_MODULECONFIG;
|
||||
requiresReboot = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case meshtastic_Config_position_tag:
|
||||
LOG_INFO("Setting config: Position\n");
|
||||
@@ -811,7 +821,7 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
|
||||
void AdminModule::reboot(int32_t seconds)
|
||||
{
|
||||
LOG_INFO("Rebooting in %d seconds\n", seconds);
|
||||
screen->startRebootScreen();
|
||||
screen->startAlert("Rebooting...");
|
||||
rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
#include "ProtobufModule.h"
|
||||
#if HAS_WIFI && !MESHTASTIC_EXCLUDE_WIFI
|
||||
#if HAS_WIFI
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Admin module for admin messages
|
||||
*/
|
||||
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>
|
||||
class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Observable<const meshtastic_AdminMessage *>
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@@ -148,8 +148,9 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
||||
if (this->currentMessageIndex == 0) {
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
|
||||
|
||||
UIFrameEvent e = {false, true};
|
||||
e.frameChanged = true;
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs
|
||||
UIFrameEvent e;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->notifyObservers(&e);
|
||||
|
||||
return 0;
|
||||
@@ -166,8 +167,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
||||
}
|
||||
}
|
||||
if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) {
|
||||
UIFrameEvent e = {false, true};
|
||||
e.frameChanged = true;
|
||||
UIFrameEvent e;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->currentMessageIndex = -1;
|
||||
|
||||
#if !defined(T_WATCH_S3) && !defined(RAK14014)
|
||||
@@ -353,6 +354,8 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
||||
}
|
||||
|
||||
if (validEvent) {
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame, next time it runs
|
||||
|
||||
// Let runOnce to be called immediately.
|
||||
if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
|
||||
setIntervalFromNow(0); // on fast keypresses, this isn't fast enough.
|
||||
@@ -378,6 +381,11 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
||||
p->decoded.payload.size++;
|
||||
}
|
||||
|
||||
// Only receive routing messages when expecting ACK for a canned message
|
||||
// Prevents the canned message module from regenerating the screen's frameset at unexpected times,
|
||||
// or raising a UIFrameEvent before another module has the chance
|
||||
this->waitingForAck = true;
|
||||
|
||||
LOG_INFO("Sending message id=%d, dest=%x, msg=%.*s\n", p->id, p->to, p->decoded.payload.size, p->decoded.payload.bytes);
|
||||
|
||||
service.sendToMesh(
|
||||
@@ -393,13 +401,13 @@ int32_t CannedMessageModule::runOnce()
|
||||
return INT32_MAX;
|
||||
}
|
||||
// LOG_DEBUG("Check status\n");
|
||||
UIFrameEvent e = {false, true};
|
||||
UIFrameEvent e;
|
||||
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) ||
|
||||
(this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) {
|
||||
// TODO: might have some feedback of sending state
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
temporaryMessage = "";
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->currentMessageIndex = -1;
|
||||
this->freetext = ""; // clear freetext
|
||||
this->cursor = 0;
|
||||
@@ -412,7 +420,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
|
||||
((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) {
|
||||
// Reset module
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->currentMessageIndex = -1;
|
||||
this->freetext = ""; // clear freetext
|
||||
this->cursor = 0;
|
||||
@@ -449,7 +457,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
}
|
||||
}
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->currentMessageIndex = -1;
|
||||
this->freetext = ""; // clear freetext
|
||||
this->cursor = 0;
|
||||
@@ -463,7 +471,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
} else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) {
|
||||
this->currentMessageIndex = 0;
|
||||
LOG_DEBUG("First touch (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage());
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
|
||||
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_UP) {
|
||||
if (this->messagesCount > 0) {
|
||||
@@ -567,7 +575,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
break;
|
||||
}
|
||||
if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the
|
||||
// display back to the default window
|
||||
case 0x08: // backspace
|
||||
@@ -597,14 +605,14 @@ int32_t CannedMessageModule::runOnce()
|
||||
// handle fn+s for shutdown
|
||||
case 0x9b:
|
||||
if (screen)
|
||||
screen->startShutdownScreen();
|
||||
screen->startAlert("Shutting down...");
|
||||
shutdownAtMsec = millis() + DEFAULT_SHUTDOWN_SECONDS * 1000;
|
||||
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
break;
|
||||
// and fn+r for reboot
|
||||
case 0x90:
|
||||
if (screen)
|
||||
screen->startRebootScreen();
|
||||
screen->startAlert("Rebooting...");
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
break;
|
||||
@@ -706,8 +714,8 @@ int CannedMessageModule::getPrevIndex()
|
||||
void CannedMessageModule::showTemporaryMessage(const String &message)
|
||||
{
|
||||
temporaryMessage = message;
|
||||
UIFrameEvent e = {false, true};
|
||||
e.frameChanged = true;
|
||||
UIFrameEvent e;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
notifyObservers(&e);
|
||||
runState = CANNED_MESSAGE_RUN_STATE_MESSAGE;
|
||||
// run this loop again in 2 seconds, next iteration will clear the display
|
||||
@@ -914,11 +922,13 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
||||
char buffer[50];
|
||||
|
||||
if (temporaryMessage.length() != 0) {
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame
|
||||
LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str());
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage);
|
||||
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) {
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
String displayString;
|
||||
@@ -940,6 +950,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
||||
display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi);
|
||||
}
|
||||
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) {
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(display->getWidth() / 2 + x, 0 + y + 12, "Sending...");
|
||||
@@ -948,7 +959,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled.");
|
||||
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
|
||||
|
||||
requestFocus(); // Tell Screen::setFrames to move to our module's frame
|
||||
#if defined(T_WATCH_S3) || defined(RAK14014)
|
||||
drawKeyboard(display, state, 0, 0);
|
||||
#else
|
||||
@@ -1030,16 +1041,18 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
||||
|
||||
ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP) {
|
||||
if (mp.decoded.portnum == meshtastic_PortNum_ROUTING_APP && waitingForAck) {
|
||||
// look for a request_id
|
||||
if (mp.decoded.request_id != 0) {
|
||||
UIFrameEvent e = {false, true};
|
||||
e.frameChanged = true;
|
||||
UIFrameEvent e;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
requestFocus(); // Tell Screen::setFrames that our module's frame should be shown, even if not "first" in the frameset
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED;
|
||||
this->incoming = service.getNodenumFromRequestId(mp.decoded.request_id);
|
||||
meshtastic_Routing decoded = meshtastic_Routing_init_default;
|
||||
pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_Routing_fields, &decoded);
|
||||
this->ack = decoded.error_reason == meshtastic_Routing_Error_NONE;
|
||||
waitingForAck = false; // No longer want routing packets
|
||||
this->notifyObservers(&e);
|
||||
// run the next time 2 seconds later
|
||||
setIntervalFromNow(2000);
|
||||
|
||||
@@ -81,9 +81,8 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
}
|
||||
|
||||
switch (p->decoded.portnum) {
|
||||
case meshtastic_PortNum_TEXT_MESSAGE_APP:
|
||||
case meshtastic_PortNum_ROUTING_APP:
|
||||
return true;
|
||||
return waitingForAck;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -140,7 +139,8 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
uint8_t numChannels = 0;
|
||||
ChannelIndex indexChannels[MAX_NUM_CHANNELS] = {0};
|
||||
NodeNum incoming = NODENUM_BROADCAST;
|
||||
bool ack = false; // True means ACK, false means NAK (error_reason != NONE)
|
||||
bool ack = false; // True means ACK, false means NAK (error_reason != NONE)
|
||||
bool waitingForAck = false; // Are currently interested in routing packets?
|
||||
float lastRxSnr = 0;
|
||||
int32_t lastRxRssi = 0;
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ int32_t DetectionSensorModule::runOnce()
|
||||
// of heartbeat. We only do this if the minimum broadcast interval is greater than zero, otherwise we'll only broadcast state
|
||||
// change detections.
|
||||
else if (moduleConfig.detection_sensor.state_broadcast_secs > 0 &&
|
||||
(millis() - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs)) {
|
||||
(millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs,
|
||||
default_telemetry_broadcast_interval_secs)) {
|
||||
sendCurrentStateMessage();
|
||||
return DELAYED_INTERVAL;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
|
||||
#include "modules/RemoteHardwareModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
|
||||
#include "modules/PowerStressModule.h"
|
||||
#endif
|
||||
#include "modules/RoutingModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
@@ -115,6 +118,9 @@ void setupModules()
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
|
||||
new RemoteHardwareModule();
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_POWERSTRESS
|
||||
new PowerStressModule();
|
||||
#endif
|
||||
// Example: Put your module here
|
||||
// new ReplyModule();
|
||||
|
||||
@@ -39,11 +39,12 @@ NeighborInfoModule::NeighborInfoModule()
|
||||
concurrency::OSThread("NeighborInfoModule")
|
||||
{
|
||||
ourPortNum = meshtastic_PortNum_NEIGHBORINFO_APP;
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
if (moduleConfig.neighbor_info.enabled) {
|
||||
isPromiscuous = true; // Update neighbors from all packets
|
||||
setIntervalFromNow(
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs));
|
||||
setIntervalFromNow(Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval,
|
||||
default_telemetry_broadcast_interval_secs));
|
||||
} else {
|
||||
LOG_DEBUG("NeighborInfoModule is disabled\n");
|
||||
disable();
|
||||
@@ -119,7 +120,8 @@ int32_t NeighborInfoModule::runOnce()
|
||||
if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
|
||||
sendNeighborInfo(NODENUM_BROADCAST, false);
|
||||
}
|
||||
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs);
|
||||
return Default::getConfiguredOrDefaultMsScaled(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs,
|
||||
numOnlineNodes);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
class NeighborInfoModule : public ProtobufModule<meshtastic_NeighborInfo>, private concurrency::OSThread
|
||||
{
|
||||
CallbackObserver<NeighborInfoModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<NeighborInfoModule, const meshtastic::Status *>(this, &NeighborInfoModule::handleStatusUpdate);
|
||||
|
||||
std::vector<meshtastic_Neighbor> neighbors;
|
||||
|
||||
public:
|
||||
|
||||
@@ -28,6 +28,8 @@ PositionModule::PositionModule()
|
||||
{
|
||||
precision = 0; // safe starting value
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
setIntervalFromNow(60 * 1000);
|
||||
@@ -73,7 +75,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
}
|
||||
|
||||
// Log packet size and data fields
|
||||
LOG_DEBUG("POSITION node=%08x l=%d latI=%d lonI=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d "
|
||||
LOG_DEBUG("POSITION node=%08x l=%d lat=%d lon=%d msl=%d hae=%d geo=%d pdop=%d hdop=%d vdop=%d siv=%d fxq=%d fxt=%d pts=%d "
|
||||
"time=%d\n",
|
||||
getFrom(&mp), mp.decoded.payload.size, p.latitude_i, p.longitude_i, p.altitude, p.altitude_hae,
|
||||
p.altitude_geoidal_separation, p.PDOP, p.HDOP, p.VDOP, p.sats_in_view, p.fix_quality, p.fix_type, p.timestamp,
|
||||
@@ -219,7 +221,7 @@ meshtastic_MeshPacket *PositionModule::allocReply()
|
||||
LOG_INFO("Providing time to mesh %u\n", p.time);
|
||||
}
|
||||
|
||||
LOG_INFO("Position reply: time=%i, latI=%i, lonI=%i\n", p.time, p.latitude_i, p.longitude_i);
|
||||
LOG_INFO("Position reply: time=%i lat=%i lon=%i\n", p.time, p.latitude_i, p.longitude_i);
|
||||
|
||||
// TAK Tracker devices should send their position in a TAK packet over the ATAK port
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)
|
||||
@@ -333,8 +335,8 @@ int32_t PositionModule::runOnce()
|
||||
|
||||
// We limit our GPS broadcasts to a max rate
|
||||
uint32_t now = millis();
|
||||
uint32_t intervalMs =
|
||||
Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
|
||||
uint32_t intervalMs = Default::getConfiguredOrDefaultMsScaled(config.position.position_broadcast_secs,
|
||||
default_broadcast_interval_secs, numOnlineNodes);
|
||||
uint32_t msSinceLastSend = now - lastGpsSend;
|
||||
// Only send packets if the channel util. is less than 25% utilized or we're a tracker with less than 40% utilized.
|
||||
if (!airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
*/
|
||||
class PositionModule : public ProtobufModule<meshtastic_Position>, private concurrency::OSThread
|
||||
{
|
||||
CallbackObserver<PositionModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<PositionModule, const meshtastic::Status *>(this, &PositionModule::handleStatusUpdate);
|
||||
|
||||
/// The id of the last packet we sent, to allow us to cancel it if we make something fresher
|
||||
PacketId prevPacketId = 0;
|
||||
|
||||
@@ -59,7 +62,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
void sendLostAndFoundText();
|
||||
|
||||
const uint32_t minimumTimeThreshold =
|
||||
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
|
||||
Default::getConfiguredOrDefaultMsScaled(config.position.broadcast_smart_minimum_interval_secs, 30, numOnlineNodes);
|
||||
};
|
||||
|
||||
struct SmartPosition {
|
||||
|
||||
77
src/modules/PowerStressModule.cpp
Normal file
77
src/modules/PowerStressModule.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "PowerStressModule.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
extern void printInfo();
|
||||
|
||||
PowerStressModule::PowerStressModule()
|
||||
: ProtobufModule("powerstress", meshtastic_PortNum_POWERSTRESS_APP, &meshtastic_PowerStressMessage_msg),
|
||||
concurrency::OSThread("PowerStressModule")
|
||||
{
|
||||
}
|
||||
|
||||
bool PowerStressModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_PowerStressMessage *pptr)
|
||||
{
|
||||
// We only respond to messages if powermon debugging is already on
|
||||
if (config.power.powermon_enables) {
|
||||
auto p = *pptr;
|
||||
LOG_INFO("Received PowerStress cmd=%d\n", p.cmd);
|
||||
|
||||
// Some commands we can handle immediately, anything else gets deferred to be handled by our thread
|
||||
switch (p.cmd) {
|
||||
case meshtastic_PowerStressMessage_Opcode_UNSET:
|
||||
LOG_ERROR("PowerStress operation unset\n");
|
||||
break;
|
||||
|
||||
case meshtastic_PowerStressMessage_Opcode_PRINT_INFO:
|
||||
printInfo();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (currentMessage.cmd != meshtastic_PowerStressMessage_Opcode_UNSET)
|
||||
LOG_ERROR("PowerStress operation %d already in progress! Can't start new command\n", currentMessage.cmd);
|
||||
else
|
||||
currentMessage = p; // copy for use by thread (the message provided to us will be getting freed)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t PowerStressModule::runOnce()
|
||||
{
|
||||
|
||||
if (!config.power.powermon_enables) {
|
||||
// Powermon not enabled - stop using CPU/stop this thread
|
||||
return disable();
|
||||
}
|
||||
|
||||
int32_t sleep_msec = 10; // when not active check for new messages every 10ms
|
||||
|
||||
auto &p = currentMessage;
|
||||
|
||||
if (isRunningCommand) {
|
||||
// Done with the previous command - our sleep must have finished
|
||||
p.cmd = meshtastic_PowerStressMessage_Opcode_UNSET;
|
||||
p.num_seconds = 0;
|
||||
} else {
|
||||
sleep_msec = (int32_t)(p.num_seconds * 1000);
|
||||
isRunningCommand = !!sleep_msec; // if the command wants us to sleep, make sure to mark that we have something running
|
||||
|
||||
switch (p.cmd) {
|
||||
case meshtastic_PowerStressMessage_Opcode_UNSET: // No need to start a new command
|
||||
break;
|
||||
case meshtastic_PowerStressMessage_Opcode_LED_ON:
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("PowerStress operation %d not yet implemented!\n", p.cmd);
|
||||
sleep_msec = 0; // Don't do whatever sleep was requested...
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sleep_msec;
|
||||
}
|
||||
38
src/modules/PowerStressModule.h
Normal file
38
src/modules/PowerStressModule.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "ProtobufModule.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "mesh/generated/meshtastic/powermon.pb.h"
|
||||
|
||||
/**
|
||||
* A module that provides easy low-level remote access to device hardware.
|
||||
*/
|
||||
class PowerStressModule : public ProtobufModule<meshtastic_PowerStressMessage>, private concurrency::OSThread
|
||||
{
|
||||
meshtastic_PowerStressMessage currentMessage = meshtastic_PowerStressMessage_init_default;
|
||||
bool isRunningCommand = false;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
PowerStressModule();
|
||||
|
||||
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 meshtastic_MeshPacket &mp, meshtastic_PowerStressMessage *p) override;
|
||||
|
||||
/**
|
||||
* Periodically read the gpios we have been asked to WATCH, if they have changed,
|
||||
* broadcast a message with the change information.
|
||||
*
|
||||
* The method that will be called each time our thread gets a chance to run
|
||||
*
|
||||
* Returns desired period for next invocation (or RUN_SAME for no change)
|
||||
*/
|
||||
virtual int32_t runOnce() override;
|
||||
};
|
||||
|
||||
extern PowerStressModule powerStressModule;
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SerialModule.h"
|
||||
#include "GeoCoord.h"
|
||||
#include "MeshService.h"
|
||||
#include "NMEAWPL.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -66,7 +67,7 @@ SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Seria
|
||||
static Print *serialPrint = &Serial2;
|
||||
#endif
|
||||
|
||||
char serialBytes[meshtastic_Constants_DATA_PAYLOAD_LEN];
|
||||
char serialBytes[512];
|
||||
size_t serialPayloadSize;
|
||||
|
||||
SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
|
||||
@@ -198,8 +199,12 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE)
|
||||
else {
|
||||
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
|
||||
processWXSerial();
|
||||
|
||||
} else {
|
||||
while (Serial2.available()) {
|
||||
serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
serialModuleRadio->sendPayload();
|
||||
@@ -213,6 +218,27 @@ int32_t SerialModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends telemetry packet over the mesh network.
|
||||
*
|
||||
* @param m The telemetry data to be sent
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws None
|
||||
*/
|
||||
void SerialModule::sendTelemetry(meshtastic_Telemetry m)
|
||||
{
|
||||
meshtastic_MeshPacket *p = router->allocForSending();
|
||||
p->decoded.portnum = meshtastic_PortNum_TELEMETRY_APP;
|
||||
p->decoded.payload.size =
|
||||
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Telemetry_msg, &m);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new mesh packet for use as a reply to a received packet.
|
||||
*
|
||||
@@ -357,4 +383,162 @@ uint32_t SerialModule::getBaudRate()
|
||||
}
|
||||
return BAUD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the received weather station serial data, extract wind, voltage, and temperature information,
|
||||
* calculate averages and send telemetry data over the mesh network.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void SerialModule::processWXSerial()
|
||||
{
|
||||
#if !defined(TTGO_T_ECHO) && !defined(CANARYONE)
|
||||
static unsigned int lastAveraged = 0;
|
||||
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
|
||||
static double dir_sum_sin = 0;
|
||||
static double dir_sum_cos = 0;
|
||||
static float velSum = 0;
|
||||
static float gust = 0;
|
||||
static float lull = -1;
|
||||
static int velCount = 0;
|
||||
static int dirCount = 0;
|
||||
static char windDir[4] = "xxx"; // Assuming windDir is 3 characters long + null terminator
|
||||
static char windVel[5] = "xx.x"; // Assuming windVel is 4 characters long + null terminator
|
||||
static char windGust[5] = "xx.x"; // Assuming windGust is 4 characters long + null terminator
|
||||
static char batVoltage[5] = "0.0V";
|
||||
static char capVoltage[5] = "0.0V";
|
||||
static float batVoltageF = 0;
|
||||
static float capVoltageF = 0;
|
||||
bool gotwind = false;
|
||||
|
||||
while (Serial2.available()) {
|
||||
// clear serialBytes buffer
|
||||
memset(serialBytes, '\0', sizeof(serialBytes));
|
||||
// memset(formattedString, '\0', sizeof(formattedString));
|
||||
serialPayloadSize = Serial2.readBytes(serialBytes, 512);
|
||||
// check for a strings we care about
|
||||
// example output of serial data fields from the WS85
|
||||
// WindDir = 79
|
||||
// WindSpeed = 0.5
|
||||
// WindGust = 0.6
|
||||
// GXTS04Temp = 24.4
|
||||
if (serialPayloadSize > 0) {
|
||||
// Define variables for line processing
|
||||
int lineStart = 0;
|
||||
int lineEnd = -1;
|
||||
|
||||
// Process each byte in the received data
|
||||
for (size_t i = 0; i < serialPayloadSize; i++) {
|
||||
// go until we hit the end of line and then process the line
|
||||
if (serialBytes[i] == '\n') {
|
||||
lineEnd = i;
|
||||
// Extract the current line
|
||||
char line[meshtastic_Constants_DATA_PAYLOAD_LEN];
|
||||
memset(line, '\0', sizeof(line));
|
||||
memcpy(line, &serialBytes[lineStart], lineEnd - lineStart);
|
||||
|
||||
if (strstr(line, "Wind") != NULL) // we have a wind line
|
||||
{
|
||||
gotwind = true;
|
||||
// Find the positions of "=" signs in the line
|
||||
char *windDirPos = strstr(line, "WindDir = ");
|
||||
char *windSpeedPos = strstr(line, "WindSpeed = ");
|
||||
char *windGustPos = strstr(line, "WindGust = ");
|
||||
|
||||
if (windDirPos != NULL) {
|
||||
// Extract data after "=" for WindDir
|
||||
strcpy(windDir, windDirPos + 15); // Add 15 to skip "WindDir = "
|
||||
double radians = toRadians(strtof(windDir, nullptr));
|
||||
dir_sum_sin += sin(radians);
|
||||
dir_sum_cos += cos(radians);
|
||||
dirCount++;
|
||||
} else if (windSpeedPos != NULL) {
|
||||
// Extract data after "=" for WindSpeed
|
||||
strcpy(windVel, windSpeedPos + 15); // Add 15 to skip "WindSpeed = "
|
||||
float newv = strtof(windVel, nullptr);
|
||||
velSum += newv;
|
||||
velCount++;
|
||||
if (newv < lull || lull == -1)
|
||||
lull = newv;
|
||||
|
||||
} else if (windGustPos != NULL) {
|
||||
strcpy(windGust, windGustPos + 15); // Add 15 to skip "WindSpeed = "
|
||||
float newg = strtof(windGust, nullptr);
|
||||
if (newg > gust)
|
||||
gust = newg;
|
||||
}
|
||||
|
||||
// these are also voltage data we care about possibly
|
||||
} else if (strstr(line, "BatVoltage") != NULL) { // we have a battVoltage line
|
||||
char *batVoltagePos = strstr(line, "BatVoltage = ");
|
||||
if (batVoltagePos != NULL) {
|
||||
strcpy(batVoltage, batVoltagePos + 17); // 18 for ws 80, 17 for ws85
|
||||
batVoltageF = strtof(batVoltage, nullptr);
|
||||
break; // last possible data we want so break
|
||||
}
|
||||
} else if (strstr(line, "CapVoltage") != NULL) { // we have a cappVoltage line
|
||||
char *capVoltagePos = strstr(line, "CapVoltage = ");
|
||||
if (capVoltagePos != NULL) {
|
||||
strcpy(capVoltage, capVoltagePos + 17); // 18 for ws 80, 17 for ws85
|
||||
capVoltageF = strtof(capVoltage, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Update lineStart for the next line
|
||||
lineStart = lineEnd + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// clear the input buffer
|
||||
while (Serial2.available() > 0) {
|
||||
Serial2.read(); // Read and discard the bytes in the input buffer
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gotwind) {
|
||||
|
||||
LOG_INFO("WS85 : %i %.1fg%.1f %.1fv %.1fv\n", atoi(windDir), strtof(windVel, nullptr), strtof(windGust, nullptr),
|
||||
batVoltageF, capVoltageF);
|
||||
}
|
||||
if (gotwind && millis() - lastAveraged > averageIntervalMillis) {
|
||||
// calulate averages and send to the mesh
|
||||
float velAvg = 1.0 * velSum / velCount;
|
||||
|
||||
double avgSin = dir_sum_sin / dirCount;
|
||||
double avgCos = dir_sum_cos / dirCount;
|
||||
|
||||
double avgRadians = atan2(avgSin, avgCos);
|
||||
float dirAvg = toDegrees(avgRadians);
|
||||
|
||||
if (dirAvg < 0) {
|
||||
dirAvg += 360.0;
|
||||
}
|
||||
lastAveraged = millis();
|
||||
|
||||
// make a telemetry packet with the data
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
m.which_variant = meshtastic_Telemetry_environment_metrics_tag;
|
||||
m.variant.environment_metrics.wind_speed = velAvg;
|
||||
m.variant.environment_metrics.wind_direction = dirAvg;
|
||||
m.variant.environment_metrics.wind_gust = gust;
|
||||
m.variant.environment_metrics.wind_lull = lull;
|
||||
m.variant.environment_metrics.voltage =
|
||||
capVoltageF > batVoltageF ? capVoltageF : batVoltageF; // send the larger of the two voltage values.
|
||||
|
||||
LOG_INFO("WS85 Transmit speed=%fm/s, direction=%d , lull=%f, gust=%f, voltage=%f\n",
|
||||
m.variant.environment_metrics.wind_speed, m.variant.environment_metrics.wind_direction,
|
||||
m.variant.environment_metrics.wind_lull, m.variant.environment_metrics.wind_gust,
|
||||
m.variant.environment_metrics.voltage);
|
||||
|
||||
sendTelemetry(m);
|
||||
|
||||
// reset counters and gust/lull
|
||||
velSum = velCount = dirCount = 0;
|
||||
dir_sum_sin = dir_sum_cos = 0;
|
||||
gust = 0;
|
||||
lull = -1;
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -28,6 +28,8 @@ class SerialModule : public StreamAPI, private concurrency::OSThread
|
||||
|
||||
private:
|
||||
uint32_t getBaudRate();
|
||||
void sendTelemetry(meshtastic_Telemetry m);
|
||||
void processWXSerial();
|
||||
};
|
||||
|
||||
extern SerialModule *serialModule;
|
||||
|
||||
@@ -47,7 +47,9 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval))) &&
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
@@ -85,53 +87,90 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
|
||||
return false; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
if (!aqi.read(&data)) {
|
||||
LOG_WARN("Skipping send measurements. Could not read AQIn\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
m.time = getTime();
|
||||
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m.variant.air_quality_metrics.pm10_standard = data.pm10_standard;
|
||||
m.variant.air_quality_metrics.pm25_standard = data.pm25_standard;
|
||||
m.variant.air_quality_metrics.pm100_standard = data.pm100_standard;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
|
||||
m->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
|
||||
m->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
|
||||
|
||||
m.variant.air_quality_metrics.pm10_environmental = data.pm10_env;
|
||||
m.variant.air_quality_metrics.pm25_environmental = data.pm25_env;
|
||||
m.variant.air_quality_metrics.pm100_environmental = data.pm100_env;
|
||||
m->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
|
||||
m->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
|
||||
m->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
|
||||
|
||||
LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i\n",
|
||||
m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,
|
||||
m.variant.air_quality_metrics.pm100_standard);
|
||||
m->variant.air_quality_metrics.pm10_standard, m->variant.air_quality_metrics.pm25_standard,
|
||||
m->variant.air_quality_metrics.pm100_standard);
|
||||
|
||||
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i\n",
|
||||
m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental,
|
||||
m.variant.air_quality_metrics.pm100_environmental);
|
||||
m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental,
|
||||
m->variant.air_quality_metrics.pm100_environmental);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
p->decoded.want_response = false;
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||
else
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
|
||||
// release previous packet before occupying a new spot
|
||||
if (lastMeasurementPacket != nullptr)
|
||||
packetPool.release(lastMeasurementPacket);
|
||||
|
||||
lastMeasurementPacket = packetPool.allocCopy(*p);
|
||||
if (phoneOnly) {
|
||||
LOG_INFO("Sending packet to phone\n");
|
||||
service.sendToPhone(p);
|
||||
} else {
|
||||
LOG_INFO("Sending packet to mesh\n");
|
||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
meshtastic_Telemetry *decoded = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding AirQualityTelemetry module!\n");
|
||||
return NULL;
|
||||
}
|
||||
// Check for a request for air quality metrics
|
||||
if (decoded->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
if (getAirQualityTelemetry(&m)) {
|
||||
LOG_INFO("Air quality telemetry replying to request\n");
|
||||
return allocDataProtobuf(m);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
{
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
if (getAirQualityTelemetry(&m)) {
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
p->decoded.want_response = false;
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)
|
||||
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||
else
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
|
||||
// release previous packet before occupying a new spot
|
||||
if (lastMeasurementPacket != nullptr)
|
||||
packetPool.release(lastMeasurementPacket);
|
||||
|
||||
lastMeasurementPacket = packetPool.allocCopy(*p);
|
||||
if (phoneOnly) {
|
||||
LOG_INFO("Sending packet to phone\n");
|
||||
service.sendToPhone(p);
|
||||
} else {
|
||||
LOG_INFO("Sending packet to mesh\n");
|
||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *>(this,
|
||||
&AirQualityTelemetryModule::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
AirQualityTelemetryModule()
|
||||
: concurrency::OSThread("AirQualityTelemetryModule"),
|
||||
@@ -18,6 +22,7 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
|
||||
lastMeasurementPacket = nullptr;
|
||||
setIntervalFromNow(10 * 1000);
|
||||
aqi = Adafruit_PM25AQI();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -26,6 +31,11 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
|
||||
virtual int32_t runOnce() override;
|
||||
/** Called to get current Air Quality data
|
||||
@return true if it contains valid data
|
||||
*/
|
||||
bool getAirQualityTelemetry(meshtastic_Telemetry *m);
|
||||
virtual meshtastic_MeshPacket *allocReply() override;
|
||||
/**
|
||||
* Send our Telemetry into the mesh
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,9 @@ int32_t DeviceTelemetryModule::runOnce()
|
||||
{
|
||||
refreshUptime();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.device_update_interval))) &&
|
||||
((uptimeLastMs - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
|
||||
@@ -52,14 +54,27 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
||||
|
||||
meshtastic_MeshPacket *DeviceTelemetryModule::allocReply()
|
||||
{
|
||||
if (ignoreRequest) {
|
||||
return NULL;
|
||||
if (currentRequest) {
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
meshtastic_Telemetry *decoded = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding DeviceTelemetry module!\n");
|
||||
return NULL;
|
||||
}
|
||||
// Check for a request for device metrics
|
||||
if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||
LOG_INFO("Device telemetry replying to request\n");
|
||||
|
||||
meshtastic_Telemetry telemetry = getDeviceTelemetry();
|
||||
return allocDataProtobuf(telemetry);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Device telemetry replying to request\n");
|
||||
|
||||
meshtastic_Telemetry telemetry = getDeviceTelemetry();
|
||||
return allocDataProtobuf(telemetry);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
|
||||
@@ -104,4 +119,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
service.sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<DeviceTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<DeviceTelemetryModule, const meshtastic::Status *>(this, &DeviceTelemetryModule::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
DeviceTelemetryModule()
|
||||
: concurrency::OSThread("DeviceTelemetryModule"),
|
||||
@@ -14,6 +17,7 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
{
|
||||
uptimeWrapCount = 0;
|
||||
uptimeLastMs = millis();
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(45 * 1000); // Wait until NodeInfo is sent
|
||||
}
|
||||
virtual bool wantUIFrame() { return false; }
|
||||
|
||||
@@ -69,7 +69,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
{
|
||||
if (sleepOnNextExecution == true) {
|
||||
sleepOnNextExecution = false;
|
||||
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval);
|
||||
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval,
|
||||
default_telemetry_broadcast_interval_secs);
|
||||
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs);
|
||||
doDeepSleep(nightyNightMs, true);
|
||||
}
|
||||
@@ -124,6 +125,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = ina219Sensor.runOnce();
|
||||
if (ina260Sensor.hasSensor())
|
||||
result = ina260Sensor.runOnce();
|
||||
if (ina3221Sensor.hasSensor())
|
||||
result = ina3221Sensor.runOnce();
|
||||
if (veml7700Sensor.hasSensor())
|
||||
result = veml7700Sensor.runOnce();
|
||||
if (tsl2591Sensor.hasSensor())
|
||||
@@ -152,7 +155,9 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) &&
|
||||
((now - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.environment_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
@@ -198,7 +203,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
// If there's no valid packet, display "Environment"
|
||||
display->drawString(x, y, "Environment");
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement");
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,31 +228,31 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
}
|
||||
|
||||
// Continue with the remaining details
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Temp/Hum: " + last_temp + " / " +
|
||||
String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%");
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) {
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA");
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.voltage != 0) {
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
|
||||
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA");
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.iaq != 0) {
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.distance != 0)
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm");
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.weight != 0)
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg");
|
||||
}
|
||||
|
||||
@@ -280,102 +285,142 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
return false; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
bool valid = true;
|
||||
bool hasSensor = false;
|
||||
m.time = getTime();
|
||||
m.which_variant = meshtastic_Telemetry_environment_metrics_tag;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_environment_metrics_tag;
|
||||
|
||||
#ifdef T1000X_SENSOR_EN // add by WayenWeng
|
||||
valid = valid && t1000xSensor.getMetrics(&m);
|
||||
hasSensor = true;
|
||||
#else
|
||||
if (dfRobotLarkSensor.hasSensor()) {
|
||||
valid = valid && dfRobotLarkSensor.getMetrics(&m);
|
||||
valid = valid && dfRobotLarkSensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (sht31Sensor.hasSensor()) {
|
||||
valid = valid && sht31Sensor.getMetrics(&m);
|
||||
valid = valid && sht31Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (sht4xSensor.hasSensor()) {
|
||||
valid = valid && sht4xSensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (lps22hbSensor.hasSensor()) {
|
||||
valid = valid && lps22hbSensor.getMetrics(&m);
|
||||
valid = valid && lps22hbSensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (shtc3Sensor.hasSensor()) {
|
||||
valid = valid && shtc3Sensor.getMetrics(&m);
|
||||
valid = valid && shtc3Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (bmp085Sensor.hasSensor()) {
|
||||
valid = valid && bmp085Sensor.getMetrics(&m);
|
||||
valid = valid && bmp085Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (bmp280Sensor.hasSensor()) {
|
||||
valid = valid && bmp280Sensor.getMetrics(&m);
|
||||
valid = valid && bmp280Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (bme280Sensor.hasSensor()) {
|
||||
valid = valid && bme280Sensor.getMetrics(&m);
|
||||
valid = valid && bme280Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (bme680Sensor.hasSensor()) {
|
||||
valid = valid && bme680Sensor.getMetrics(&m);
|
||||
valid = valid && bme680Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (mcp9808Sensor.hasSensor()) {
|
||||
valid = valid && mcp9808Sensor.getMetrics(&m);
|
||||
valid = valid && mcp9808Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (ina219Sensor.hasSensor()) {
|
||||
valid = valid && ina219Sensor.getMetrics(&m);
|
||||
valid = valid && ina219Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (ina260Sensor.hasSensor()) {
|
||||
valid = valid && ina260Sensor.getMetrics(&m);
|
||||
valid = valid && ina260Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (ina3221Sensor.hasSensor()) {
|
||||
valid = valid && ina3221Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (veml7700Sensor.hasSensor()) {
|
||||
valid = valid && veml7700Sensor.getMetrics(&m);
|
||||
valid = valid && veml7700Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (tsl2591Sensor.hasSensor()) {
|
||||
valid = valid && tsl2591Sensor.getMetrics(&m);
|
||||
valid = valid && tsl2591Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (opt3001Sensor.hasSensor()) {
|
||||
valid = valid && opt3001Sensor.getMetrics(&m);
|
||||
valid = valid && opt3001Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (mlx90632Sensor.hasSensor()) {
|
||||
valid = valid && mlx90632Sensor.getMetrics(&m);
|
||||
valid = valid && mlx90632Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (rcwl9620Sensor.hasSensor()) {
|
||||
valid = valid && rcwl9620Sensor.getMetrics(&m);
|
||||
valid = valid && rcwl9620Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (nau7802Sensor.hasSensor()) {
|
||||
valid = valid && nau7802Sensor.getMetrics(&m);
|
||||
valid = valid && nau7802Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
if (aht10Sensor.hasSensor()) {
|
||||
if (!bmp280Sensor.hasSensor()) {
|
||||
valid = valid && aht10Sensor.getMetrics(&m);
|
||||
valid = valid && aht10Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
} else {
|
||||
// prefer bmp280 temp if both sensors are present, fetch only humidity
|
||||
meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
|
||||
LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n");
|
||||
aht10Sensor.getMetrics(&m_ahtx);
|
||||
m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
|
||||
m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
valid = valid && hasSensor;
|
||||
|
||||
if (valid) {
|
||||
#endif
|
||||
return valid && hasSensor;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
meshtastic_Telemetry *decoded = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding EnvironmentTelemetry module!\n");
|
||||
return NULL;
|
||||
}
|
||||
// Check for a request for environment metrics
|
||||
if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
if (getEnvironmentTelemetry(&m)) {
|
||||
LOG_INFO("Environment telemetry replying to request\n");
|
||||
return allocDataProtobuf(m);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
{
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
if (getEnvironmentTelemetry(&m)) {
|
||||
LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n",
|
||||
m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current,
|
||||
m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity,
|
||||
@@ -413,8 +458,9 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
setIntervalFromNow(5000);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return valid;
|
||||
return false;
|
||||
}
|
||||
|
||||
AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
||||
@@ -477,6 +523,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
if (ina3221Sensor.hasSensor()) {
|
||||
result = ina3221Sensor.handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
if (veml7700Sensor.hasSensor()) {
|
||||
result = veml7700Sensor.handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
@@ -515,4 +566,4 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -11,12 +11,17 @@
|
||||
|
||||
class EnvironmentTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<EnvironmentTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<EnvironmentTelemetryModule, const meshtastic::Status *>(this,
|
||||
&EnvironmentTelemetryModule::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
EnvironmentTelemetryModule()
|
||||
: concurrency::OSThread("EnvironmentTelemetryModule"),
|
||||
ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
|
||||
{
|
||||
lastMeasurementPacket = nullptr;
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(10 * 1000);
|
||||
}
|
||||
virtual bool wantUIFrame() override;
|
||||
@@ -32,6 +37,11 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
|
||||
virtual int32_t runOnce() override;
|
||||
/** Called to get current Environment telemetry data
|
||||
@return true if it contains valid data
|
||||
*/
|
||||
bool getEnvironmentTelemetry(meshtastic_Telemetry *m);
|
||||
virtual meshtastic_MeshPacket *allocReply() override;
|
||||
/**
|
||||
* Send our Telemetry into the mesh
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,8 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
{
|
||||
if (sleepOnNextExecution == true) {
|
||||
sleepOnNextExecution = false;
|
||||
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval);
|
||||
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval,
|
||||
default_telemetry_broadcast_interval_secs);
|
||||
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs);
|
||||
doDeepSleep(nightyNightMs, true);
|
||||
}
|
||||
@@ -70,7 +71,9 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) &&
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.power_update_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes))) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = now;
|
||||
@@ -108,7 +111,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
display->drawString(x, y, "Power Telemetry");
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement");
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,22 +123,22 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
auto &p = lastMeasurementPacket->decoded;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error");
|
||||
LOG_ERROR("Unable to decode last packet");
|
||||
return;
|
||||
}
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C";
|
||||
display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) {
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " +
|
||||
String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " +
|
||||
String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
display->drawString(x, y += fontHeight(FONT_SMALL),
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " +
|
||||
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
}
|
||||
@@ -163,29 +166,63 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m
|
||||
return false; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
bool valid = false;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_power_metrics_tag;
|
||||
|
||||
m->variant.power_metrics.ch1_voltage = 0;
|
||||
m->variant.power_metrics.ch1_current = 0;
|
||||
m->variant.power_metrics.ch2_voltage = 0;
|
||||
m->variant.power_metrics.ch2_current = 0;
|
||||
m->variant.power_metrics.ch3_voltage = 0;
|
||||
m->variant.power_metrics.ch3_current = 0;
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
if (ina219Sensor.hasSensor())
|
||||
valid = ina219Sensor.getMetrics(m);
|
||||
if (ina260Sensor.hasSensor())
|
||||
valid = ina260Sensor.getMetrics(m);
|
||||
if (ina3221Sensor.hasSensor())
|
||||
valid = ina3221Sensor.getMetrics(m);
|
||||
#endif
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *PowerTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
meshtastic_Telemetry *decoded = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding PowerTelemetry module!\n");
|
||||
return NULL;
|
||||
}
|
||||
// Check for a request for power metrics
|
||||
if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
if (getPowerTelemetry(&m)) {
|
||||
LOG_INFO("Power telemetry replying to request\n");
|
||||
return allocDataProtobuf(m);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
{
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
bool valid = false;
|
||||
m.time = getTime();
|
||||
m.which_variant = meshtastic_Telemetry_power_metrics_tag;
|
||||
|
||||
m.variant.power_metrics.ch1_voltage = 0;
|
||||
m.variant.power_metrics.ch1_current = 0;
|
||||
m.variant.power_metrics.ch2_voltage = 0;
|
||||
m.variant.power_metrics.ch2_current = 0;
|
||||
m.variant.power_metrics.ch3_voltage = 0;
|
||||
m.variant.power_metrics.ch3_current = 0;
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
if (ina219Sensor.hasSensor())
|
||||
valid = ina219Sensor.getMetrics(&m);
|
||||
if (ina260Sensor.hasSensor())
|
||||
valid = ina260Sensor.getMetrics(&m);
|
||||
if (ina3221Sensor.hasSensor())
|
||||
valid = ina3221Sensor.getMetrics(&m);
|
||||
#endif
|
||||
|
||||
if (valid) {
|
||||
if (getPowerTelemetry(&m)) {
|
||||
LOG_INFO("(Sending): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, "
|
||||
"ch3_voltage=%f, ch3_current=%f\n",
|
||||
m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage,
|
||||
@@ -218,8 +255,9 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
setIntervalFromNow(5000);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return valid;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -12,12 +12,16 @@
|
||||
|
||||
class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<PowerTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<PowerTelemetryModule, const meshtastic::Status *>(this, &PowerTelemetryModule::handleStatusUpdate);
|
||||
|
||||
public:
|
||||
PowerTelemetryModule()
|
||||
: concurrency::OSThread("PowerTelemetryModule"),
|
||||
ProtobufModule("PowerTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
|
||||
{
|
||||
lastMeasurementPacket = nullptr;
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
setIntervalFromNow(10 * 1000);
|
||||
}
|
||||
virtual bool wantUIFrame() override;
|
||||
@@ -33,6 +37,11 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul
|
||||
*/
|
||||
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
|
||||
virtual int32_t runOnce() override;
|
||||
/** Called to get current Power telemetry data
|
||||
@return true if it contains valid data
|
||||
*/
|
||||
bool getPowerTelemetry(meshtastic_Telemetry *m);
|
||||
virtual meshtastic_MeshPacket *allocReply() override;
|
||||
/**
|
||||
* Send our Telemetry into the mesh
|
||||
*/
|
||||
|
||||
@@ -16,8 +16,7 @@ int32_t INA3221Sensor::runOnce()
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
if (!status) {
|
||||
ina3221.setAddr(INA3221_ADDR42_SDA); // i2c address 0x42
|
||||
ina3221.begin();
|
||||
ina3221.begin(nodeTelemetrySensorsMap[sensorType].second);
|
||||
ina3221.setShuntRes(100, 100, 100); // 0.1 Ohm shunt resistors
|
||||
status = true;
|
||||
} else {
|
||||
@@ -28,22 +27,69 @@ int32_t INA3221Sensor::runOnce()
|
||||
|
||||
void INA3221Sensor::setup() {}
|
||||
|
||||
struct _INA3221Measurement INA3221Sensor::getMeasurement(ina3221_ch_t ch)
|
||||
{
|
||||
struct _INA3221Measurement measurement;
|
||||
|
||||
measurement.voltage = ina3221.getVoltage(ch);
|
||||
measurement.current = ina3221.getCurrent(ch);
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
struct _INA3221Measurements INA3221Sensor::getMeasurements()
|
||||
{
|
||||
struct _INA3221Measurements measurements;
|
||||
|
||||
// INA3221 has 3 channels starting from 0
|
||||
for (int i = 0; i < 3; i++) {
|
||||
measurements.measurements[i] = getMeasurement((ina3221_ch_t)i);
|
||||
}
|
||||
|
||||
return measurements;
|
||||
}
|
||||
|
||||
bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
measurement->variant.environment_metrics.voltage = ina3221.getVoltage(INA3221_CH1);
|
||||
measurement->variant.environment_metrics.current = ina3221.getCurrent(INA3221_CH1);
|
||||
measurement->variant.power_metrics.ch1_voltage = ina3221.getVoltage(INA3221_CH1);
|
||||
measurement->variant.power_metrics.ch1_current = ina3221.getCurrent(INA3221_CH1);
|
||||
measurement->variant.power_metrics.ch2_voltage = ina3221.getVoltage(INA3221_CH2);
|
||||
measurement->variant.power_metrics.ch2_current = ina3221.getCurrent(INA3221_CH2);
|
||||
measurement->variant.power_metrics.ch3_voltage = ina3221.getVoltage(INA3221_CH3);
|
||||
measurement->variant.power_metrics.ch3_current = ina3221.getCurrent(INA3221_CH3);
|
||||
switch (measurement->which_variant) {
|
||||
case meshtastic_Telemetry_environment_metrics_tag:
|
||||
return getEnvironmentMetrics(measurement);
|
||||
|
||||
case meshtastic_Telemetry_power_metrics_tag:
|
||||
return getPowerMetrics(measurement);
|
||||
}
|
||||
|
||||
// unsupported metric
|
||||
return false;
|
||||
}
|
||||
|
||||
bool INA3221Sensor::getEnvironmentMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
struct _INA3221Measurement m = getMeasurement(ENV_CH);
|
||||
|
||||
measurement->variant.environment_metrics.voltage = m.voltage;
|
||||
measurement->variant.environment_metrics.current = m.current;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool INA3221Sensor::getPowerMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
struct _INA3221Measurements m = getMeasurements();
|
||||
|
||||
measurement->variant.power_metrics.ch1_voltage = m.measurements[INA3221_CH1].voltage;
|
||||
measurement->variant.power_metrics.ch1_current = m.measurements[INA3221_CH1].current;
|
||||
measurement->variant.power_metrics.ch2_voltage = m.measurements[INA3221_CH2].voltage;
|
||||
measurement->variant.power_metrics.ch2_current = m.measurements[INA3221_CH2].current;
|
||||
measurement->variant.power_metrics.ch3_voltage = m.measurements[INA3221_CH3].voltage;
|
||||
measurement->variant.power_metrics.ch3_current = m.measurements[INA3221_CH3].current;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t INA3221Sensor::getBusVoltageMv()
|
||||
{
|
||||
return lround(ina3221.getVoltage(INA3221_CH1) * 1000);
|
||||
return lround(ina3221.getVoltage(BAT_CH) * 1000);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,21 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor
|
||||
private:
|
||||
INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA);
|
||||
|
||||
// channel to report voltage/current for environment metrics
|
||||
ina3221_ch_t ENV_CH = INA3221_CH1;
|
||||
|
||||
// channel to report battery voltage for device_battery_ina_address
|
||||
ina3221_ch_t BAT_CH = INA3221_CH1;
|
||||
|
||||
// get a single measurement for a channel
|
||||
struct _INA3221Measurement getMeasurement(ina3221_ch_t ch);
|
||||
|
||||
// get all measurements for all channels
|
||||
struct _INA3221Measurements getMeasurements();
|
||||
|
||||
bool getEnvironmentMetrics(meshtastic_Telemetry *measurement);
|
||||
bool getPowerMetrics(meshtastic_Telemetry *measurement);
|
||||
|
||||
protected:
|
||||
void setup() override;
|
||||
|
||||
@@ -22,4 +37,14 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor
|
||||
virtual uint16_t getBusVoltageMv() override;
|
||||
};
|
||||
|
||||
struct _INA3221Measurement {
|
||||
float voltage;
|
||||
float current;
|
||||
};
|
||||
|
||||
struct _INA3221Measurements {
|
||||
// INA3221 has 3 channels
|
||||
struct _INA3221Measurement measurements[3];
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -2,6 +2,11 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#endif
|
||||
|
||||
WaypointModule *waypointModule;
|
||||
|
||||
@@ -11,14 +16,171 @@ ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
auto &p = mp.decoded;
|
||||
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s\n", mp.from, mp.id, p.payload.size, p.payload.bytes);
|
||||
#endif
|
||||
|
||||
// We only store/display messages destined for us.
|
||||
// Keep a copy of the most recent text message.
|
||||
devicestate.rx_waypoint = mp;
|
||||
devicestate.has_rx_waypoint = true;
|
||||
|
||||
powerFSM.trigger(EVENT_RECEIVED_MSG);
|
||||
notifyObservers(&mp);
|
||||
|
||||
#if HAS_SCREEN
|
||||
|
||||
UIFrameEvent e;
|
||||
|
||||
// New or updated waypoint: focus on this frame next time Screen::setFrames runs
|
||||
if (shouldDraw()) {
|
||||
requestFocus();
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
|
||||
}
|
||||
|
||||
// Deleting an old waypoint: remove the frame quietly, don't change frame position if possible
|
||||
else
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND;
|
||||
|
||||
notifyObservers(&e);
|
||||
|
||||
#endif
|
||||
|
||||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
bool WaypointModule::shouldDraw()
|
||||
{
|
||||
#if !MESHTASTIC_EXCLUDE_WAYPOINT
|
||||
// If no waypoint to show
|
||||
if (!devicestate.has_rx_waypoint)
|
||||
return false;
|
||||
|
||||
// Decode the message, to find the expiration time (is waypoint still valid)
|
||||
// This handles "deletion" as well as expiration
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (pb_decode_from_bytes(devicestate.rx_waypoint.decoded.payload.bytes, devicestate.rx_waypoint.decoded.payload.size,
|
||||
&meshtastic_Waypoint_msg, &wp)) {
|
||||
// Valid waypoint
|
||||
if (wp.expire > getTime())
|
||||
return devicestate.has_rx_waypoint = true;
|
||||
|
||||
// Expired, or deleted
|
||||
else
|
||||
return devicestate.has_rx_waypoint = false;
|
||||
}
|
||||
|
||||
// If decoding failed
|
||||
LOG_ERROR("Failed to decode waypoint\n");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return false;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Draw the last waypoint we received
|
||||
void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Prepare to draw
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
// Handle inverted display
|
||||
// Unsure of expected behavior: for now, copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED)
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
|
||||
// Decode the waypoint
|
||||
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
||||
meshtastic_Waypoint wp;
|
||||
memset(&wp, 0, sizeof(wp));
|
||||
if (!pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Waypoint_msg, &wp)) {
|
||||
// This *should* be caught by shouldDrawWaypoint, but we'll short-circuit here just in case
|
||||
display->drawStringMaxWidth(0 + x, 0 + y, x + display->getWidth(), "Couldn't decode waypoint");
|
||||
devicestate.has_rx_waypoint = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get timestamp info. Will pass as a field to drawColumns
|
||||
static char lastStr[20];
|
||||
screen->getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
|
||||
|
||||
// Will contain distance information, passed as a field to drawColumns
|
||||
static char distStr[20];
|
||||
|
||||
// Get our node, to use our own position
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// Text fields to draw (left of compass)
|
||||
// Last element must be NULL. This signals the end of the char*[] to drawColumns
|
||||
const char *fields[] = {"Waypoint", lastStr, wp.name, distStr, NULL};
|
||||
|
||||
// Dimensions / co-ordinates for the compass/circle
|
||||
int16_t compassX = 0, compassY = 0;
|
||||
uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
|
||||
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + display->getHeight() / 2;
|
||||
} else {
|
||||
compassX = x + display->getWidth() - compassDiam / 2 - 5;
|
||||
compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
|
||||
}
|
||||
|
||||
// If our node has a position:
|
||||
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
|
||||
const meshtastic_PositionLite &op = ourNode->position;
|
||||
float myHeading;
|
||||
if (screen->hasHeading())
|
||||
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
|
||||
else
|
||||
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
screen->drawCompassNorth(display, compassX, compassY, myHeading);
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
if (d < (2 * MILES_TO_FEET))
|
||||
snprintf(distStr, sizeof(distStr), "%.0f ft", d * METERS_TO_FEET);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f mi", d * METERS_TO_FEET / MILES_TO_FEET);
|
||||
} else {
|
||||
if (d < 2000)
|
||||
snprintf(distStr, sizeof(distStr), "%.0f m", d);
|
||||
else
|
||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||
}
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
|
||||
if (!config.display.compass_north_top)
|
||||
bearingToOther -= myHeading;
|
||||
screen->drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
}
|
||||
|
||||
// If our node doesn't have position
|
||||
else {
|
||||
// ? in the compass
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
|
||||
// ? in the distance field
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
strncpy(distStr, "? mi", sizeof(distStr));
|
||||
else
|
||||
strncpy(distStr, "? km", sizeof(distStr));
|
||||
}
|
||||
|
||||
// Undo color-inversion, if set prior to drawing header
|
||||
// Unsure of expected behavior? For now: copy drawNodeInfo
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED) {
|
||||
display->setColor(BLACK);
|
||||
}
|
||||
|
||||
// Draw compass circle
|
||||
display->drawCircle(compassX, compassY, compassDiam / 2);
|
||||
|
||||
// Must be after distStr is populated
|
||||
screen->drawColumns(display, x, y, fields);
|
||||
}
|
||||
#endif
|
||||
@@ -5,21 +5,29 @@
|
||||
/**
|
||||
* Waypoint message handling for meshtastic
|
||||
*/
|
||||
class WaypointModule : public SinglePortModule, public Observable<const meshtastic_MeshPacket *>
|
||||
class WaypointModule : public SinglePortModule, public Observable<const UIFrameEvent *>
|
||||
{
|
||||
public:
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
WaypointModule() : SinglePortModule("waypoint", meshtastic_PortNum_WAYPOINT_APP) {}
|
||||
|
||||
#if HAS_SCREEN
|
||||
bool shouldDraw();
|
||||
#endif
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
|
||||
it
|
||||
*/
|
||||
|
||||
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
|
||||
#if HAS_SCREEN
|
||||
virtual bool wantUIFrame() override { return this->shouldDraw(); }
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
|
||||
#endif
|
||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
};
|
||||
|
||||
extern WaypointModule *waypointModule;
|
||||
extern WaypointModule *waypointModule;
|
||||
@@ -190,13 +190,13 @@ int32_t AudioModule::runOnce()
|
||||
|
||||
firstTime = false;
|
||||
} else {
|
||||
UIFrameEvent e = {false, true};
|
||||
UIFrameEvent e;
|
||||
// Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive.
|
||||
if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) {
|
||||
if (radio_state == RadioState::rx) {
|
||||
LOG_INFO("PTT pressed, switching to TX\n");
|
||||
radio_state = RadioState::tx;
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
} else {
|
||||
@@ -209,7 +209,7 @@ int32_t AudioModule::runOnce()
|
||||
}
|
||||
tx_encode_frame_index = sizeof(tx_header);
|
||||
radio_state = RadioState::rx;
|
||||
e.frameChanged = true;
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +66,6 @@ bool PaxcounterModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m
|
||||
|
||||
meshtastic_MeshPacket *PaxcounterModule::allocReply()
|
||||
{
|
||||
if (ignoreRequest) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
meshtastic_Paxcount pl = meshtastic_Paxcount_init_default;
|
||||
pl.wifi = count_from_libpax.wifi_count;
|
||||
pl.ble = count_from_libpax.ble_count;
|
||||
@@ -84,7 +80,7 @@ int32_t PaxcounterModule::runOnce()
|
||||
firstTime = false;
|
||||
LOG_DEBUG("Paxcounter starting up with interval of %d seconds\n",
|
||||
Default::getConfiguredOrDefault(moduleConfig.paxcounter.paxcounter_update_interval,
|
||||
default_broadcast_interval_secs));
|
||||
default_telemetry_broadcast_interval_secs));
|
||||
struct libpax_config_t configuration;
|
||||
libpax_default_config(&configuration);
|
||||
|
||||
@@ -104,8 +100,8 @@ int32_t PaxcounterModule::runOnce()
|
||||
} else {
|
||||
sendInfo(NODENUM_BROADCAST);
|
||||
}
|
||||
return Default::getConfiguredOrDefaultMs(moduleConfig.paxcounter.paxcounter_update_interval,
|
||||
default_broadcast_interval_secs);
|
||||
return Default::getConfiguredOrDefaultMsScaled(moduleConfig.paxcounter.paxcounter_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes);
|
||||
} else {
|
||||
return disable();
|
||||
}
|
||||
@@ -131,4 +127,4 @@ void PaxcounterModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
}
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -35,13 +35,10 @@ int32_t StoreForwardModule::runOnce()
|
||||
if (moduleConfig.store_forward.enabled && is_server) {
|
||||
// Send out the message queue.
|
||||
if (this->busy) {
|
||||
// Only send packets if the channel is less than 25% utilized.
|
||||
if (airTime->isTxAllowedChannelUtil(true)) {
|
||||
storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index);
|
||||
if (this->packetHistoryTXQueue_index < packetHistoryTXQueue_size - 1) {
|
||||
this->packetHistoryTXQueue_index++;
|
||||
} else {
|
||||
this->packetHistoryTXQueue_index = 0;
|
||||
// Only send packets if the channel is less than 25% utilized and until historyReturnMax
|
||||
if (airTime->isTxAllowedChannelUtil(true) && this->requestCount < this->historyReturnMax) {
|
||||
if (!storeForwardModule->sendPayload(this->busyTo, this->last_time)) {
|
||||
this->requestCount = 0;
|
||||
this->busy = false;
|
||||
}
|
||||
}
|
||||
@@ -75,9 +72,6 @@ void StoreForwardModule::populatePSRAM()
|
||||
LOG_DEBUG("*** Before PSRAM initialization: heap %d/%d PSRAM %d/%d\n", memGet.getFreeHeap(), memGet.getHeapSize(),
|
||||
memGet.getFreePsram(), memGet.getPsramSize());
|
||||
|
||||
this->packetHistoryTXQueue =
|
||||
static_cast<PacketHistoryStruct *>(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct)));
|
||||
|
||||
/* Use a maximum of 2/3 the available PSRAM unless otherwise specified.
|
||||
Note: This needs to be done after every thing that would use PSRAM
|
||||
*/
|
||||
@@ -95,13 +89,15 @@ void StoreForwardModule::populatePSRAM()
|
||||
/**
|
||||
* Sends messages from the message history to the specified recipient.
|
||||
*
|
||||
* @param msAgo The number of milliseconds ago from which to start sending messages.
|
||||
* @param sAgo The number of seconds ago from which to start sending messages.
|
||||
* @param to The recipient ID to send the messages to.
|
||||
*/
|
||||
void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to)
|
||||
void StoreForwardModule::historySend(uint32_t secAgo, uint32_t to)
|
||||
{
|
||||
uint32_t lastIndex = lastRequest.find(to) != lastRequest.end() ? lastRequest[to] : 0;
|
||||
uint32_t queueSize = storeForwardModule->historyQueueCreate(msAgo, to, &lastIndex);
|
||||
this->last_time = getTime() < secAgo ? 0 : getTime() - secAgo;
|
||||
uint32_t queueSize = getNumAvailablePackets(to, last_time);
|
||||
if (queueSize > this->historyReturnMax)
|
||||
queueSize = this->historyReturnMax;
|
||||
|
||||
if (queueSize) {
|
||||
LOG_INFO("*** S&F - Sending %u message(s)\n", queueSize);
|
||||
@@ -114,62 +110,66 @@ void StoreForwardModule::historySend(uint32_t msAgo, uint32_t to)
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_HISTORY;
|
||||
sf.which_variant = meshtastic_StoreAndForward_history_tag;
|
||||
sf.variant.history.history_messages = queueSize;
|
||||
sf.variant.history.window = msAgo;
|
||||
sf.variant.history.last_request = lastIndex;
|
||||
lastRequest[to] = lastIndex;
|
||||
sf.variant.history.window = secAgo * 1000;
|
||||
sf.variant.history.last_request = lastRequest[to];
|
||||
storeForwardModule->sendMessage(to, sf);
|
||||
setIntervalFromNow(this->packetTimeMax); // Delay start of sending payloads
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new history queue with messages that were received within the specified time frame.
|
||||
* Returns the number of available packets in the message history for a specified destination node.
|
||||
*
|
||||
* @param msAgo The number of milliseconds ago to start the history queue.
|
||||
* @param to The NodeNum of the recipient.
|
||||
* @param last_request_index The index in the packet history of the last request from this node.
|
||||
* @return The ID of the newly created history queue.
|
||||
* @param dest The destination node number.
|
||||
* @param last_time The relative time to start counting messages from.
|
||||
* @return The number of available packets in the message history.
|
||||
*/
|
||||
uint32_t StoreForwardModule::historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index)
|
||||
uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_time)
|
||||
{
|
||||
|
||||
this->packetHistoryTXQueue_size = 0;
|
||||
// If our history was cleared, ignore the last request index
|
||||
uint32_t last_index = *last_request_index > this->packetHistoryCurrent ? 0 : *last_request_index;
|
||||
|
||||
for (uint32_t i = last_index; i < this->packetHistoryCurrent; i++) {
|
||||
/*
|
||||
LOG_DEBUG("SF historyQueueCreate\n");
|
||||
LOG_DEBUG("SF historyQueueCreate - time %d\n", this->packetHistory[i].time);
|
||||
LOG_DEBUG("SF historyQueueCreate - millis %d\n", millis());
|
||||
LOG_DEBUG("SF historyQueueCreate - math %d\n", (millis() - msAgo));
|
||||
*/
|
||||
if (this->packetHistoryTXQueue_size < this->historyReturnMax) {
|
||||
if (this->packetHistory[i].time && (this->packetHistory[i].time < (millis() - msAgo))) {
|
||||
/* Copy the messages that were received by the router in the last msAgo
|
||||
to the packetHistoryTXQueue structure.
|
||||
Client not interested in packets from itself and only in broadcast packets or packets towards it. */
|
||||
if (this->packetHistory[i].from != to &&
|
||||
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == to)) {
|
||||
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].time = this->packetHistory[i].time;
|
||||
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].to = this->packetHistory[i].to;
|
||||
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].from = this->packetHistory[i].from;
|
||||
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].channel = this->packetHistory[i].channel;
|
||||
this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload_size =
|
||||
this->packetHistory[i].payload_size;
|
||||
memcpy(this->packetHistoryTXQueue[this->packetHistoryTXQueue_size].payload, this->packetHistory[i].payload,
|
||||
meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
this->packetHistoryTXQueue_size++;
|
||||
*last_request_index = i + 1; // Set to one higher such that we don't send the same message again
|
||||
|
||||
LOG_DEBUG("*** PacketHistoryStruct time=%d, msg=%s\n", this->packetHistory[i].time,
|
||||
this->packetHistory[i].payload);
|
||||
}
|
||||
uint32_t count = 0;
|
||||
if (lastRequest.find(dest) == lastRequest.end()) {
|
||||
lastRequest[dest] = 0;
|
||||
}
|
||||
for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) {
|
||||
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
|
||||
// Client is only interested in packets not from itself and only in broadcast packets or packets towards it.
|
||||
if (this->packetHistory[i].from != dest &&
|
||||
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("*** S&F - Maximum history return reached.\n");
|
||||
return this->packetHistoryTXQueue_size;
|
||||
}
|
||||
}
|
||||
return this->packetHistoryTXQueue_size;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a mesh packet for sending to the phone.
|
||||
*
|
||||
* @return A pointer to the allocated mesh packet or nullptr if none is available.
|
||||
*/
|
||||
meshtastic_MeshPacket *StoreForwardModule::getForPhone()
|
||||
{
|
||||
if (moduleConfig.store_forward.enabled && is_server) {
|
||||
NodeNum to = nodeDB->getNodeNum();
|
||||
if (!this->busy) {
|
||||
// Get number of packets we're going to send in this loop
|
||||
uint32_t histSize = getNumAvailablePackets(to, 0); // No time limit
|
||||
if (histSize) {
|
||||
this->busy = true;
|
||||
this->busyTo = to;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// We're busy with sending to us until no payload is available anymore
|
||||
if (this->busy && this->busyTo == to) {
|
||||
meshtastic_MeshPacket *p = preparePayload(to, 0, true); // No time limit
|
||||
if (!p) // No more messages to send
|
||||
this->busy = false;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,66 +181,97 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
const auto &p = mp.decoded;
|
||||
|
||||
if (this->packetHistoryCurrent == this->records) {
|
||||
if (this->packetHistoryTotalCount == this->records) {
|
||||
LOG_WARN("*** S&F - PSRAM Full. Starting overwrite now.\n");
|
||||
this->packetHistoryCurrent = 0;
|
||||
this->packetHistoryMax = 0;
|
||||
this->packetHistoryTotalCount = 0;
|
||||
for (auto &i : lastRequest) {
|
||||
i.second = 0; // Clear the last request index for each client device
|
||||
}
|
||||
}
|
||||
|
||||
this->packetHistory[this->packetHistoryCurrent].time = millis();
|
||||
this->packetHistory[this->packetHistoryCurrent].to = mp.to;
|
||||
this->packetHistory[this->packetHistoryCurrent].channel = mp.channel;
|
||||
this->packetHistory[this->packetHistoryCurrent].from = mp.from;
|
||||
this->packetHistory[this->packetHistoryCurrent].payload_size = p.payload.size;
|
||||
memcpy(this->packetHistory[this->packetHistoryCurrent].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
this->packetHistory[this->packetHistoryTotalCount].time = getTime();
|
||||
this->packetHistory[this->packetHistoryTotalCount].to = mp.to;
|
||||
this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel;
|
||||
this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp);
|
||||
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
|
||||
this->packetHistoryCurrent++;
|
||||
this->packetHistoryMax++;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *StoreForwardModule::allocReply()
|
||||
{
|
||||
auto reply = allocDataPacket(); // Allocate a packet for sending
|
||||
return reply;
|
||||
this->packetHistoryTotalCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a payload to a specified destination node using the store and forward mechanism.
|
||||
*
|
||||
* @param dest The destination node number.
|
||||
* @param packetHistory_index The index of the packet in the packet history buffer.
|
||||
* @param last_time The relative time to start sending messages from.
|
||||
* @return True if a packet was successfully sent, false otherwise.
|
||||
*/
|
||||
void StoreForwardModule::sendPayload(NodeNum dest, uint32_t packetHistory_index)
|
||||
bool StoreForwardModule::sendPayload(NodeNum dest, uint32_t last_time)
|
||||
{
|
||||
LOG_INFO("*** Sending S&F Payload\n");
|
||||
meshtastic_MeshPacket *p = allocReply();
|
||||
|
||||
p->to = dest;
|
||||
p->from = this->packetHistoryTXQueue[packetHistory_index].from;
|
||||
p->channel = this->packetHistoryTXQueue[packetHistory_index].channel;
|
||||
|
||||
// Let's assume that if the router received the S&F request that the client is in range.
|
||||
// TODO: Make this configurable.
|
||||
p->want_ack = false;
|
||||
|
||||
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
|
||||
sf.which_variant = meshtastic_StoreAndForward_text_tag;
|
||||
sf.variant.text.size = this->packetHistoryTXQueue[packetHistory_index].payload_size;
|
||||
memcpy(sf.variant.text.bytes, this->packetHistoryTXQueue[packetHistory_index].payload,
|
||||
this->packetHistoryTXQueue[packetHistory_index].payload_size);
|
||||
if (this->packetHistoryTXQueue[packetHistory_index].to == NODENUM_BROADCAST) {
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST;
|
||||
} else {
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT;
|
||||
meshtastic_MeshPacket *p = preparePayload(dest, last_time);
|
||||
if (p) {
|
||||
LOG_INFO("*** Sending S&F Payload\n");
|
||||
service.sendToMesh(p);
|
||||
this->requestCount++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p->decoded.payload.size =
|
||||
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_StoreAndForward_msg, &sf);
|
||||
/**
|
||||
* Prepares a payload to be sent to a specified destination node from the S&F packet history.
|
||||
*
|
||||
* @param dest The destination node number.
|
||||
* @param last_time The relative time to start sending messages from.
|
||||
* @return A pointer to the prepared mesh packet or nullptr if none is available.
|
||||
*/
|
||||
meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t last_time, bool local)
|
||||
{
|
||||
for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) {
|
||||
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
|
||||
/* Copy the messages that were received by the server in the last msAgo
|
||||
to the packetHistoryTXQueue structure.
|
||||
Client not interested in packets from itself and only in broadcast packets or packets towards it. */
|
||||
if (this->packetHistory[i].from != dest &&
|
||||
(this->packetHistory[i].to == NODENUM_BROADCAST || this->packetHistory[i].to == dest)) {
|
||||
|
||||
service.sendToMesh(p);
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
|
||||
p->to = local ? this->packetHistory[i].to : dest; // PhoneAPI can handle original `to`
|
||||
p->from = this->packetHistory[i].from;
|
||||
p->channel = this->packetHistory[i].channel;
|
||||
p->rx_time = this->packetHistory[i].time;
|
||||
|
||||
// Let's assume that if the server received the S&F request that the client is in range.
|
||||
// TODO: Make this configurable.
|
||||
p->want_ack = false;
|
||||
|
||||
if (local) { // PhoneAPI gets normal TEXT_MESSAGE_APP
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
memcpy(p->decoded.payload.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
|
||||
p->decoded.payload.size = this->packetHistory[i].payload_size;
|
||||
} else {
|
||||
meshtastic_StoreAndForward sf = meshtastic_StoreAndForward_init_zero;
|
||||
sf.which_variant = meshtastic_StoreAndForward_text_tag;
|
||||
sf.variant.text.size = this->packetHistory[i].payload_size;
|
||||
memcpy(sf.variant.text.bytes, this->packetHistory[i].payload, this->packetHistory[i].payload_size);
|
||||
if (this->packetHistory[i].to == NODENUM_BROADCAST) {
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_BROADCAST;
|
||||
} else {
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_TEXT_DIRECT;
|
||||
}
|
||||
|
||||
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
|
||||
&meshtastic_StoreAndForward_msg, &sf);
|
||||
}
|
||||
|
||||
lastRequest[dest] = i + 1; // Update the last request index for the client device
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,11 +288,7 @@ void StoreForwardModule::sendMessage(NodeNum dest, const meshtastic_StoreAndForw
|
||||
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
|
||||
// FIXME - Determine if the delayed packet is broadcast or delayed. For now, assume
|
||||
// everything is broadcast.
|
||||
p->delayed = meshtastic_MeshPacket_Delayed_DELAYED_BROADCAST;
|
||||
|
||||
// Let's assume that if the router received the S&F request that the client is in range.
|
||||
// Let's assume that if the server received the S&F request that the client is in range.
|
||||
// TODO: Make this configurable.
|
||||
p->want_ack = false;
|
||||
p->decoded.want_response = false;
|
||||
@@ -283,6 +310,35 @@ void StoreForwardModule::sendMessage(NodeNum dest, meshtastic_StoreAndForward_Re
|
||||
storeForwardModule->sendMessage(dest, sf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a text message with an error (busy or channel not available) to the specified destination node.
|
||||
*
|
||||
* @param dest The destination node number.
|
||||
* @param want_response True if the original message requested a response, false otherwise.
|
||||
*/
|
||||
void StoreForwardModule::sendErrorTextMessage(NodeNum dest, bool want_response)
|
||||
{
|
||||
meshtastic_MeshPacket *pr = allocDataPacket();
|
||||
pr->to = dest;
|
||||
pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
pr->want_ack = false;
|
||||
pr->decoded.want_response = false;
|
||||
pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
const char *str;
|
||||
if (this->busy) {
|
||||
str = "** S&F - Busy. Try again shortly.";
|
||||
} else {
|
||||
str = "** S&F - Not available on this channel.";
|
||||
}
|
||||
LOG_WARN("%s\n", str);
|
||||
memcpy(pr->decoded.payload.bytes, str, strlen(str));
|
||||
pr->decoded.payload.size = strlen(str);
|
||||
if (want_response) {
|
||||
ignoreRequest = true; // This text message counts as response.
|
||||
}
|
||||
service.sendToMesh(pr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends statistics about the store and forward module to the specified node.
|
||||
*
|
||||
@@ -294,8 +350,8 @@ void StoreForwardModule::statsSend(uint32_t to)
|
||||
|
||||
sf.rr = meshtastic_StoreAndForward_RequestResponse_ROUTER_STATS;
|
||||
sf.which_variant = meshtastic_StoreAndForward_stats_tag;
|
||||
sf.variant.stats.messages_total = this->packetHistoryMax;
|
||||
sf.variant.stats.messages_saved = this->packetHistoryCurrent;
|
||||
sf.variant.stats.messages_total = this->records;
|
||||
sf.variant.stats.messages_saved = this->packetHistoryTotalCount;
|
||||
sf.variant.stats.messages_max = this->records;
|
||||
sf.variant.stats.up_time = millis() / 1000;
|
||||
sf.variant.stats.requests = this->requests;
|
||||
@@ -319,51 +375,37 @@ ProcessMessage StoreForwardModule::handleReceived(const meshtastic_MeshPacket &m
|
||||
#ifdef ARCH_ESP32
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
|
||||
// The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT
|
||||
if ((getFrom(&mp) != nodeDB->getNodeNum()) || (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) {
|
||||
if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) {
|
||||
auto &p = mp.decoded;
|
||||
if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') &&
|
||||
(p.payload.bytes[2] == 0x00)) {
|
||||
LOG_DEBUG("*** Legacy Request to send\n");
|
||||
|
||||
if ((mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) && is_server) {
|
||||
auto &p = mp.decoded;
|
||||
if (mp.to == nodeDB->getNodeNum() && (p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') &&
|
||||
(p.payload.bytes[2] == 0x00)) {
|
||||
LOG_DEBUG("*** Legacy Request to send\n");
|
||||
|
||||
// Send the last 60 minutes of messages.
|
||||
if (this->busy) {
|
||||
storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY);
|
||||
LOG_INFO("*** S&F - Busy. Try again shortly.\n");
|
||||
meshtastic_MeshPacket *pr = allocReply();
|
||||
pr->to = getFrom(&mp);
|
||||
pr->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
pr->want_ack = false;
|
||||
pr->decoded.want_response = false;
|
||||
pr->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
memcpy(pr->decoded.payload.bytes, "** S&F - Busy. Try again shortly.", 34);
|
||||
pr->decoded.payload.size = 34;
|
||||
service.sendToMesh(pr);
|
||||
} else {
|
||||
storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp));
|
||||
}
|
||||
// Send the last 60 minutes of messages.
|
||||
if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) {
|
||||
sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response);
|
||||
} else {
|
||||
storeForwardModule->historyAdd(mp);
|
||||
LOG_INFO("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryCurrent);
|
||||
storeForwardModule->historySend(historyReturnWindow * 60, getFrom(&mp));
|
||||
}
|
||||
} else if (mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) {
|
||||
auto &p = mp.decoded;
|
||||
meshtastic_StoreAndForward scratch;
|
||||
meshtastic_StoreAndForward *decoded = NULL;
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_StoreAndForward_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding protobuf module!\n");
|
||||
// if we can't decode it, nobody can process it!
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE;
|
||||
} else {
|
||||
storeForwardModule->historyAdd(mp);
|
||||
LOG_INFO("*** S&F stored. Message history contains %u records now.\n", this->packetHistoryTotalCount);
|
||||
}
|
||||
} else if (getFrom(&mp) != nodeDB->getNodeNum() && mp.decoded.portnum == meshtastic_PortNum_STORE_FORWARD_APP) {
|
||||
auto &p = mp.decoded;
|
||||
meshtastic_StoreAndForward scratch;
|
||||
meshtastic_StoreAndForward *decoded = NULL;
|
||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_StoreAndForward_msg, &scratch)) {
|
||||
decoded = &scratch;
|
||||
} else {
|
||||
LOG_ERROR("Error decoding protobuf module!\n");
|
||||
// if we can't decode it, nobody can process it!
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
} // all others are irrelevant
|
||||
}
|
||||
return handleReceivedProtobuf(mp, decoded) ? ProcessMessage::STOP : ProcessMessage::CONTINUE;
|
||||
}
|
||||
} // all others are irrelevant
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -394,7 +436,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
// stop sending stuff, the client wants to abort or has another error
|
||||
if ((this->busy) && (this->busyTo == getFrom(&mp))) {
|
||||
LOG_ERROR("*** Client in ERROR or ABORT requested\n");
|
||||
this->packetHistoryTXQueue_index = 0;
|
||||
this->requestCount = 0;
|
||||
this->busy = false;
|
||||
}
|
||||
}
|
||||
@@ -405,15 +447,14 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
requests_history++;
|
||||
LOG_INFO("*** Client Request to send HISTORY\n");
|
||||
// Send the last 60 minutes of messages.
|
||||
if (this->busy) {
|
||||
storeForwardModule->sendMessage(getFrom(&mp), meshtastic_StoreAndForward_RequestResponse_ROUTER_BUSY);
|
||||
LOG_INFO("*** S&F - Busy. Try again shortly.\n");
|
||||
if (this->busy || channels.isDefaultChannel(channels.getByIndex(mp.channel))) {
|
||||
sendErrorTextMessage(getFrom(&mp), mp.decoded.want_response);
|
||||
} else {
|
||||
if ((p->which_variant == meshtastic_StoreAndForward_history_tag) && (p->variant.history.window > 0)) {
|
||||
// window is in minutes
|
||||
storeForwardModule->historySend(p->variant.history.window * 60000, getFrom(&mp));
|
||||
storeForwardModule->historySend(p->variant.history.window * 60, getFrom(&mp));
|
||||
} else {
|
||||
storeForwardModule->historySend(historyReturnWindow * 60000, getFrom(&mp)); // defaults to 4 hours
|
||||
storeForwardModule->historySend(historyReturnWindow * 60, getFrom(&mp)); // defaults to 4 hours
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,7 +492,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
if (is_client) {
|
||||
LOG_DEBUG("*** StoreAndForward_RequestResponse_ROUTER_BUSY\n");
|
||||
// retry in messages_saved * packetTimeMax ms
|
||||
retry_delay = millis() + packetHistoryCurrent * packetTimeMax *
|
||||
retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax *
|
||||
(meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1);
|
||||
}
|
||||
break;
|
||||
@@ -482,8 +523,6 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
LOG_DEBUG("*** Router Response STATS\n");
|
||||
// These fields only have informational purpose on a client. Fill them to consume later.
|
||||
if (p->which_variant == meshtastic_StoreAndForward_stats_tag) {
|
||||
this->packetHistoryMax = p->variant.stats.messages_total;
|
||||
this->packetHistoryCurrent = p->variant.stats.messages_saved;
|
||||
this->records = p->variant.stats.messages_max;
|
||||
this->requests = p->variant.stats.requests;
|
||||
this->requests_history = p->variant.stats.requests_history;
|
||||
@@ -508,7 +547,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
default:
|
||||
break; // no need to do anything
|
||||
}
|
||||
return true; // There's no need for others to look at this message.
|
||||
return false; // RoutingModule sends it to the phone
|
||||
}
|
||||
|
||||
StoreForwardModule::StoreForwardModule()
|
||||
@@ -532,9 +571,8 @@ StoreForwardModule::StoreForwardModule()
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
|
||||
// Router
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ||
|
||||
(config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT)) {
|
||||
LOG_INFO("*** Initializing Store & Forward Module in Router mode\n");
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || moduleConfig.store_forward.is_server)) {
|
||||
LOG_INFO("*** Initializing Store & Forward Module in Server mode\n");
|
||||
if (memGet.getPsramSize() > 0) {
|
||||
if (memGet.getFreePsram() >= 1024 * 1024) {
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
|
||||
char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
|
||||
|
||||
PacketHistoryStruct *packetHistory = 0;
|
||||
uint32_t packetHistoryCurrent = 0;
|
||||
uint32_t packetHistoryMax = 0;
|
||||
|
||||
PacketHistoryStruct *packetHistoryTXQueue = 0;
|
||||
uint32_t packetHistoryTXQueue_size = 0;
|
||||
uint32_t packetHistoryTXQueue_index = 0;
|
||||
uint32_t packetHistoryTotalCount = 0;
|
||||
uint32_t last_time = 0;
|
||||
uint32_t requestCount = 0;
|
||||
|
||||
uint32_t packetTimeMax = 5000; // Interval between sending history packets as a server.
|
||||
|
||||
@@ -52,18 +49,21 @@ class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<
|
||||
*/
|
||||
void historyAdd(const meshtastic_MeshPacket &mp);
|
||||
void statsSend(uint32_t to);
|
||||
void historySend(uint32_t msAgo, uint32_t to);
|
||||
|
||||
uint32_t historyQueueCreate(uint32_t msAgo, uint32_t to, uint32_t *last_request_index);
|
||||
void historySend(uint32_t secAgo, uint32_t to);
|
||||
uint32_t getNumAvailablePackets(NodeNum dest, uint32_t last_time);
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0);
|
||||
bool sendPayload(NodeNum dest = NODENUM_BROADCAST, uint32_t packetHistory_index = 0);
|
||||
meshtastic_MeshPacket *preparePayload(NodeNum dest, uint32_t packetHistory_index, bool local = false);
|
||||
void sendMessage(NodeNum dest, const meshtastic_StoreAndForward &payload);
|
||||
void sendMessage(NodeNum dest, meshtastic_StoreAndForward_RequestResponse rr);
|
||||
void sendErrorTextMessage(NodeNum dest, bool want_response);
|
||||
meshtastic_MeshPacket *getForPhone();
|
||||
// Returns true if we are configured as server AND we could allocate PSRAM.
|
||||
bool isServer() { return is_server; }
|
||||
|
||||
virtual meshtastic_MeshPacket *allocReply() override;
|
||||
/*
|
||||
-Override the wantPacket method.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user