diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 1c8e95a82..9bc41bfa5 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -166,11 +166,11 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta std::vector entries; if (m.has_pm10_standard) - entries.push_back("PM1.0: " + String(m.pm10_standard, 0) + "ug/m3"); + entries.push_back("PM1.0: " + String(m.pm10_standard) + "ug/m3"); if (m.has_pm25_standard) - entries.push_back("PM2.5: " + String(m.pm25_standard, 0) + "ug/m3"); + entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3"); if (m.has_pm100_standard) - entries.push_back("PM10.0: " + String(m.pm100_standard, 0) + "ug/m3"); + entries.push_back("PM10.0: " + String(m.pm100_standard) + "ug/m3"); // === Show first available metric on top-right of first line === if (!entries.empty()) { @@ -274,7 +274,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) 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", + LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, pm10_environmental=%u, pm100_environmental=%u", 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); diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 2b165cd6d..39f8269a2 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -1,97 +1,175 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #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") {} +#include -int32_t PMSA003ISensor::runOnce() +PMSA003ISensor::PMSA003ISensor() + : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") { - 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 pinMode(PMSA003I_ENABLE_PIN, OUTPUT); -#endif /* PMSA003I_ENABLE_PIN */ +#endif } -#ifdef PMSA003I_ENABLE_PIN -void PMSA003ISensor::sleep() { - digitalWrite(PMSA003I_ENABLE_PIN, LOW); - state = State::IDLE; +bool PMSA003ISensor::restoreClock(uint32_t currentClock){ +#ifdef PMSA003I_I2C_CLOCK_SPEED + if (currentClock != PMSA003I_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Restoring I2C clock to %uHz", currentClock); + return bus->setClock(currentClock); + } + return true; +#endif } -uint32_t PMSA003ISensor::wakeUp() { - digitalWrite(PMSA003I_ENABLE_PIN, HIGH); - state = State::ACTIVE; - return PMSA003I_WARMUP_MS; -} -#endif /* PMSA003I_ENABLE_PIN */ +int32_t PMSA003ISensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); -bool PMSA003ISensor::isActive() { - return state == State::ACTIVE; + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + bus = nodeTelemetrySensorsMap[sensorType].second; + address = (uint8_t)nodeTelemetrySensorsMap[sensorType].first; + +#ifdef PMSA003I_I2C_CLOCK_SPEED + uint32_t currentClock; + currentClock = bus->getClock(); + if (currentClock != PMSA003I_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Changing I2C clock to %u", PMSA003I_I2C_CLOCK_SPEED); + bus->setClock(PMSA003I_I2C_CLOCK_SPEED); + } +#endif + + bus->beginTransmission(address); + if (bus->endTransmission() != 0) { + LOG_WARN("PMSA003I not found on I2C at 0x12"); + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + restoreClock(currentClock); + + status = 1; + LOG_INFO("PMSA003I Enabled"); + + return initI2CSensor(); } bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) { - if (!pmsa003i.read(&pmsa003iData)) { - LOG_WARN("Skip send measurements. Could not read AQI"); + if(!isActive()){ + LOG_WARN("PMSA003I is not active"); + return false; + } + +#ifdef PMSA003I_I2C_CLOCK_SPEED + uint32_t currentClock; + currentClock = bus->getClock(); + if (currentClock != PMSA003I_I2C_CLOCK_SPEED){ + // LOG_DEBUG("Changing I2C clock to %u", PMSA003I_I2C_CLOCK_SPEED); + bus->setClock(PMSA003I_I2C_CLOCK_SPEED); + } +#endif + + bus->requestFrom(address, PMSA003I_FRAME_LENGTH); + if (bus->available() < PMSA003I_FRAME_LENGTH) { + LOG_WARN("PMSA003I read failed: incomplete data (%d bytes)", bus->available()); + return false; + } + + restoreClock(currentClock); + + for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH; i++) { + buffer[i] = bus->read(); + } + + if (buffer[0] != 0x42 || buffer[1] != 0x4D) { + LOG_WARN("PMSA003I frame header invalid: 0x%02X 0x%02X", buffer[0], buffer[1]); + return false; + } + + auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { + return (data[idx] << 8) | data[idx + 1]; + }; + + computedChecksum = 0; + + for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH - 2; i++) { + computedChecksum += buffer[i]; + } + receivedChecksum = read16(buffer, PMSA003I_FRAME_LENGTH - 2); + + if (computedChecksum != receivedChecksum) { + LOG_WARN("PMSA003I checksum failed: computed 0x%04X, received 0x%04X", computedChecksum, receivedChecksum); 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.pm10_standard = read16(buffer, 4); + measurement->variant.air_quality_metrics.has_pm25_standard = true; - measurement->variant.air_quality_metrics.pm25_standard = pmsa003iData.pm25_standard; + measurement->variant.air_quality_metrics.pm25_standard = read16(buffer, 6); + measurement->variant.air_quality_metrics.has_pm100_standard = true; - measurement->variant.air_quality_metrics.pm100_standard = pmsa003iData.pm100_standard; + measurement->variant.air_quality_metrics.pm100_standard = read16(buffer, 8); 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; + measurement->variant.air_quality_metrics.pm10_environmental = read16(buffer, 10); + measurement->variant.air_quality_metrics.has_pm25_environmental = true; + measurement->variant.air_quality_metrics.pm25_environmental = read16(buffer, 12); + + measurement->variant.air_quality_metrics.has_pm100_environmental = true; + measurement->variant.air_quality_metrics.pm100_environmental = read16(buffer, 14); + + measurement->variant.air_quality_metrics.has_particles_03um = true; + measurement->variant.air_quality_metrics.particles_03um = read16(buffer, 16); + + measurement->variant.air_quality_metrics.has_particles_05um = true; + measurement->variant.air_quality_metrics.particles_05um = read16(buffer, 18); + + measurement->variant.air_quality_metrics.has_particles_10um = true; + measurement->variant.air_quality_metrics.particles_10um = read16(buffer, 20); + + measurement->variant.air_quality_metrics.has_particles_25um = true; + measurement->variant.air_quality_metrics.particles_25um = read16(buffer, 22); + + measurement->variant.air_quality_metrics.has_particles_50um = true; + measurement->variant.air_quality_metrics.particles_50um = read16(buffer, 24); + + measurement->variant.air_quality_metrics.has_particles_100um = true; + measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26); + return true; } -#endif \ No newline at end of file +bool PMSA003ISensor::isActive() +{ + return state == State::ACTIVE; +} + +#ifdef PMSA003I_ENABLE_PIN +void PMSA003ISensor::sleep() +{ + digitalWrite(PMSA003I_ENABLE_PIN, LOW); + state = State::IDLE; +} + +uint32_t PMSA003ISensor::wakeUp() +{ + digitalWrite(PMSA003I_ENABLE_PIN, HIGH); + state = State::ACTIVE; + return PMSA003I_WARMUP_MS; +} +#endif + +#endif diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h index 7e460ce33..d6f12dfbb 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.h +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -1,49 +1,38 @@ -#include "configuration.h" +#pragma once -#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 +#ifndef PMSA003I_I2C_CLOCK_SPEED +#define PMSA003I_I2C_CLOCK_SPEED 100000 +#endif + +#ifndef PMSA003I_ENABLE_PIN +#define PMSA003I_FRAME_LENGTH 32 #endif class PMSA003ISensor : public TelemetrySensor { - private: - Adafruit_PM25AQI pmsa003i = Adafruit_PM25AQI(); - PM25_AQI_Data pmsa003iData = {0}; - - protected: +public: + PMSA003ISensor(); virtual void setup() override; - - public: - enum State { - IDLE = 0, - ACTIVE = 1, - }; + virtual int32_t runOnce() override; + virtual bool restoreClock(uint32_t currentClock); + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool isActive(); #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 - 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; -}; +private: + enum class State { IDLE, ACTIVE }; + State state = State::ACTIVE; + TwoWire * bus; + uint8_t address; -#endif \ No newline at end of file + uint16_t computedChecksum = 0; + uint16_t receivedChecksum = 0; + + uint8_t buffer[PMSA003I_FRAME_LENGTH]; +};