mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-03 12:20:43 +00:00
PMSA003I 1st round test
This commit is contained in:
parent
ff8691dc13
commit
0dda175d97
@ -166,11 +166,11 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
std::vector<String> 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);
|
||||
|
@ -1,97 +1,175 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_PM25AQI.h>)
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "PMSA003ISensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <Adafruit_PM25AQI.h>
|
||||
|
||||
PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {}
|
||||
#include <Wire.h>
|
||||
|
||||
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<ScanI2CTwoWire>(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
|
||||
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
|
||||
|
@ -1,49 +1,38 @@
|
||||
#include "configuration.h"
|
||||
#pragma once
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_PM25AQI.h>)
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <Adafruit_PM25AQI.h>
|
||||
|
||||
#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
|
||||
uint16_t computedChecksum = 0;
|
||||
uint16_t receivedChecksum = 0;
|
||||
|
||||
uint8_t buffer[PMSA003I_FRAME_LENGTH];
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user