From e25f0392c5271cb7d381734e5544c9dceaabc6ab Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Tue, 1 Jul 2025 17:23:52 +0200 Subject: [PATCH 01/12] Move PMSA003I to separate class and update AQ telemetry --- src/modules/Telemetry/AirQualityTelemetry.cpp | 313 ++++++++++++------ src/modules/Telemetry/AirQualityTelemetry.h | 39 ++- .../Telemetry/EnvironmentTelemetry.cpp | 2 - src/modules/Telemetry/EnvironmentTelemetry.h | 1 - .../Telemetry/Sensor/PMSA003ISensor.cpp | 89 +++++ src/modules/Telemetry/Sensor/PMSA003ISensor.h | 52 +++ 6 files changed, 368 insertions(+), 128 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/PMSA003ISensor.cpp create mode 100644 src/modules/Telemetry/Sensor/PMSA003ISensor.h diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 2472b95b1..8b7ab1b24 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,36 +1,54 @@ #include "configuration.h" -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h") +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "AirQualityTelemetry.h" #include "Default.h" +#include "AirQualityTelemetry.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerFSM.h" #include "RTC.h" #include "Router.h" -#include "detect/ScanI2CTwoWire.h" +#include "UnitConversions.h" +#include "graphics/SharedUIDisplay.h" +#include "graphics/images.h" #include "main.h" +#include "sleep.h" #include -#ifndef PMSA003I_WARMUP_MS -// from the PMSA003I datasheet: -// "Stable data should be got at least 30 seconds after the sensor wakeup -// from the sleep mode because of the fan’s performance." -#define PMSA003I_WARMUP_MS 30000 +#if __has_include() +#include "Sensor/PMSA003ISensor.h" +PMSA003ISensor pmsa003iSensor; +#else +NullSensor pmsa003iSensor; #endif int32_t AirQualityTelemetryModule::runOnce() { + if (sleepOnNextExecution == true) { + sleepOnNextExecution = false; + // uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs); + LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs); + doDeepSleep(nightyNightMs, true, false); + } + + 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.air_quality_enabled = 1; + // TODO there is no config in module_config.proto for air_quality_screen_enabled. Reusing environment one, although it should have its own + // moduleConfig.telemetry.environment_screen_enabled = 1; + // moduleConfig.telemetry.air_quality_interval = 15; - if (!(moduleConfig.telemetry.air_quality_enabled)) { + if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.environment_screen_enabled || + AIR_QUALITY_TELEMETRY_MODULE_ENABLE)) { // 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(); } @@ -42,79 +60,141 @@ int32_t AirQualityTelemetryModule::runOnce() if (moduleConfig.telemetry.air_quality_enabled) { LOG_INFO("Air quality Telemetry: init"); -#ifdef PMSA003I_ENABLE_PIN - // put the sensor to sleep on startup - pinMode(PMSA003I_ENABLE_PIN, OUTPUT); - digitalWrite(PMSA003I_ENABLE_PIN, LOW); -#endif /* PMSA003I_ENABLE_PIN */ - - if (!aqi.begin_I2C()) { -#ifndef I2C_NO_RESCAN - LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan"); - // rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty. - uint8_t i2caddr_scan[] = {PMSA0031_ADDR}; - uint8_t i2caddr_asize = 1; - auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); -#if defined(I2C_SDA1) - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); -#endif - i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); - auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031); - if (found.type != ScanI2C::DeviceType::NONE) { - nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address; - nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second = - i2cScanner->fetchI2CBus(found.address); - return setStartDelay(); - } -#endif - return disable(); - } - return setStartDelay(); + if (pmsa003iSensor.hasSensor()) + result = pmsa003iSensor.runOnce(); } - return disable(); + + // 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 + return result == UINT32_MAX ? disable() : setStartDelay(); } else { // if we somehow got to a second run of this module with measurement disabled, then just wait forever - if (!moduleConfig.telemetry.air_quality_enabled) - return disable(); - - switch (state) { -#ifdef PMSA003I_ENABLE_PIN - case State::IDLE: - // sensor is in standby; fire it up and sleep - LOG_DEBUG("runOnce(): state = idle"); - digitalWrite(PMSA003I_ENABLE_PIN, HIGH); - state = State::ACTIVE; - - return PMSA003I_WARMUP_MS; -#endif /* PMSA003I_ENABLE_PIN */ - case State::ACTIVE: - // sensor is already warmed up; grab telemetry and send it - LOG_DEBUG("runOnce(): state = active"); - - if (((lastSentToMesh == 0) || - !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 = 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) - sendTelemetry(NODENUM_BROADCAST, true); - } - -#ifdef PMSA003I_ENABLE_PIN - // put sensor back to sleep - digitalWrite(PMSA003I_ENABLE_PIN, LOW); - state = State::IDLE; -#endif /* PMSA003I_ENABLE_PIN */ - - return sendToPhoneIntervalMs; - default: + if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) { return disable(); } + + // Wake up the sensors that need it +#ifdef PMSA003I_ENABLE_PIN + if (pmsa003iSensor.hasSensor() && pmsa003iSensor.state == pmsa003iSensor::State::IDLE) + return pmsa003iSensor.wakeUp(); +#endif /* PMSA003I_ENABLE_PIN */ + + if (((lastSentToMesh == 0) || + !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 = 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 = millis(); + } + +#ifdef PMSA003I_ENABLE_PIN + pmsa003iSensor.sleep(); +#endif /* PMSA003I_ENABLE_PIN */ + + } + return min(sendToPhoneIntervalMs, result); +} + +bool AirQualityTelemetryModule::wantUIFrame() +{ + return moduleConfig.telemetry.environment_screen_enabled; +} + +void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // === Setup display === + display->clear(); + display->setFont(FONT_SMALL); + display->setTextAlignment(TEXT_ALIGN_LEFT); + int line = 1; + + // === Set Title + const char *titleStr = (graphics::isHighResolution) ? "Environment" : "Env."; + + // === Header === + graphics::drawCommonHeader(display, x, y, titleStr); + + // === Row spacing setup === + const int rowHeight = FONT_HEIGHT_SMALL - 4; + int currentY = graphics::getTextPositions(display)[line++]; + + // === Show "No Telemetry" if no data available === + if (!lastMeasurementPacket) { + display->drawString(x, currentY, "No Telemetry"); + return; + } + + // Decode the telemetry message from the latest received packet + const meshtastic_Data &p = lastMeasurementPacket->decoded; + meshtastic_Telemetry telemetry; + if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &telemetry)) { + display->drawString(x, currentY, "No Telemetry"); + return; + } + + const auto &m = telemetry.variant.air_quality_metrics; + + // Check if any telemetry field has valid data + bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental || m.has_pm25_environmental || + m.has_pm100_environmental; + + if (!hasAny) { + display->drawString(x, currentY, "No Telemetry"); + return; + } + + // === First line: Show sender name + time since received (left), and first metric (right) === + const char *sender = getSenderShortName(*lastMeasurementPacket); + uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); + String agoStr = (agoSecs > 864000) ? "?" + : (agoSecs > 3600) ? String(agoSecs / 3600) + "h" + : (agoSecs > 60) ? String(agoSecs / 60) + "m" + : String(agoSecs) + "s"; + + String leftStr = String(sender) + " (" + agoStr + ")"; + display->drawString(x, currentY, leftStr); // Left side: who and when + + // === Collect sensor readings as label strings (no icons) === + std::vector entries; + + if (m.has_pm10_standard) + entries.push_back("PM1.0: " + String(m.pm10_standard, 0) + "ug/m3"); + if (m.has_pm25_standard) + entries.push_back("PM2.5: " + String(m.pm25_standard, 0) + "ug/m3"); + if (m.has_pm100_standard) + entries.push_back("PM10.0: " + String(m.pm100_standard, 0) + "ug/m3"); + + // === Show first available metric on top-right of first line === + if (!entries.empty()) { + String valueStr = entries.front(); + int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr); + display->drawString(rightX, currentY, valueStr); + entries.erase(entries.begin()); // Remove from queue + } + + // === Advance to next line for remaining telemetry entries === + currentY += rowHeight; + + // === Draw remaining entries in 2-column format (left and right) === + for (size_t i = 0; i < entries.size(); i += 2) { + // Left column + display->drawString(x, currentY, entries[i]); + + // Right column if it exists + if (i + 1 < entries.size()) { + int rightX = SCREEN_WIDTH / 2; + display->drawString(rightX, currentY, entries[i + 1]); + } + + currentY += rowHeight; } } @@ -142,37 +222,23 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } +// CHECKED bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { - if (!aqi.read(&data)) { - LOG_WARN("Skip send measurements. Could not read AQIn"); - return false; - } - + bool valid = true; + bool hasSensor = false; m->time = getTime(); m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; - m->variant.air_quality_metrics.has_pm10_standard = true; - m->variant.air_quality_metrics.pm10_standard = data.pm10_standard; - m->variant.air_quality_metrics.has_pm25_standard = true; - m->variant.air_quality_metrics.pm25_standard = data.pm25_standard; - m->variant.air_quality_metrics.has_pm100_standard = true; - m->variant.air_quality_metrics.pm100_standard = data.pm100_standard; + m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero; - m->variant.air_quality_metrics.has_pm10_environmental = true; - m->variant.air_quality_metrics.pm10_environmental = data.pm10_env; - m->variant.air_quality_metrics.has_pm25_environmental = true; - m->variant.air_quality_metrics.pm25_environmental = data.pm25_env; - m->variant.air_quality_metrics.has_pm100_environmental = true; - m->variant.air_quality_metrics.pm100_environmental = data.pm100_env; + if (pmsa003iSensor.hasSensor()) { + // TODO - Should we check for sensor state here? + // If a sensor is sleeping, we should know and check to wake it up + valid = valid && pmsa003iSensor.getMetrics(m); + hasSensor = true; + } - LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", 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", - m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental, - m->variant.air_quality_metrics.pm100_environmental); - - return true; + return valid && hasSensor; } meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() @@ -206,7 +272,14 @@ meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; + m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; + m.time = getTime(); if (getAirQualityTelemetry(&m)) { + LOG_INFO("Send: pm10_standard=%f, pm25_standard=%f, pm100_standard=%f, pm10_environmental=%f, pm100_environmental=%f", + 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_environmental, + m.variant.air_quality_metrics.pm100_environmental); + meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; p->decoded.want_response = false; @@ -221,16 +294,46 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) lastMeasurementPacket = packetPool.allocCopy(*p); if (phoneOnly) { - LOG_INFO("Send packet to phone"); + LOG_INFO("Sending packet to phone"); service->sendToPhone(p); } else { - LOG_INFO("Send packet to mesh"); + LOG_INFO("Sending packet to mesh"); service->sendToMesh(p, RX_SRC_LOCAL, true); + + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { + meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + sleepOnNextExecution = true; + LOG_DEBUG("Start next execution in 5s, then sleep"); + setIntervalFromNow(FIVE_SECONDS_MS); + } } return true; } - return false; } +AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp, + meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED; +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL + if (pmsa003iSensor.hasSensor()) { + result = pmsa003iSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } + + +#endif + return result; +} + #endif \ No newline at end of file diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 0142ee686..8314c54bc 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -1,12 +1,18 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h") +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #pragma once + +#ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE +#define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0 +#endif + #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "Adafruit_PM25AQI.h" #include "NodeDB.h" #include "ProtobufModule.h" +#include +#include class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule { @@ -20,18 +26,15 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { lastMeasurementPacket = nullptr; - setIntervalFromNow(10 * 1000); - aqi = Adafruit_PM25AQI(); nodeStatusObserver.observe(&nodeStatus->onNewStatus); - -#ifdef PMSA003I_ENABLE_PIN - // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking - // a reading - state = State::IDLE; -#else - state = State::ACTIVE; -#endif + 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 @@ -49,19 +52,15 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf */ bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); + virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, + meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; private: - enum State { - IDLE = 0, - ACTIVE = 1, - }; - - State state; - Adafruit_PM25AQI aqi; - PM25_AQI_Data data = {0}; bool firstTime = true; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; + uint32_t lastSentToPhone = 0; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index d1b10fa82..2d6a8a0cb 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -743,8 +743,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature, m.variant.environment_metrics.soil_moisture); - sensor_read_error_count = 0; - meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; p->decoded.want_response = false; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index d70c063fc..ffbb229f0 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -62,7 +62,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu 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; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp new file mode 100644 index 000000000..dacdf5ff4 --- /dev/null +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -0,0 +1,89 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "PMSA003ISensor.h" +#include "TelemetrySensor.h" +#include "detect/ScanI2CTwoWire.h" +#include + +PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {} + +int32_t PMSA003ISensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + +#ifdef PMSA003I_ENABLE_PIN +// TODO not sure why this was like this + sleep(); +#endif /* PMSA003I_ENABLE_PIN */ + + if (!pmsa003i.begin_I2C()){ +#ifndef I2C_NO_RESCAN + LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan"); + // rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty. + uint8_t i2caddr_scan[] = {PMSA0031_ADDR}; + uint8_t i2caddr_asize = 1; + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); +#if defined(I2C_SDA1) + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); + auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031); + if (found.type != ScanI2C::DeviceType::NONE) { + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address; + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second = + i2cScanner->fetchI2CBus(found.address); + return initI2CSensor(); + } +#endif + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + return initI2CSensor(); +} + +void PMSA003ISensor::setup() +{ +} + +#ifdef PMSA003I_ENABLE_PIN +void sleep() { + digitalWrite(PMSA003I_ENABLE_PIN, LOW); + state = State::IDLE; +} + +uint32_t wakeUp() { + digitalWrite(PMSA003I_ENABLE_PIN, HIGH); + state = State::ACTIVE; +} +#endif /* PMSA003I_ENABLE_PIN */ + +bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) +{ + if (!pmsa003i.read(&pmsa003iData)) { + LOG_WARN("Skip send measurements. Could not read AQIn"); + return false; + } + + measurement->variant.air_quality_metrics.has_pm10_standard = true; + measurement->variant.air_quality_metrics.pm10_standard = pmsa003iData.pm10_standard; + measurement->variant.air_quality_metrics.has_pm25_standard = true; + measurement->variant.air_quality_metrics.pm25_standard = pmsa003iData.pm25_standard; + measurement->variant.air_quality_metrics.has_pm100_standard = true; + measurement->variant.air_quality_metrics.pm100_standard = pmsa003iData.pm100_standard; + + measurement->variant.air_quality_metrics.has_pm10_environmental = true; + measurement->variant.air_quality_metrics.pm10_environmental = pmsa003iData.pm10_env; + measurement->variant.air_quality_metrics.has_pm25_environmental = true; + measurement->variant.air_quality_metrics.pm25_environmental = pmsa003iData.pm25_env; + measurement->variant.air_quality_metrics.has_pm100_environmental = true; + measurement->variant.air_quality_metrics.pm100_environmental = pmsa003iData.pm100_env; + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h new file mode 100644 index 000000000..01b04368e --- /dev/null +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -0,0 +1,52 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "detect/ScanI2CTwoWire.h" +#include + +#ifndef PMSA003I_WARMUP_MS +// from the PMSA003I datasheet: +// "Stable data should be got at least 30 seconds after the sensor wakeup +// from the sleep mode because of the fan’s performance." +#define PMSA003I_WARMUP_MS 30000 +#endif + +class PMSA003ISensor : public TelemetrySensor +{ + private: + Adafruit_PM25AQI pmsa003i = Adafruit_PM25AQI(); + PM25_AQI_Data pmsa003iData = {0}; + +#ifdef PMSA003I_ENABLE_PIN + void sleep(); + uint32_t wakeUp(); +#endif + + protected: + virtual void setup() override; + + public: + enum State { + IDLE = 0, + ACTIVE = 1, + }; + +#ifdef PMSA003I_ENABLE_PIN + // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking + // a reading + // put the sensor to sleep on startup + pinMode(PMSA003I_ENABLE_PIN, OUTPUT); + State state = State::IDLE; +#else + State state = State::ACTIVE; +#endif + + PMSA003ISensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From 1d941019b17a027ecc925cfcb022fd962c4bfaa4 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Tue, 1 Jul 2025 22:17:38 +0200 Subject: [PATCH 02/12] AirQualityTelemetry module not depend on PM sensor presence --- src/modules/Modules.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 3528f57f5..2ff5a345a 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -221,11 +221,7 @@ void setupModules() // TODO: How to improve this? #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new EnvironmentTelemetryModule(); -#if __has_include("Adafruit_PM25AQI.h") - if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { - new AirQualityTelemetryModule(); - } -#endif + new AirQualityTelemetryModule(); #if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 || nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) { From a1c6fdb5a0e43a3185afe960742ad73ccb92732d Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 2 Jul 2025 11:29:02 +0200 Subject: [PATCH 03/12] Remove commented line --- src/modules/Telemetry/AirQualityTelemetry.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 8b7ab1b24..75058f849 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -28,7 +28,6 @@ int32_t AirQualityTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { sleepOnNextExecution = false; - // uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, default_telemetry_broadcast_interval_secs); LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs); @@ -222,7 +221,6 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } -// CHECKED bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { bool valid = true; From a10e3168939c7722c969f612fe4afa46383132bf Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 2 Jul 2025 13:12:07 +0200 Subject: [PATCH 04/12] Fixes on PMS class --- src/modules/Telemetry/AirQualityTelemetry.cpp | 2 +- src/modules/Telemetry/Sensor/PMSA003ISensor.cpp | 13 ++++++++++--- src/modules/Telemetry/Sensor/PMSA003ISensor.h | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 75058f849..7689802ea 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -74,7 +74,7 @@ int32_t AirQualityTelemetryModule::runOnce() // Wake up the sensors that need it #ifdef PMSA003I_ENABLE_PIN - if (pmsa003iSensor.hasSensor() && pmsa003iSensor.state == pmsa003iSensor::State::IDLE) + if (pmsa003iSensor.hasSensor() && !pmsa003iSensor.isActive()) return pmsa003iSensor.wakeUp(); #endif /* PMSA003I_ENABLE_PIN */ diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index dacdf5ff4..8567d7e70 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -48,24 +48,31 @@ int32_t PMSA003ISensor::runOnce() void PMSA003ISensor::setup() { +#ifdef PMSA003I_ENABLE_PIN + pinMode(PMSA003I_ENABLE_PIN, OUTPUT); +#endif /* PMSA003I_ENABLE_PIN */ } #ifdef PMSA003I_ENABLE_PIN -void sleep() { +void PMSA003ISensor::sleep() { digitalWrite(PMSA003I_ENABLE_PIN, LOW); state = State::IDLE; } -uint32_t wakeUp() { +uint32_t PMSA003ISensor::wakeUp() { digitalWrite(PMSA003I_ENABLE_PIN, HIGH); state = State::ACTIVE; } #endif /* PMSA003I_ENABLE_PIN */ +bool PMSA003ISensor::isActive() { + return state == State::ACTIVE; +} + bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) { if (!pmsa003i.read(&pmsa003iData)) { - LOG_WARN("Skip send measurements. Could not read AQIn"); + LOG_WARN("Skip send measurements. Could not read AQI"); return false; } diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h index 01b04368e..db7c9aaa9 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.h +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -38,13 +38,13 @@ class PMSA003ISensor : public TelemetrySensor // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking // a reading // put the sensor to sleep on startup - pinMode(PMSA003I_ENABLE_PIN, OUTPUT); State state = State::IDLE; #else State state = State::ACTIVE; #endif PMSA003ISensor(); + bool isActive(); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; From 3b317f01c3c41b85783ab06960e1030ba3341984 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 2 Jul 2025 14:41:44 +0200 Subject: [PATCH 05/12] Add missing warmup period to wakeUp function --- src/modules/Telemetry/Sensor/PMSA003ISensor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 8567d7e70..67f00574e 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -62,6 +62,7 @@ void PMSA003ISensor::sleep() { uint32_t PMSA003ISensor::wakeUp() { digitalWrite(PMSA003I_ENABLE_PIN, HIGH); state = State::ACTIVE; + return PMSA003I_WARMUP_MS } #endif /* PMSA003I_ENABLE_PIN */ From a6878b6cd2d05327a309d7789b762fbefaa7f721 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Sun, 6 Jul 2025 08:31:57 +0200 Subject: [PATCH 06/12] Fixes on compilation for different variants --- src/modules/Telemetry/Sensor/PMSA003ISensor.cpp | 2 +- src/modules/Telemetry/Sensor/PMSA003ISensor.h | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 67f00574e..2b165cd6d 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -62,7 +62,7 @@ void PMSA003ISensor::sleep() { uint32_t PMSA003ISensor::wakeUp() { digitalWrite(PMSA003I_ENABLE_PIN, HIGH); state = State::ACTIVE; - return PMSA003I_WARMUP_MS + return PMSA003I_WARMUP_MS; } #endif /* PMSA003I_ENABLE_PIN */ diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h index db7c9aaa9..7e460ce33 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.h +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -20,11 +20,6 @@ class PMSA003ISensor : public TelemetrySensor Adafruit_PM25AQI pmsa003i = Adafruit_PM25AQI(); PM25_AQI_Data pmsa003iData = {0}; -#ifdef PMSA003I_ENABLE_PIN - void sleep(); - uint32_t wakeUp(); -#endif - protected: virtual void setup() override; @@ -35,6 +30,8 @@ class PMSA003ISensor : public TelemetrySensor }; #ifdef PMSA003I_ENABLE_PIN + void sleep(); + uint32_t wakeUp(); // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking // a reading // put the sensor to sleep on startup From 547767ca68fc435b84a61634907d5496829028c1 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Fri, 11 Jul 2025 14:39:26 +0200 Subject: [PATCH 07/12] Add functions to check for I2C bus speed and set it --- src/detect/ScanI2CTwoWire.cpp | 69 +++++++++++++++++++++++++++++++++++ src/detect/ScanI2CTwoWire.h | 3 ++ src/main.cpp | 21 +++++++++++ 3 files changed, 93 insertions(+) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 9e9441123..5734fc200 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -109,6 +109,75 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation return value; } +bool ScanI2CTwoWire::setClockSpeed(I2CPort port, uint32_t speed) { + + DeviceAddress addr(port, 0x00); + TwoWire *i2cBus; + +#if WIRE_INTERFACES_COUNT == 2 + if (port == I2CPort::WIRE1) { + i2cBus = &Wire1; + } else { +#endif + i2cBus = &Wire; +#if WIRE_INTERFACES_COUNT == 2 + } +#endif + + return i2cBus->setClock(speed); +} + +uint32_t ScanI2CTwoWire::getClockSpeed(I2CPort port) { + + DeviceAddress addr(port, 0x00); + TwoWire *i2cBus; + +#if WIRE_INTERFACES_COUNT == 2 + if (port == I2CPort::WIRE1) { + i2cBus = &Wire1; + } else { +#endif + i2cBus = &Wire; +#if WIRE_INTERFACES_COUNT == 2 + } +#endif + + return i2cBus->getClock(); +} + +/// for SEN5X detection +String readSEN5xProductName(TwoWire* i2cBus, uint8_t address) { + uint8_t cmd[] = { 0xD0, 0x14 }; + uint8_t response[48] = {0}; + + i2cBus->beginTransmission(address); + i2cBus->write(cmd, 2); + if (i2cBus->endTransmission() != 0) return ""; + + delay(20); + if (i2cBus->requestFrom(address, (uint8_t)48) != 48) return ""; + + for (int i = 0; i < 48 && i2cBus->available(); ++i) { + response[i] = i2cBus->read(); + } + + char productName[33] = {0}; + int j = 0; + for (int i = 0; i < 48 && j < 32; i += 3) { + if (response[i] >= 32 && response[i] <= 126) + productName[j++] = response[i]; + else + break; + + if (response[i + 1] >= 32 && response[i + 1] <= 126) + productName[j++] = response[i + 1]; + else + break; + } + + return String(productName); +} + #define SCAN_SIMPLE_CASE(ADDR, T, ...) \ case ADDR: \ logFoundDevice(__VA_ARGS__); \ diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index 6988091ad..28b073a17 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -29,6 +29,9 @@ class ScanI2CTwoWire : public ScanI2C size_t countDevices() const override; + bool setClockSpeed(ScanI2C::I2CPort, uint32_t); + uint32_t getClockSpeed(ScanI2C::I2CPort); + protected: FoundDevice firstOfOrNONE(size_t, DeviceType[]) const override; diff --git a/src/main.cpp b/src/main.cpp index 2e2adfd46..9f3e4fe6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -472,6 +472,7 @@ void setup() Wire.setSCL(I2C_SCL); Wire.begin(); #elif defined(I2C_SDA) && !defined(ARCH_RP2040) + LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL); Wire.begin(I2C_SDA, I2C_SCL); #elif defined(ARCH_PORTDUINO) if (settingsStrings[i2cdev] != "") { @@ -530,6 +531,26 @@ void setup() i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); #endif +#ifdef I2C_CLOCK_SPEED + uint32_t currentClock; + currentClock = i2cScanner->getClockSpeed(ScanI2C::I2CPort::WIRE); + LOG_INFO("Clock speed: %uHz on WIRE", currentClock); + LOG_DEBUG("Setting Wire with defined clock speed, %uHz...", I2C_CLOCK_SPEED); + if(!i2cScanner->setClockSpeed(ScanI2C::I2CPort::WIRE, I2C_CLOCK_SPEED)) { + LOG_ERROR("Unable to set clock speed on WIRE"); + } else { + + currentClock = i2cScanner->getClockSpeed(ScanI2C::I2CPort::WIRE); + LOG_INFO("Set clock speed: %uHz on WIRE", currentClock); + } + // LOG_DEBUG("Starting Wire with defined clock speed, %d...", I2C_CLOCK_SPEED); + // if(!i2cScanner->setClockSpeed(ScanI2C::I2CPort::WIRE1, I2C_CLOCK_SPEED)) { + // LOG_ERROR("Unable to set clock speed on WIRE1"); + // } else { + // LOG_INFO("Set clock speed: %d on WIRE1", I2C_CLOCK_SPEED); + // } +#endif + auto i2cCount = i2cScanner->countDevices(); if (i2cCount == 0) { LOG_INFO("No I2C devices found"); From 49b32debe42cedb615a05b0a1ba93a719f175f04 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 23 Jun 2025 15:35:12 +0200 Subject: [PATCH 08/12] Initial integration of ADS1X15 ADC --- platformio.ini | 2 + src/configuration.h | 4 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 22 +++- src/main.cpp | 1 + src/modules/Telemetry/PowerTelemetry.cpp | 16 ++- .../Telemetry/Sensor/ADS1X15Sensor.cpp | 111 ++++++++++++++++++ src/modules/Telemetry/Sensor/ADS1X15Sensor.h | 24 ++++ 8 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/ADS1X15Sensor.h diff --git a/platformio.ini b/platformio.ini index c0eb6fedb..c01a41b76 100644 --- a/platformio.ini +++ b/platformio.ini @@ -202,3 +202,5 @@ lib_deps = sensirion/Sensirion Core@0.7.1 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 + # renovate: datasource=custom.pio depName=Adafruit ADS1X15 packageName=adafruit/library/Adafruit ADS1X15 Library + adafruit/Adafruit ADS1X15@2.5.0 diff --git a/src/configuration.h b/src/configuration.h index cddc7ba7a..92df118d7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -195,6 +195,10 @@ along with this program. If not, see . #define LTR390UV_ADDR 0x53 #define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418 #define PCT2075_ADDR 0x37 +#define ADS1X15_ADDR 0x48 // same address as FT6336U +#define ADS1X15_ADDR_ALT1 0x49 +#define ADS1X15_ADDR_ALT2 0x4A +#define ADS1X15_ADDR_ALT3 0x4B // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dd290db98..232c43c34 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -75,6 +75,7 @@ class ScanI2C TCA8418KB, PCT2075, BMM150, + ADS1X15, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 5734fc200..6c8851fc7 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -564,7 +564,16 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - case 0x48: { + case 0x48: { // same as ADS1X15 main address + + // ADS1X15 default config register is 8583h + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); + if (registerValue == 0x8583) { + type = ADS1X15; + logFoundDevice("ADS1X15", (uint8_t)addr.address); + break; + } + i2cBus->beginTransmission(addr.address); uint8_t getInfo[] = {0x5A, 0xC0, 0x00, 0xFF, 0xFC}; uint8_t expectedInfo[] = {0xa5, 0xE0, 0x00, 0x3F, 0x19}; @@ -584,6 +593,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; } + case ADS1X15_ADDR_ALT1: + case ADS1X15_ADDR_ALT2: + case ADS1X15_ADDR_ALT3: + // ADS1X15 default config register is 8583h + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); + if (registerValue == 0x8583) { + type = ADS1X15; + logFoundDevice("ADS1X15", (uint8_t)addr.address); + break; + } + default: LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address); } diff --git a/src/main.cpp b/src/main.cpp index 9f3e4fe6a..be64f2a64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -703,6 +703,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ADS1X15, meshtastic_TelemetrySensorType_ADS1X15); i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index a92013d01..435859c91 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -22,6 +22,13 @@ #include "graphics/ScreenFonts.h" #include +#if __has_include() +#include "Sensor/ADS1X15Sensor.h" +ADS1X15Sensor ads1x15Sensor; +#else +NullSensor ads1x15Sensor; +#endif + namespace graphics { extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only); @@ -74,6 +81,8 @@ int32_t PowerTelemetryModule::runOnce() result = ina3221Sensor.isInitialized() ? 0 : ina3221Sensor.runOnce(); if (max17048Sensor.hasSensor()) result = max17048Sensor.isInitialized() ? 0 : max17048Sensor.runOnce(); + if (ads1x15Sensor.hasSensor()) + result = ads1x15Sensor.isInitialized() ? 0 : ads1x15Sensor.runOnce(); } // it's possible to have this module enabled, only for displaying values on the screen. @@ -205,6 +214,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) valid = ina3221Sensor.getMetrics(m); if (max17048Sensor.hasSensor()) valid = max17048Sensor.getMetrics(m); + if (ads1x15Sensor.hasSensor()) + valid = ads1x15Sensor.getMetrics(m); #endif return valid; @@ -246,9 +257,10 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); if (getPowerTelemetry(&m)) { LOG_INFO("Send: ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " - "ch3_voltage=%f, ch3_current=%f", + "ch3_voltage=%f, ch3_current=%f, ch4_voltage=%f", 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); + m.variant.power_metrics.ch2_current, m.variant.power_metrics.ch3_voltage, m.variant.power_metrics.ch3_current, + m.variant.power_metrics.ch4_voltage); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp new file mode 100644 index 000000000..deaf22619 --- /dev/null +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp @@ -0,0 +1,111 @@ +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "ADS1X15Sensor.h" +#include "TelemetrySensor.h" +#include + +ADS1X15Sensor::ADS1X15Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_ADS1X15, "ADS1X15") {} + +int32_t ADS1X15Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + status = ads1x15.begin(nodeTelemetrySensorsMap[sensorType].first); + + return initI2CSensor(); +} + +void ADS1X15Sensor::setup() {} + +struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(ads1x15_ch_t ch) +{ + struct _ADS1X15Measurement measurement; + + // Reset gain + ads1x15.setGain(GAIN_TWOTHIRDS); + double voltage_range = 6.144; + + // Get value with full range + uint16_t value = ads.readADC_SingleEnded(ch); + + // Dynamic gain, to increase resolution of low voltage values + // If value is under 4.096v increase the gain depending on voltage + if (value < 21845) { + if (value > 10922) { + + // 1x gain, 4.096V + ads1x15.setGain(GAIN_ONE); + voltage_range = 4.096; + + } else if (value > 5461) { + + // 2x gain, 2.048V + ads1x15.setGain(GAIN_TWO); + voltage_range = 2.048; + + } else if (value > 2730) { + + // 4x gain, 1.024V + ads1x15.setGain(GAIN_FOUR); + voltage_range = 1.024; + + } else if (value > 1365) { + + // 8x gain, 0.25V + ads1x15.setGain(GAIN_EIGHT); + voltage_range = 0.512; + + } else { + + // 16x gain, 0.125V + ads1x15.setGain(GAIN_SIXTEEN); + voltage_range = 0.256; + } + + // Get the value again + value = ads1x15.readADC_SingleEnded(ch); + } + + reading = (float)value / 32768 * voltage_range; + measurement.voltage = reading; + + return measurement; +} + +struct _ADS1X15Measurements ADS1X15Sensor::getMeasurements() +{ + struct _ADS1X15Measurements measurements; + + // ADS1X15 has 4 channels starting from 0 + for (int i = 0; i < 4; i++) { + measurements.measurements[i] = getMeasurement((ads1x15_ch_t)i); + } + + return measurements; +} + +bool ADS1X15Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + + struct _ADS1X15Measurements m = getMeasurements(); + + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.has_ch2_voltage = true; + measurement->variant.power_metrics.has_ch3_voltage = true; + measurement->variant.power_metrics.has_ch4_voltage = true; + + measurement->variant.power_metrics.ch1_voltage = m.measurements[0].voltage; + measurement->variant.power_metrics.ch2_voltage = m.measurements[1].voltage; + measurement->variant.power_metrics.ch3_voltage = m.measurements[2].voltage; + measurement->variant.power_metrics.ch4_voltage = m.measurements[3].voltage; + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h new file mode 100644 index 000000000..ca3422736 --- /dev/null +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h @@ -0,0 +1,24 @@ +#include "configuration.h" + +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "VoltageSensor.h" +#include + +class ADS1X15Sensor : public TelemetrySensor, VoltageSensor +{ + private: + Adafruit_ADS1015 ads1x15; + + protected: + virtual void setup() override; + + public: + ADS1X15Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file From cf39519c83cdc1cda1d2d48d450f0062150458f7 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 25 Jun 2025 11:04:54 +0200 Subject: [PATCH 09/12] Fixes on imports of Adafruit library, and ADS1x15 integration --- src/modules/Telemetry/PowerTelemetry.cpp | 18 +++++++++++--- .../Telemetry/Sensor/ADS1X15Sensor.cpp | 13 +++++----- src/modules/Telemetry/Sensor/ADS1X15Sensor.h | 24 +++++++++++++++---- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 435859c91..025e6b858 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -22,7 +22,15 @@ #include "graphics/ScreenFonts.h" #include -#if __has_include() +namespace graphics +{ +extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr); +} + + +#include "Sensor/nullSensor.h" + +#if __has_include() #include "Sensor/ADS1X15Sensor.h" ADS1X15Sensor ads1x15Sensor; #else @@ -49,8 +57,8 @@ int32_t PowerTelemetryModule::runOnce() 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_measurement_enabled = 1; + moduleConfig.telemetry.power_screen_enabled = 1; // moduleConfig.telemetry.power_update_interval = 45; if (!(moduleConfig.telemetry.power_measurement_enabled)) { @@ -83,6 +91,9 @@ int32_t PowerTelemetryModule::runOnce() result = max17048Sensor.isInitialized() ? 0 : max17048Sensor.runOnce(); if (ads1x15Sensor.hasSensor()) result = ads1x15Sensor.isInitialized() ? 0 : ads1x15Sensor.runOnce(); + if (!ads1x15Sensor.hasSensor()) { + LOG_INFO("ADS1X15 not found"); + } } // it's possible to have this module enabled, only for displaying values on the screen. @@ -215,6 +226,7 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) if (max17048Sensor.hasSensor()) valid = max17048Sensor.getMetrics(m); if (ads1x15Sensor.hasSensor()) + LOG_INFO("Getting ADS1X15 sensor"); valid = ads1x15Sensor.getMetrics(m); #endif diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp index deaf22619..b51a574d9 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp @@ -1,11 +1,11 @@ #include "configuration.h" -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "ADS1X15Sensor.h" #include "TelemetrySensor.h" -#include +#include ADS1X15Sensor::ADS1X15Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_ADS1X15, "ADS1X15") {} @@ -23,7 +23,7 @@ int32_t ADS1X15Sensor::runOnce() void ADS1X15Sensor::setup() {} -struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(ads1x15_ch_t ch) +struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(uint8_t ch) { struct _ADS1X15Measurement measurement; @@ -32,7 +32,7 @@ struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(ads1x15_ch_t ch) double voltage_range = 6.144; // Get value with full range - uint16_t value = ads.readADC_SingleEnded(ch); + uint16_t value = ads1x15.readADC_SingleEnded(ch); // Dynamic gain, to increase resolution of low voltage values // If value is under 4.096v increase the gain depending on voltage @@ -72,8 +72,7 @@ struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(ads1x15_ch_t ch) value = ads1x15.readADC_SingleEnded(ch); } - reading = (float)value / 32768 * voltage_range; - measurement.voltage = reading; + measurement.voltage = (float)value / 32768 * voltage_range; return measurement; } @@ -84,7 +83,7 @@ struct _ADS1X15Measurements ADS1X15Sensor::getMeasurements() // ADS1X15 has 4 channels starting from 0 for (int i = 0; i < 4; i++) { - measurements.measurements[i] = getMeasurement((ads1x15_ch_t)i); + measurements.measurements[i] = getMeasurement(i); } return measurements; diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h index ca3422736..469dab945 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h @@ -1,16 +1,21 @@ #include "configuration.h" -#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" -#include "VoltageSensor.h" -#include +#include -class ADS1X15Sensor : public TelemetrySensor, VoltageSensor +class ADS1X15Sensor : public TelemetrySensor { private: - Adafruit_ADS1015 ads1x15; + Adafruit_ADS1X15 ads1x15; + + // get a single measurement for a channel + struct _ADS1X15Measurement getMeasurement(uint8_t ch); + + // get all measurements for all channels + struct _ADS1X15Measurements getMeasurements(); protected: virtual void setup() override; @@ -21,4 +26,13 @@ class ADS1X15Sensor : public TelemetrySensor, VoltageSensor virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; +struct _ADS1X15Measurement { + float voltage; +}; + +struct _ADS1X15Measurements { + // ADS1X15 has 4 channels + struct _ADS1X15Measurement measurements[4]; +}; + #endif \ No newline at end of file From 1f424a7f823c6b8ef15a4a63db7a87b412ad9136 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Sat, 28 Jun 2025 16:22:33 +0200 Subject: [PATCH 10/12] Add additional ADS1X15 to support daisy chaining --- src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 2 +- src/main.cpp | 2 +- src/modules/Telemetry/PowerTelemetry.cpp | 15 +++--- .../Telemetry/Sensor/ADS1X15Sensor.cpp | 50 +++++++++++++++---- src/modules/Telemetry/Sensor/ADS1X15Sensor.h | 2 +- 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 232c43c34..16383bbd1 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -76,6 +76,7 @@ class ScanI2C PCT2075, BMM150, ADS1X15, + ADS1X15_ALT, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 6c8851fc7..441743a90 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -599,7 +599,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) // ADS1X15 default config register is 8583h registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); if (registerValue == 0x8583) { - type = ADS1X15; + type = ADS1X15_ALT; logFoundDevice("ADS1X15", (uint8_t)addr.address); break; } diff --git a/src/main.cpp b/src/main.cpp index be64f2a64..54baae0db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -704,7 +704,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ADS1X15, meshtastic_TelemetrySensorType_ADS1X15); - + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ADS1X15_ALT, meshtastic_TelemetrySensorType_ADS1X15_ALT); i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 025e6b858..5dcb794fc 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -33,8 +33,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #if __has_include() #include "Sensor/ADS1X15Sensor.h" ADS1X15Sensor ads1x15Sensor; +ADS1X15Sensor ads1x15Sensor_alt(meshtastic_TelemetrySensorType_ADS1X15_ALT); #else NullSensor ads1x15Sensor; +NullSensor ads1x15Sensor_alt; #endif namespace graphics @@ -57,8 +59,8 @@ int32_t PowerTelemetryModule::runOnce() 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_measurement_enabled = 1; + // moduleConfig.telemetry.power_screen_enabled = 1; // moduleConfig.telemetry.power_update_interval = 45; if (!(moduleConfig.telemetry.power_measurement_enabled)) { @@ -91,9 +93,8 @@ int32_t PowerTelemetryModule::runOnce() result = max17048Sensor.isInitialized() ? 0 : max17048Sensor.runOnce(); if (ads1x15Sensor.hasSensor()) result = ads1x15Sensor.isInitialized() ? 0 : ads1x15Sensor.runOnce(); - if (!ads1x15Sensor.hasSensor()) { - LOG_INFO("ADS1X15 not found"); - } + if (ads1x15Sensor_alt.hasSensor()) + result = ads1x15Sensor_alt.isInitialized() ? 0 : ads1x15Sensor_alt.runOnce(); } // it's possible to have this module enabled, only for displaying values on the screen. @@ -226,8 +227,9 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) if (max17048Sensor.hasSensor()) valid = max17048Sensor.getMetrics(m); if (ads1x15Sensor.hasSensor()) - LOG_INFO("Getting ADS1X15 sensor"); valid = ads1x15Sensor.getMetrics(m); + if (ads1x15Sensor_alt.hasSensor()) + valid = ads1x15Sensor_alt.getMetrics(m); #endif return valid; @@ -268,6 +270,7 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.which_variant = meshtastic_Telemetry_power_metrics_tag; m.time = getTime(); if (getPowerTelemetry(&m)) { + // TODO - Consider adding all 8 channels here - seems a bit much? LOG_INFO("Send: ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " "ch3_voltage=%f, ch3_current=%f, ch4_voltage=%f", m.variant.power_metrics.ch1_voltage, m.variant.power_metrics.ch1_current, m.variant.power_metrics.ch2_voltage, diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp index b51a574d9..d196e7256 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp @@ -7,7 +7,7 @@ #include "TelemetrySensor.h" #include -ADS1X15Sensor::ADS1X15Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_ADS1X15, "ADS1X15") {} +ADS1X15Sensor::ADS1X15Sensor(meshtastic_TelemetrySensorType sensorType) : TelemetrySensor(sensorType, "ADS1X15") {} int32_t ADS1X15Sensor::runOnce() { @@ -94,16 +94,48 @@ bool ADS1X15Sensor::getMetrics(meshtastic_Telemetry *measurement) struct _ADS1X15Measurements m = getMeasurements(); - measurement->variant.power_metrics.has_ch1_voltage = true; - measurement->variant.power_metrics.has_ch2_voltage = true; - measurement->variant.power_metrics.has_ch3_voltage = true; - measurement->variant.power_metrics.has_ch4_voltage = true; + switch (sensorType) + { + case meshtastic_TelemetrySensorType_ADS1X15: + { + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.has_ch2_voltage = true; + measurement->variant.power_metrics.has_ch3_voltage = true; + measurement->variant.power_metrics.has_ch4_voltage = true; - measurement->variant.power_metrics.ch1_voltage = m.measurements[0].voltage; - measurement->variant.power_metrics.ch2_voltage = m.measurements[1].voltage; - measurement->variant.power_metrics.ch3_voltage = m.measurements[2].voltage; - measurement->variant.power_metrics.ch4_voltage = m.measurements[3].voltage; + measurement->variant.power_metrics.ch1_voltage = m.measurements[0].voltage; + measurement->variant.power_metrics.ch2_voltage = m.measurements[1].voltage; + measurement->variant.power_metrics.ch3_voltage = m.measurements[2].voltage; + measurement->variant.power_metrics.ch4_voltage = m.measurements[3].voltage; + break; + } + case meshtastic_TelemetrySensorType_ADS1X15_ALT: + { + measurement->variant.power_metrics.has_ch5_voltage = true; + measurement->variant.power_metrics.has_ch6_voltage = true; + measurement->variant.power_metrics.has_ch7_voltage = true; + measurement->variant.power_metrics.has_ch8_voltage = true; + measurement->variant.power_metrics.ch5_voltage = m.measurements[0].voltage; + measurement->variant.power_metrics.ch6_voltage = m.measurements[1].voltage; + measurement->variant.power_metrics.ch7_voltage = m.measurements[2].voltage; + measurement->variant.power_metrics.ch8_voltage = m.measurements[3].voltage; + break; + } + default: + { + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.has_ch2_voltage = true; + measurement->variant.power_metrics.has_ch3_voltage = true; + measurement->variant.power_metrics.has_ch4_voltage = true; + + measurement->variant.power_metrics.ch1_voltage = m.measurements[0].voltage; + measurement->variant.power_metrics.ch2_voltage = m.measurements[1].voltage; + measurement->variant.power_metrics.ch3_voltage = m.measurements[2].voltage; + measurement->variant.power_metrics.ch4_voltage = m.measurements[3].voltage; + break; + } + } return true; } diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h index 469dab945..7ddd3d073 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h @@ -21,7 +21,7 @@ class ADS1X15Sensor : public TelemetrySensor virtual void setup() override; public: - ADS1X15Sensor(); + ADS1X15Sensor(meshtastic_TelemetrySensorType sensorType = meshtastic_TelemetrySensorType_ADS1X15); virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; From 81124cae999cfa3c120553af901b50382fc69b13 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 7 Jul 2025 16:54:15 +0200 Subject: [PATCH 11/12] Fix in screen functions --- src/modules/Telemetry/PowerTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 5dcb794fc..e007a6115 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -24,7 +24,7 @@ namespace graphics { -extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr); +extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only); } From e514052f72dfd7a6f3ea55247d627ac548ca8bb2 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 21 Jul 2025 14:01:09 +0200 Subject: [PATCH 12/12] Changes on bus speed for ADS1X15 This fixes daisy chaining of various sensors together, with a "dirty" I2C. I2C buses with step-ups and with level-shifters with mosfets can have issues at high speeds. --- src/detect/ScanI2CTwoWire.cpp | 2 +- src/modules/Telemetry/PowerTelemetry.cpp | 5 +++ .../Telemetry/Sensor/ADS1X15Sensor.cpp | 37 ++++++++++++++++++- src/modules/Telemetry/Sensor/ADS1X15Sensor.h | 4 ++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 441743a90..63f50b9a1 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -600,7 +600,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2); if (registerValue == 0x8583) { type = ADS1X15_ALT; - logFoundDevice("ADS1X15", (uint8_t)addr.address); + logFoundDevice("ADS1X15_ALT", (uint8_t)addr.address); break; } diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index e007a6115..9200cf2c7 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -276,6 +276,11 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) 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, m.variant.power_metrics.ch4_voltage); + LOG_INFO("Send: ch5_voltage=%f, ch5_current=%f, ch6_voltage=%f, ch6_current=%f, " + "ch7_voltage=%f, ch7_current=%f, ch8_voltage=%f", + m.variant.power_metrics.ch5_voltage, m.variant.power_metrics.ch5_current, m.variant.power_metrics.ch6_voltage, + m.variant.power_metrics.ch6_current, m.variant.power_metrics.ch7_voltage, m.variant.power_metrics.ch7_current, + m.variant.power_metrics.ch8_voltage, m.variant.power_metrics.ch8_current); sensor_read_error_count = 0; diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp index d196e7256..3715b2f9b 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.cpp @@ -16,7 +16,26 @@ int32_t ADS1X15Sensor::runOnce() return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; } - status = ads1x15.begin(nodeTelemetrySensorsMap[sensorType].first); + bus = nodeTelemetrySensorsMap[sensorType].second; + address = (uint8_t)nodeTelemetrySensorsMap[sensorType].first; + +#ifdef ADS1X15_I2C_CLOCK_SPEED + uint32_t currentClock; + currentClock = bus->getClock(); + if (currentClock != ADS1X15_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Changing I2C clock to %u", ADS1X15_I2C_CLOCK_SPEED); + bus->setClock(ADS1X15_I2C_CLOCK_SPEED); + } +#endif + + status = ads1x15.begin(address); + +#ifdef ADS1X15_I2C_CLOCK_SPEED + if (currentClock != ADS1X15_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Restoring I2C clock to %uHz", currentClock); + bus->setClock(currentClock); + } +#endif return initI2CSensor(); } @@ -27,6 +46,15 @@ struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(uint8_t ch) { struct _ADS1X15Measurement measurement; +#ifdef ADS1X15_I2C_CLOCK_SPEED + uint32_t currentClock; + currentClock = bus->getClock(); + if (currentClock != ADS1X15_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Changing I2C clock to %u", ADS1X15_I2C_CLOCK_SPEED); + bus->setClock(ADS1X15_I2C_CLOCK_SPEED); + } +#endif + // Reset gain ads1x15.setGain(GAIN_TWOTHIRDS); double voltage_range = 6.144; @@ -72,6 +100,13 @@ struct _ADS1X15Measurement ADS1X15Sensor::getMeasurement(uint8_t ch) value = ads1x15.readADC_SingleEnded(ch); } +#ifdef ADS1X15_I2C_CLOCK_SPEED + if (currentClock != ADS1X15_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Restoring I2C clock to %uHz", currentClock); + bus->setClock(currentClock); + } +#endif + measurement.voltage = (float)value / 32768 * voltage_range; return measurement; diff --git a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h index 7ddd3d073..309beb922 100644 --- a/src/modules/Telemetry/Sensor/ADS1X15Sensor.h +++ b/src/modules/Telemetry/Sensor/ADS1X15Sensor.h @@ -6,10 +6,14 @@ #include "TelemetrySensor.h" #include +#define ADS1X15_I2C_CLOCK_SPEED 100000 + class ADS1X15Sensor : public TelemetrySensor { private: Adafruit_ADS1X15 ads1x15; + TwoWire * bus; + uint8_t address; // get a single measurement for a channel struct _ADS1X15Measurement getMeasurement(uint8_t ch);