mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-30 06:31:01 +00:00
Merge branch 'master' into esp32-c6
This commit is contained in:
@@ -34,8 +34,8 @@
|
||||
#include "modules/PositionModule.h"
|
||||
#endif
|
||||
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
#include "AccelerometerThread.h"
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "motion/AccelerometerThread.h"
|
||||
#endif
|
||||
|
||||
AdminModule *adminModule;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#endif
|
||||
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
// Remove Canned message screen if no action is taken for some milliseconds
|
||||
#define INACTIVATE_AFTER_MS 20000
|
||||
@@ -275,6 +276,13 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
||||
showTemporaryMessage("Node Info \nUpdate Sent");
|
||||
}
|
||||
break;
|
||||
case INPUT_BROKER_MSG_DISMISS_FRAME: // fn+del: dismiss screen frames like text or waypoint
|
||||
// Avoid opening the canned message screen frame
|
||||
// We're only handling the keypress here by convention, this has nothing to do with canned messages
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||
// Attempt to close whatever frame is currently shown on display
|
||||
screen->dismissCurrentFrame();
|
||||
return 0;
|
||||
default:
|
||||
// pass the pressed key
|
||||
// LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar);
|
||||
@@ -422,7 +430,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
|
||||
this->notifyObservers(&e);
|
||||
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
|
||||
((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) {
|
||||
!Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) {
|
||||
// Reset module
|
||||
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; // We want to change the list of frames shown on-screen
|
||||
this->currentMessageIndex = -1;
|
||||
@@ -934,6 +942,19 @@ void CannedMessageModule::drawEnterIcon(OLEDDisplay *display, int x, int y, floa
|
||||
|
||||
#endif
|
||||
|
||||
// Indicate to screen class that module is handling keyboard input specially (at certain times)
|
||||
// This prevents the left & right keys being used for nav. between screen frames during text entry.
|
||||
bool CannedMessageModule::interceptingKeyboardInput()
|
||||
{
|
||||
switch (runState) {
|
||||
case CANNED_MESSAGE_RUN_STATE_DISABLED:
|
||||
case CANNED_MESSAGE_RUN_STATE_INACTIVE:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
char buffer[50];
|
||||
|
||||
@@ -114,6 +114,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
|
||||
virtual bool wantUIFrame() override { return this->shouldDraw(); }
|
||||
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
|
||||
virtual bool interceptingKeyboardInput() override;
|
||||
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response) override;
|
||||
|
||||
@@ -5,11 +5,47 @@
|
||||
#include "PowerFSM.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
DetectionSensorModule *detectionSensorModule;
|
||||
|
||||
#define GPIO_POLLING_INTERVAL 100
|
||||
#define DELAYED_INTERVAL 1000
|
||||
|
||||
typedef enum {
|
||||
DetectionSensorVerdictDetected,
|
||||
DetectionSensorVerdictSendState,
|
||||
DetectionSensorVerdictNoop,
|
||||
} DetectionSensorTriggerVerdict;
|
||||
|
||||
typedef DetectionSensorTriggerVerdict (*DetectionSensorTriggerHandler)(bool prev, bool current);
|
||||
|
||||
static DetectionSensorTriggerVerdict detection_trigger_logic_level(bool prev, bool current)
|
||||
{
|
||||
return current ? DetectionSensorVerdictDetected : DetectionSensorVerdictNoop;
|
||||
}
|
||||
|
||||
static DetectionSensorTriggerVerdict detection_trigger_single_edge(bool prev, bool current)
|
||||
{
|
||||
return (!prev && current) ? DetectionSensorVerdictDetected : DetectionSensorVerdictNoop;
|
||||
}
|
||||
|
||||
static DetectionSensorTriggerVerdict detection_trigger_either_edge(bool prev, bool current)
|
||||
{
|
||||
if (prev == current) {
|
||||
return DetectionSensorVerdictNoop;
|
||||
}
|
||||
return current ? DetectionSensorVerdictDetected : DetectionSensorVerdictSendState;
|
||||
}
|
||||
|
||||
const static DetectionSensorTriggerHandler handlers[_meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MAX + 1] = {
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_LOW] = detection_trigger_logic_level,
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH] = detection_trigger_logic_level,
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_FALLING_EDGE] = detection_trigger_single_edge,
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_RISING_EDGE] = detection_trigger_single_edge,
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_LOW] = detection_trigger_either_edge,
|
||||
[meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_EITHER_EDGE_ACTIVE_HIGH] = detection_trigger_either_edge,
|
||||
};
|
||||
|
||||
int32_t DetectionSensorModule::runOnce()
|
||||
{
|
||||
/*
|
||||
@@ -21,7 +57,8 @@ int32_t DetectionSensorModule::runOnce()
|
||||
// moduleConfig.detection_sensor.monitor_pin = 21; // WisBlock RAK12013 Radar IO6
|
||||
// moduleConfig.detection_sensor.minimum_broadcast_secs = 30;
|
||||
// moduleConfig.detection_sensor.state_broadcast_secs = 120;
|
||||
// moduleConfig.detection_sensor.detection_triggered_high = true;
|
||||
// moduleConfig.detection_sensor.detection_trigger_type =
|
||||
// meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH;
|
||||
// strcpy(moduleConfig.detection_sensor.name, "Motion");
|
||||
|
||||
if (moduleConfig.detection_sensor.enabled == false)
|
||||
@@ -49,18 +86,31 @@ int32_t DetectionSensorModule::runOnce()
|
||||
|
||||
// LOG_DEBUG("Detection Sensor Module: Current pin state: %i\n", digitalRead(moduleConfig.detection_sensor.monitor_pin));
|
||||
|
||||
if ((millis() - lastSentToMesh) >= Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs) &&
|
||||
hasDetectionEvent()) {
|
||||
sendDetectionMessage();
|
||||
return DELAYED_INTERVAL;
|
||||
if (!Throttle::isWithinTimespanMs(lastSentToMesh,
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.minimum_broadcast_secs))) {
|
||||
bool isDetected = hasDetectionEvent();
|
||||
DetectionSensorTriggerVerdict verdict =
|
||||
handlers[moduleConfig.detection_sensor.detection_trigger_type](wasDetected, isDetected);
|
||||
wasDetected = isDetected;
|
||||
switch (verdict) {
|
||||
case DetectionSensorVerdictDetected:
|
||||
sendDetectionMessage();
|
||||
return DELAYED_INTERVAL;
|
||||
case DetectionSensorVerdictSendState:
|
||||
sendCurrentStateMessage(isDetected);
|
||||
return DELAYED_INTERVAL;
|
||||
case DetectionSensorVerdictNoop:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Even if we haven't detected an event, broadcast our current state to the mesh on the scheduled interval as a sort
|
||||
// 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,
|
||||
default_telemetry_broadcast_interval_secs)) {
|
||||
sendCurrentStateMessage();
|
||||
if (moduleConfig.detection_sensor.state_broadcast_secs > 0 &&
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh,
|
||||
Default::getConfiguredOrDefaultMs(moduleConfig.detection_sensor.state_broadcast_secs,
|
||||
default_telemetry_broadcast_interval_secs))) {
|
||||
sendCurrentStateMessage(hasDetectionEvent());
|
||||
return DELAYED_INTERVAL;
|
||||
}
|
||||
return GPIO_POLLING_INTERVAL;
|
||||
@@ -86,10 +136,10 @@ void DetectionSensorModule::sendDetectionMessage()
|
||||
delete[] message;
|
||||
}
|
||||
|
||||
void DetectionSensorModule::sendCurrentStateMessage()
|
||||
void DetectionSensorModule::sendCurrentStateMessage(bool state)
|
||||
{
|
||||
char *message = new char[40];
|
||||
sprintf(message, "%s state: %i", moduleConfig.detection_sensor.name, hasDetectionEvent());
|
||||
sprintf(message, "%s state: %i", moduleConfig.detection_sensor.name, state);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
p->want_ack = false;
|
||||
@@ -105,5 +155,5 @@ bool DetectionSensorModule::hasDetectionEvent()
|
||||
{
|
||||
bool currentState = digitalRead(moduleConfig.detection_sensor.monitor_pin);
|
||||
// LOG_DEBUG("Detection Sensor Module: Current state: %i\n", currentState);
|
||||
return moduleConfig.detection_sensor.detection_triggered_high ? currentState : !currentState;
|
||||
return (moduleConfig.detection_sensor.detection_trigger_type & 1) ? currentState : !currentState;
|
||||
}
|
||||
@@ -15,8 +15,9 @@ class DetectionSensorModule : public SinglePortModule, private concurrency::OSTh
|
||||
private:
|
||||
bool firstTime = true;
|
||||
uint32_t lastSentToMesh = 0;
|
||||
bool wasDetected = false;
|
||||
void sendDetectionMessage();
|
||||
void sendCurrentStateMessage();
|
||||
void sendCurrentStateMessage(bool state);
|
||||
bool hasDetectionEvent();
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
NeighborInfoModule *neighborInfoModule;
|
||||
|
||||
@@ -83,11 +84,11 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb
|
||||
*/
|
||||
void NeighborInfoModule::cleanUpNeighbors()
|
||||
{
|
||||
uint32_t now = getTime();
|
||||
NodeNum my_node_id = nodeDB->getNodeNum();
|
||||
for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
|
||||
// We will remove a neighbor if we haven't heard from them in twice the broadcast interval
|
||||
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
|
||||
if (!Throttle::isWithinTimespanMs(it->last_rx_time, it->node_broadcast_interval_secs * 2) &&
|
||||
(it->node_id != my_node_id)) {
|
||||
LOG_DEBUG("Removing neighbor with node ID 0x%x\n", it->node_id);
|
||||
it = std::vector<meshtastic_Neighbor>::reverse_iterator(
|
||||
neighbors.erase(std::next(it).base())); // Erase the element and update the iterator
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
NodeInfoModule *nodeInfoModule;
|
||||
|
||||
@@ -67,13 +68,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
||||
LOG_DEBUG("Skip sending NodeInfo due to > 40 percent channel util.\n");
|
||||
return NULL;
|
||||
}
|
||||
uint32_t now = millis();
|
||||
// If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway.
|
||||
if (!shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) {
|
||||
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) {
|
||||
LOG_DEBUG("Skip sending NodeInfo since we just sent it less than 5 minutes ago.\n");
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return NULL;
|
||||
} else if (shorterTimeout && lastSentToMesh && (now - lastSentToMesh) < (60 * 1000)) {
|
||||
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
|
||||
LOG_DEBUG("Skip sending actively requested NodeInfo since we just sent it less than 60 seconds ago.\n");
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return NULL;
|
||||
@@ -82,7 +82,7 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
||||
meshtastic_User &u = owner;
|
||||
|
||||
LOG_INFO("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name);
|
||||
lastSentToMesh = now;
|
||||
lastSentToMesh = millis();
|
||||
return allocDataProtobuf(u);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "gps/GeoCoord.h"
|
||||
#include "main.h"
|
||||
#include "mesh/compression/unishox2.h"
|
||||
#include "meshUtils.h"
|
||||
#include "meshtastic/atak.pb.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
@@ -145,7 +146,7 @@ void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUp
|
||||
bool PositionModule::hasQualityTimesource()
|
||||
{
|
||||
bool setFromPhoneOrNtpToday =
|
||||
lastSetFromPhoneNtpOrGps == 0 ? false : (millis() - lastSetFromPhoneNtpOrGps) <= (SEC_PER_DAY * 1000UL);
|
||||
lastSetFromPhoneNtpOrGps == 0 ? false : Throttle::isWithinTimespanMs(lastSetFromPhoneNtpOrGps, SEC_PER_DAY * 1000UL);
|
||||
#if MESHTASTIC_EXCLUDE_GPS
|
||||
bool hasGpsOrRtc = (rtc_found.address != ScanI2C::ADDRESS_NONE.address);
|
||||
#else
|
||||
@@ -347,8 +348,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
|
||||
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) &&
|
||||
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER,
|
||||
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) &&
|
||||
config.power.is_power_saving) {
|
||||
LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n");
|
||||
sleepOnNextExecution = true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
extern void printInfo();
|
||||
|
||||
@@ -114,7 +115,7 @@ int32_t PowerStressModule::runOnce()
|
||||
break;
|
||||
case meshtastic_PowerStressMessage_Opcode_CPU_FULLON: {
|
||||
uint32_t start_msec = millis();
|
||||
while ((millis() - start_msec) < (uint32_t)sleep_msec)
|
||||
while (Throttle::isWithinTimespanMs(start_msec, sleep_msec))
|
||||
; // Don't let CPU idle at all
|
||||
sleep_msec = 0; // we already slept
|
||||
break;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "configuration.h"
|
||||
#include "gps/GeoCoord.h"
|
||||
#include <Arduino.h>
|
||||
#include <Throttle.h>
|
||||
|
||||
RangeTestModule *rangeTestModule;
|
||||
RangeTestModuleRadio *rangeTestModuleRadio;
|
||||
@@ -79,7 +80,7 @@ int32_t RangeTestModule::runOnce()
|
||||
}
|
||||
|
||||
// If we have been running for more than 8 hours, turn module back off
|
||||
if (millis() - started > 28800000) {
|
||||
if (!Throttle::isWithinTimespanMs(started, 28800000)) {
|
||||
LOG_INFO("Range Test Module - Disabling after 8 hours\n");
|
||||
return disable();
|
||||
} else {
|
||||
@@ -114,7 +115,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
|
||||
packetSequence++;
|
||||
|
||||
static char heartbeatString[MAX_RHPACKETLEN];
|
||||
static char heartbeatString[MAX_LORA_PAYLOAD_LEN + 1];
|
||||
snprintf(heartbeatString, sizeof(heartbeatString), "seq %u", packetSequence);
|
||||
|
||||
p->decoded.payload.size = strlen(heartbeatString); // You must specify how many bytes are in the reply
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
#define NUM_GPIOS 64
|
||||
|
||||
@@ -118,11 +119,10 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r
|
||||
int32_t RemoteHardwareModule::runOnce()
|
||||
{
|
||||
if (moduleConfig.remote_hardware.enabled && watchGpios) {
|
||||
uint32_t now = millis();
|
||||
|
||||
if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
|
||||
if (!Throttle::isWithinTimespanMs(lastWatchMsec, WATCH_INTERVAL_MSEC)) {
|
||||
uint64_t curVal = digitalReads(watchGpios);
|
||||
lastWatchMsec = now;
|
||||
lastWatchMsec = millis();
|
||||
|
||||
if (curVal != previousWatch) {
|
||||
previousWatch = curVal;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <Throttle.h>
|
||||
|
||||
/*
|
||||
SerialModule
|
||||
@@ -100,8 +101,7 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
|
||||
*/
|
||||
bool SerialModule::checkIsConnected()
|
||||
{
|
||||
uint32_t now = millis();
|
||||
return (now - lastContactMsec) < SERIAL_CONNECTION_TIMEOUT;
|
||||
return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
int32_t SerialModule::runOnce()
|
||||
@@ -194,13 +194,13 @@ int32_t SerialModule::runOnce()
|
||||
return runOncePart();
|
||||
} else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA) && HAS_GPS) {
|
||||
// in NMEA mode send out GGA every 2 seconds, Don't read from Port
|
||||
if (millis() - lastNmeaTime > 2000) {
|
||||
if (!Throttle::isWithinTimespanMs(lastNmeaTime, 2000)) {
|
||||
lastNmeaTime = millis();
|
||||
printGGA(outbuf, sizeof(outbuf), localPosition);
|
||||
serialPrint->printf("%s", outbuf);
|
||||
}
|
||||
} else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO) && HAS_GPS) {
|
||||
if (millis() - lastNmeaTime > 10000) {
|
||||
if (!Throttle::isWithinTimespanMs(lastNmeaTime, 10000)) {
|
||||
lastNmeaTime = millis();
|
||||
uint32_t readIndex = 0;
|
||||
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
||||
@@ -517,7 +517,7 @@ void SerialModule::processWXSerial()
|
||||
LOG_INFO("WS85 : %i %.1fg%.1f %.1fv %.1fv\n", atoi(windDir), strtof(windVel, nullptr), strtof(windGust, nullptr),
|
||||
batVoltageF, capVoltageF);
|
||||
}
|
||||
if (gotwind && millis() - lastAveraged > averageIntervalMillis) {
|
||||
if (gotwind && !Throttle::isWithinTimespanMs(lastAveraged, averageIntervalMillis)) {
|
||||
// calulate averages and send to the mesh
|
||||
float velAvg = 1.0 * velSum / velCount;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Router.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
int32_t AirQualityTelemetryModule::runOnce()
|
||||
{
|
||||
@@ -60,15 +61,14 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
if (!moduleConfig.telemetry.air_quality_enabled)
|
||||
return disable();
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes))) &&
|
||||
!Throttle::isWithinTimespanMs(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();
|
||||
lastSentToMesh = now;
|
||||
lastSentToMesh = millis();
|
||||
} else if (service->isToPhoneQueueEmpty()) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
|
||||
@@ -11,14 +11,15 @@
|
||||
#include "main.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
#include <meshUtils.h>
|
||||
|
||||
#define MAGIC_USB_BATTERY_LEVEL 101
|
||||
|
||||
int32_t DeviceTelemetryModule::runOnce()
|
||||
{
|
||||
refreshUptime();
|
||||
bool isImpoliteRole = config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
bool isImpoliteRole =
|
||||
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER);
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((uptimeLastMs - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
|
||||
|
||||
@@ -64,6 +64,7 @@ T1000xSensor t1000xSensor;
|
||||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
|
||||
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
int32_t EnvironmentTelemetryModule::runOnce()
|
||||
{
|
||||
@@ -82,7 +83,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
*/
|
||||
|
||||
// moduleConfig.telemetry.environment_measurement_enabled = 1;
|
||||
// moduleConfig.telemetry.environment_screen_enabled = 1;
|
||||
// moduleConfig.telemetry.environment_screen_enabled = 1;
|
||||
// moduleConfig.telemetry.environment_update_interval = 15;
|
||||
|
||||
if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
||||
@@ -143,6 +144,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = mlx90632Sensor.runOnce();
|
||||
if (nau7802Sensor.hasSensor())
|
||||
result = nau7802Sensor.runOnce();
|
||||
if (max17048Sensor.hasSensor())
|
||||
result = max17048Sensor.runOnce();
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
@@ -155,21 +158,20 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = bme680Sensor.runTrigger();
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.environment_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
!Throttle::isWithinTimespanMs(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();
|
||||
lastSentToMesh = now;
|
||||
} else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) &&
|
||||
lastSentToMesh = millis();
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
sendTelemetry(NODENUM_BROADCAST, true);
|
||||
lastSentToPhone = now;
|
||||
lastSentToPhone = millis();
|
||||
}
|
||||
}
|
||||
return min(sendToPhoneIntervalMs, result);
|
||||
@@ -397,6 +399,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
|
||||
}
|
||||
}
|
||||
if (max17048Sensor.hasSensor()) {
|
||||
valid = valid && max17048Sensor.getMetrics(m);
|
||||
hasSensor = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
return valid && hasSensor;
|
||||
@@ -587,6 +593,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
if (max17048Sensor.hasSensor()) {
|
||||
result = max17048Sensor.handleAdminMessage(mp, request, response);
|
||||
if (result != AdminMessageHandleResult::NOT_HANDLED)
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
|
||||
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
int32_t PowerTelemetryModule::runOnce()
|
||||
{
|
||||
@@ -59,6 +60,8 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
result = ina260Sensor.runOnce();
|
||||
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized())
|
||||
result = ina3221Sensor.runOnce();
|
||||
if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized())
|
||||
result = max17048Sensor.runOnce();
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
@@ -69,20 +72,19 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
if (!moduleConfig.telemetry.power_measurement_enabled)
|
||||
return disable();
|
||||
|
||||
uint32_t now = millis();
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((now - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.power_update_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes))) &&
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.power_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = now;
|
||||
} else if (((lastSentToPhone == 0) || ((now - lastSentToPhone) >= sendToPhoneIntervalMs)) &&
|
||||
lastSentToMesh = millis();
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
sendTelemetry(NODENUM_BROADCAST, true);
|
||||
lastSentToPhone = now;
|
||||
lastSentToPhone = millis();
|
||||
}
|
||||
}
|
||||
return min(sendToPhoneIntervalMs, result);
|
||||
@@ -128,19 +130,23 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
return;
|
||||
}
|
||||
|
||||
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||
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)");
|
||||
if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) {
|
||||
if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) {
|
||||
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");
|
||||
"Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) {
|
||||
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");
|
||||
"Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) {
|
||||
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");
|
||||
"Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,8 +156,8 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m
|
||||
#ifdef DEBUG_PORT
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, "
|
||||
"ch3_voltage=%f, ch3_current=%f\n",
|
||||
LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, "
|
||||
"ch3_voltage=%.1f, ch3_current=%.1f\n",
|
||||
sender, t->variant.power_metrics.ch1_voltage, t->variant.power_metrics.ch1_current,
|
||||
t->variant.power_metrics.ch2_voltage, t->variant.power_metrics.ch2_current, t->variant.power_metrics.ch3_voltage,
|
||||
t->variant.power_metrics.ch3_current);
|
||||
@@ -172,12 +178,7 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
||||
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;
|
||||
m->variant.power_metrics = meshtastic_PowerMetrics_init_zero;
|
||||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
|
||||
if (ina219Sensor.hasSensor())
|
||||
valid = ina219Sensor.getMetrics(m);
|
||||
@@ -185,6 +186,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
||||
valid = ina260Sensor.getMetrics(m);
|
||||
if (ina3221Sensor.hasSensor())
|
||||
valid = ina3221Sensor.getMetrics(m);
|
||||
if (max17048Sensor.hasSensor())
|
||||
valid = max17048Sensor.getMetrics(m);
|
||||
#endif
|
||||
|
||||
return valid;
|
||||
|
||||
176
src/modules/Telemetry/Sensor/MAX17048Sensor.cpp
Normal file
176
src/modules/Telemetry/Sensor/MAX17048Sensor.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "MAX17048Sensor.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
|
||||
MAX17048Singleton *MAX17048Singleton::GetInstance()
|
||||
{
|
||||
if (pinstance == nullptr) {
|
||||
pinstance = new MAX17048Singleton();
|
||||
}
|
||||
return pinstance;
|
||||
}
|
||||
|
||||
MAX17048Singleton::MAX17048Singleton() {}
|
||||
|
||||
MAX17048Singleton::~MAX17048Singleton() {}
|
||||
|
||||
MAX17048Singleton *MAX17048Singleton::pinstance{nullptr};
|
||||
|
||||
bool MAX17048Singleton::runOnce(TwoWire *theWire)
|
||||
{
|
||||
initialized = begin(theWire);
|
||||
LOG_DEBUG("MAX17048Sensor::runOnce %s\n", initialized ? "began ok" : "begin failed");
|
||||
return initialized;
|
||||
}
|
||||
|
||||
bool MAX17048Singleton::isBatteryCharging()
|
||||
{
|
||||
float volts = cellVoltage();
|
||||
if (isnan(volts)) {
|
||||
LOG_DEBUG("MAX17048Sensor::isBatteryCharging is not connected\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
MAX17048ChargeSample sample;
|
||||
sample.chargeRate = chargeRate(); // charge/discharge rate in percent/hr
|
||||
sample.cellPercent = cellPercent(); // state of charge in percent 0 to 100
|
||||
chargeSamples.push(sample); // save a sample into a fifo buffer
|
||||
|
||||
// Keep the fifo buffer trimmed
|
||||
while (chargeSamples.size() > MAX17048_CHARGING_SAMPLES)
|
||||
chargeSamples.pop();
|
||||
|
||||
// Based on the past n samples, is the lipo charging, discharging or idle
|
||||
if (chargeSamples.front().chargeRate > MAX17048_CHARGING_MINIMUM_RATE &&
|
||||
chargeSamples.back().chargeRate > MAX17048_CHARGING_MINIMUM_RATE) {
|
||||
if (chargeSamples.front().cellPercent > chargeSamples.back().cellPercent)
|
||||
chargeState = MAX17048ChargeState::EXPORT;
|
||||
else if (chargeSamples.front().cellPercent < chargeSamples.back().cellPercent)
|
||||
chargeState = MAX17048ChargeState::IMPORT;
|
||||
else
|
||||
chargeState = MAX17048ChargeState::IDLE;
|
||||
} else {
|
||||
chargeState = MAX17048ChargeState::IDLE;
|
||||
}
|
||||
|
||||
LOG_DEBUG("MAX17048Sensor::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", chargeLabels[chargeState], volts,
|
||||
sample.cellPercent, sample.chargeRate);
|
||||
return chargeState == MAX17048ChargeState::IMPORT;
|
||||
}
|
||||
|
||||
uint16_t MAX17048Singleton::getBusVoltageMv()
|
||||
{
|
||||
float volts = cellVoltage();
|
||||
if (isnan(volts)) {
|
||||
LOG_DEBUG("MAX17048Sensor::getBusVoltageMv is not connected\n");
|
||||
return 0;
|
||||
}
|
||||
LOG_DEBUG("MAX17048Sensor::getBusVoltageMv %.3fmV\n", volts);
|
||||
return (uint16_t)(volts * 1000.0f);
|
||||
}
|
||||
|
||||
uint8_t MAX17048Singleton::getBusBatteryPercent()
|
||||
{
|
||||
float soc = cellPercent();
|
||||
LOG_DEBUG("MAX17048Sensor::getBusBatteryPercent %.1f%%\n", soc);
|
||||
return clamp(static_cast<uint8_t>(round(soc)), static_cast<uint8_t>(0), static_cast<uint8_t>(100));
|
||||
}
|
||||
|
||||
uint16_t MAX17048Singleton::getTimeToGoSecs()
|
||||
{
|
||||
float rate = chargeRate(); // charge/discharge rate in percent/hr
|
||||
float soc = cellPercent(); // state of charge in percent 0 to 100
|
||||
soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100%
|
||||
float ttg = ((100.0f - soc) / rate) * 3600.0f; // calculate seconds to charge/discharge
|
||||
LOG_DEBUG("MAX17048Sensor::getTimeToGoSecs %.0f seconds\n", ttg);
|
||||
return (uint16_t)ttg;
|
||||
}
|
||||
|
||||
bool MAX17048Singleton::isBatteryConnected()
|
||||
{
|
||||
float volts = cellVoltage();
|
||||
if (isnan(volts)) {
|
||||
LOG_DEBUG("MAX17048Sensor::isBatteryConnected is not connected\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if a valid voltage is returned, then battery must be connected
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MAX17048Singleton::isExternallyPowered()
|
||||
{
|
||||
float volts = cellVoltage();
|
||||
if (isnan(volts)) {
|
||||
// if the battery is not connected then there must be external power
|
||||
LOG_DEBUG("MAX17048Sensor::isExternallyPowered battery is\n");
|
||||
return true;
|
||||
}
|
||||
// if the bus voltage is over MAX17048_BUS_POWER_VOLTS, then the external power
|
||||
// is assumed to be connected
|
||||
LOG_DEBUG("MAX17048Sensor::isExternallyPowered %s connected\n", volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not");
|
||||
return volts >= MAX17048_BUS_POWER_VOLTS;
|
||||
}
|
||||
|
||||
#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY))
|
||||
|
||||
MAX17048Sensor::MAX17048Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MAX17048, "MAX17048") {}
|
||||
|
||||
int32_t MAX17048Sensor::runOnce()
|
||||
{
|
||||
if (isInitialized()) {
|
||||
LOG_INFO("Init sensor: %s is already initialised\n", sensorName);
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("Init sensor: %s\n", sensorName);
|
||||
if (!hasSensor()) {
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
|
||||
// Get a singleton instance and initialise the max17048
|
||||
if (max17048 == nullptr) {
|
||||
max17048 = MAX17048Singleton::GetInstance();
|
||||
}
|
||||
status = max17048->runOnce(nodeTelemetrySensorsMap[sensorType].second);
|
||||
return initI2CSensor();
|
||||
}
|
||||
|
||||
void MAX17048Sensor::setup() {}
|
||||
|
||||
bool MAX17048Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
LOG_DEBUG("MAX17048Sensor::getMetrics id: %i\n", measurement->which_variant);
|
||||
|
||||
float volts = max17048->cellVoltage();
|
||||
if (isnan(volts)) {
|
||||
LOG_DEBUG("MAX17048Sensor::getMetrics battery is not connected\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
float rate = max17048->chargeRate(); // charge/discharge rate in percent/hr
|
||||
float soc = max17048->cellPercent(); // state of charge in percent 0 to 100
|
||||
soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100%
|
||||
float ttg = (100.0f - soc) / rate; // calculate hours to charge/discharge
|
||||
|
||||
LOG_DEBUG("MAX17048Sensor::getMetrics volts: %.3fV soc: %.1f%% ttg: %.1f hours\n", volts, soc, ttg);
|
||||
if ((int)measurement->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||
measurement->variant.power_metrics.has_ch1_voltage = true;
|
||||
measurement->variant.power_metrics.ch1_voltage = volts;
|
||||
} else if ((int)measurement->which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||
measurement->variant.device_metrics.has_battery_level = true;
|
||||
measurement->variant.device_metrics.has_voltage = true;
|
||||
measurement->variant.device_metrics.battery_level = static_cast<uint32_t>(round(soc));
|
||||
measurement->variant.device_metrics.voltage = volts;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t MAX17048Sensor::getBusVoltageMv()
|
||||
{
|
||||
return max17048->getBusVoltageMv();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
110
src/modules/Telemetry/Sensor/MAX17048Sensor.h
Normal file
110
src/modules/Telemetry/Sensor/MAX17048Sensor.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef MAX17048_SENSOR_H
|
||||
#define MAX17048_SENSOR_H
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
|
||||
// Samples to store in a buffer to determine if the battery is charging or discharging
|
||||
#define MAX17048_CHARGING_SAMPLES 3
|
||||
|
||||
// Threshold to determine if the battery is on charge, in percent/hour
|
||||
#define MAX17048_CHARGING_MINIMUM_RATE 1.0f
|
||||
|
||||
// Threshold to determine if the board has bus power
|
||||
#define MAX17048_BUS_POWER_VOLTS 4.195f
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "VoltageSensor.h"
|
||||
|
||||
#include "meshUtils.h"
|
||||
#include <Adafruit_MAX1704X.h>
|
||||
#include <queue>
|
||||
|
||||
struct MAX17048ChargeSample {
|
||||
float cellPercent;
|
||||
float chargeRate;
|
||||
};
|
||||
|
||||
enum MAX17048ChargeState { IDLE, EXPORT, IMPORT };
|
||||
|
||||
// Singleton wrapper for the Adafruit_MAX17048 class
|
||||
class MAX17048Singleton : public Adafruit_MAX17048
|
||||
{
|
||||
private:
|
||||
static MAX17048Singleton *pinstance;
|
||||
bool initialized = false;
|
||||
std::queue<MAX17048ChargeSample> chargeSamples;
|
||||
MAX17048ChargeState chargeState = IDLE;
|
||||
const String chargeLabels[3] = {F("idle"), F("export"), F("import")};
|
||||
|
||||
protected:
|
||||
MAX17048Singleton();
|
||||
~MAX17048Singleton();
|
||||
|
||||
public:
|
||||
// Create a singleton instance (not thread safe)
|
||||
static MAX17048Singleton *GetInstance();
|
||||
|
||||
// Singletons should not be cloneable.
|
||||
MAX17048Singleton(MAX17048Singleton &other) = delete;
|
||||
|
||||
// Singletons should not be assignable.
|
||||
void operator=(const MAX17048Singleton &) = delete;
|
||||
|
||||
// Initialise the sensor (not thread safe)
|
||||
virtual bool runOnce(TwoWire *theWire = &Wire);
|
||||
|
||||
// Get the current bus voltage
|
||||
uint16_t getBusVoltageMv();
|
||||
|
||||
// Get the state of charge in percent 0 to 100
|
||||
uint8_t getBusBatteryPercent();
|
||||
|
||||
// Calculate the seconds to charge/discharge
|
||||
uint16_t getTimeToGoSecs();
|
||||
|
||||
// Returns true if the battery sensor has started
|
||||
inline virtual bool isInitialised() { return initialized; };
|
||||
|
||||
// Returns true if the battery is currently on charge (not thread safe)
|
||||
bool isBatteryCharging();
|
||||
|
||||
// Returns true if a battery is actually connected
|
||||
bool isBatteryConnected();
|
||||
|
||||
// Returns true if there is bus or external power connected
|
||||
bool isExternallyPowered();
|
||||
};
|
||||
|
||||
#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY))
|
||||
|
||||
class MAX17048Sensor : public TelemetrySensor, VoltageSensor
|
||||
{
|
||||
private:
|
||||
MAX17048Singleton *max17048 = nullptr;
|
||||
|
||||
protected:
|
||||
virtual void setup() override;
|
||||
|
||||
public:
|
||||
MAX17048Sensor();
|
||||
|
||||
// Initialise the sensor
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
// Get the current bus voltage and state of charge
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
|
||||
// Get the current bus voltage
|
||||
virtual uint16_t getBusVoltageMv() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "NAU7802Sensor.h"
|
||||
#include "SafeFile.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <Throttle.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
@@ -40,7 +41,7 @@ bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
uint32_t start = millis();
|
||||
while (!nau7802.available()) {
|
||||
delay(100);
|
||||
if (millis() - start > 1000) {
|
||||
if (!Throttle::isWithinTimespanMs(start, 1000)) {
|
||||
nau7802.powerDown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "Throttle.h"
|
||||
#include "airtime.h"
|
||||
#include "configuration.h"
|
||||
#include "memGet.h"
|
||||
@@ -29,6 +30,9 @@
|
||||
|
||||
StoreForwardModule *storeForwardModule;
|
||||
|
||||
uint32_t lastHeartbeat = 0;
|
||||
uint32_t heartbeatInterval = 60; // Default to 60 seconds, adjust as needed
|
||||
|
||||
int32_t StoreForwardModule::runOnce()
|
||||
{
|
||||
#ifdef ARCH_ESP32
|
||||
@@ -42,7 +46,7 @@ int32_t StoreForwardModule::runOnce()
|
||||
this->busy = false;
|
||||
}
|
||||
}
|
||||
} else if (this->heartbeat && (millis() - lastHeartbeat > (heartbeatInterval * 1000)) &&
|
||||
} else if (this->heartbeat && (!Throttle::isWithinTimespanMs(lastHeartbeat, heartbeatInterval * 1000)) &&
|
||||
airTime->isTxAllowedChannelUtil(true)) {
|
||||
lastHeartbeat = millis();
|
||||
LOG_INFO("*** Sending heartbeat\n");
|
||||
@@ -127,7 +131,7 @@ uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_
|
||||
{
|
||||
uint32_t count = 0;
|
||||
if (lastRequest.find(dest) == lastRequest.end()) {
|
||||
lastRequest[dest] = 0;
|
||||
lastRequest.emplace(dest, 0);
|
||||
}
|
||||
for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) {
|
||||
if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) {
|
||||
|
||||
Reference in New Issue
Block a user