-
Notifications
You must be signed in to change notification settings - Fork 964
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
INA3221 / Power Telemetry Payload Variant Implementation (#2916)
* INA3221 / Power Telemetry Variant Implementation modified: platformio.ini modified: src/configuration.h modified: src/detect/ScanI2C.h modified: src/detect/ScanI2CTwoWire.cpp modified: src/main.cpp modified: src/modules/Modules.cpp new file: src/modules/Telemetry/PowerTelemetry.cpp new file: src/modules/Telemetry/PowerTelemetry.h new file: src/modules/Telemetry/Sensor/INA3221Sensor.cpp new file: src/modules/Telemetry/Sensor/INA3221Sensor.h modified: src/mqtt/MQTT.cpp * ifdef for portduino / linux native modified: src/modules/Telemetry/PowerTelemetry.cpp * try #2 modified: src/modules/Modules.cpp modified: src/modules/Telemetry/PowerTelemetry.cpp deleted: variants/xiao_ble/1.0.0/libraries/SPI/SPI.cpp * try #3 modified: src/modules/Modules.cpp * try #4 modified: src/modules/Telemetry/PowerTelemetry.cpp * try #5? modified: src/modules/Telemetry/PowerTelemetry.cpp * try #6 modified: src/modules/Telemetry/PowerTelemetry.cpp --------- Co-authored-by: Ben Meadors <[email protected]>
- Loading branch information
1 parent
4a6cc8f
commit f570204
Showing
11 changed files
with
364 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ class ScanI2C | |
BMP_280, | ||
INA260, | ||
INA219, | ||
INA3221, | ||
MCP9808, | ||
SHT31, | ||
SHTC3, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
#include "PowerTelemetry.h" | ||
#include "../mesh/generated/meshtastic/telemetry.pb.h" | ||
#include "MeshService.h" | ||
#include "NodeDB.h" | ||
#include "PowerFSM.h" | ||
#include "RTC.h" | ||
#include "Router.h" | ||
#include "configuration.h" | ||
#include "main.h" | ||
#include "power.h" | ||
#include "sleep.h" | ||
#include "target_specific.h" | ||
|
||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) | ||
#include "Sensor/INA3221Sensor.h" | ||
INA3221Sensor ina3221Sensor; | ||
#endif | ||
|
||
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 | ||
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true | ||
|
||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \ | ||
!defined(DISPLAY_FORCE_SMALL_FONTS) | ||
|
||
// The screen is bigger so use bigger fonts | ||
#define FONT_SMALL ArialMT_Plain_16 | ||
#define FONT_MEDIUM ArialMT_Plain_24 | ||
#define FONT_LARGE ArialMT_Plain_24 | ||
#else | ||
#define FONT_SMALL ArialMT_Plain_10 | ||
#define FONT_MEDIUM ArialMT_Plain_16 | ||
#define FONT_LARGE ArialMT_Plain_24 | ||
#endif | ||
|
||
#define fontHeight(font) ((font)[1] + 1) // height is position 1 | ||
|
||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) | ||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) | ||
|
||
int32_t PowerTelemetryModule::runOnce() | ||
{ | ||
if (sleepOnNextExecution == true) { | ||
sleepOnNextExecution = false; | ||
uint32_t nightyNightMs = getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval); | ||
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.\n", nightyNightMs); | ||
doDeepSleep(nightyNightMs, true); | ||
} | ||
|
||
uint32_t result = UINT32_MAX; | ||
/* | ||
Uncomment the preferences below if you want to use the module | ||
without having to configure it from the PythonAPI or WebUI. | ||
*/ | ||
|
||
// moduleConfig.telemetry.power_measurement_enabled = 1; | ||
// moduleConfig.telemetry.power_screen_enabled = 1; | ||
// moduleConfig.telemetry.power_update_interval = 45; | ||
|
||
if (!(moduleConfig.telemetry.power_measurement_enabled)) { | ||
// If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it | ||
return disable(); | ||
} | ||
|
||
if (firstTime) { | ||
// This is the first time the OSThread library has called this function, so do some setup | ||
firstTime = 0; | ||
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) | ||
if (moduleConfig.telemetry.power_measurement_enabled) { | ||
LOG_INFO("Power Telemetry: Initializing\n"); | ||
// it's possible to have this module enabled, only for displaying values on the screen. | ||
// therefore, we should only enable the sensor loop if measurement is also enabled | ||
if (ina219Sensor.hasSensor() && !ina219Sensor.isInitialized()) | ||
result = ina219Sensor.runOnce(); | ||
if (ina260Sensor.hasSensor() && !ina260Sensor.isInitialized()) | ||
result = ina260Sensor.runOnce(); | ||
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized()) | ||
result = ina3221Sensor.runOnce(); | ||
} | ||
return result; | ||
#else | ||
return disable(); | ||
#endif | ||
} else { | ||
// if we somehow got to a second run of this module with measurement disabled, then just wait forever | ||
if (!moduleConfig.telemetry.power_measurement_enabled) | ||
return disable(); | ||
|
||
uint32_t now = millis(); | ||
if (((lastSentToMesh == 0) || | ||
((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.power_update_interval))) && | ||
airTime->isTxAllowedAirUtil()) { | ||
sendTelemetry(); | ||
lastSentToMesh = now; | ||
} else if (((lastSentToPhone == 0) || ((now - 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; | ||
} | ||
} | ||
return min(sendToPhoneIntervalMs, result); | ||
} | ||
bool PowerTelemetryModule::wantUIFrame() | ||
{ | ||
return moduleConfig.telemetry.power_screen_enabled; | ||
} | ||
|
||
uint32_t GetTimeyWimeySinceMeshPacket(const meshtastic_MeshPacket *mp) | ||
{ | ||
uint32_t now = getTime(); | ||
|
||
uint32_t last_seen = mp->rx_time; | ||
int delta = (int)(now - last_seen); | ||
if (delta < 0) // our clock must be slightly off still - not set from GPS yet | ||
delta = 0; | ||
|
||
return delta; | ||
} | ||
|
||
void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) | ||
{ | ||
display->setTextAlignment(TEXT_ALIGN_LEFT); | ||
display->setFont(FONT_MEDIUM); | ||
display->drawString(x, y, "Power Telemetry"); | ||
if (lastMeasurementPacket == nullptr) { | ||
display->setFont(FONT_SMALL); | ||
display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement"); | ||
return; | ||
} | ||
|
||
meshtastic_Telemetry lastMeasurement; | ||
|
||
uint32_t agoSecs = GetTimeyWimeySinceMeshPacket(lastMeasurementPacket); | ||
const char *lastSender = getSenderShortName(*lastMeasurementPacket); | ||
|
||
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"); | ||
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)"); | ||
if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { | ||
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), | ||
"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), | ||
"Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + | ||
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); | ||
} | ||
} | ||
|
||
bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) | ||
{ | ||
if (t->which_variant == meshtastic_Telemetry_power_metrics_tag) { | ||
#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", | ||
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); | ||
#endif | ||
// release previous packet before occupying a new spot | ||
if (lastMeasurementPacket != nullptr) | ||
packetPool.release(lastMeasurementPacket); | ||
|
||
lastMeasurementPacket = packetPool.allocCopy(mp); | ||
} | ||
|
||
return false; // Let others look at this message also if they want | ||
} | ||
|
||
bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) | ||
{ | ||
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 | ||
|
||
if (valid) { | ||
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, | ||
m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_voltage, m.variant.power_metrics.ch3_current); | ||
|
||
sensor_read_error_count = 0; | ||
|
||
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_MIN; | ||
// 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); | ||
|
||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { | ||
LOG_DEBUG("Starting next execution in 5 seconds and then going to sleep.\n"); | ||
sleepOnNextExecution = true; | ||
setIntervalFromNow(5000); | ||
} | ||
} | ||
} | ||
return valid; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#pragma once | ||
#include "../mesh/generated/meshtastic/telemetry.pb.h" | ||
#include "NodeDB.h" | ||
#include "ProtobufModule.h" | ||
#include <OLEDDisplay.h> | ||
#include <OLEDDisplayUi.h> | ||
|
||
class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry> | ||
{ | ||
public: | ||
PowerTelemetryModule() | ||
: concurrency::OSThread("PowerTelemetryModule"), | ||
ProtobufModule("PowerTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) | ||
{ | ||
lastMeasurementPacket = nullptr; | ||
setIntervalFromNow(10 * 1000); | ||
} | ||
virtual bool wantUIFrame() override; | ||
#if !HAS_SCREEN | ||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); | ||
#else | ||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; | ||
#endif | ||
|
||
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_Telemetry *p) override; | ||
virtual int32_t runOnce() override; | ||
/** | ||
* Send our Telemetry into the mesh | ||
*/ | ||
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); | ||
|
||
private: | ||
bool firstTime = 1; | ||
meshtastic_MeshPacket *lastMeasurementPacket; | ||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute | ||
uint32_t lastSentToMesh = 0; | ||
uint32_t lastSentToPhone = 0; | ||
uint32_t sensor_read_error_count = 0; | ||
}; |
Oops, something went wrong.