Add support for PMSA003i (#1501)

Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: steve <steve@Hackintosh.local>
Co-authored-by: Otto winter <otto@otto-winter.com>
This commit is contained in:
Stephen Tierney 2021-08-10 19:00:16 +10:00 committed by GitHub
parent 183e2a8471
commit 6a2f0f5143
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 294 additions and 0 deletions

View file

@ -86,6 +86,7 @@ esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter esphome/components/pid/* @OttoWinter
esphome/components/pmsa003i/* @sjtrny
esphome/components/pn532/* @OttoWinter @jesserockz esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz esphome/components/pn532_i2c/* @OttoWinter @jesserockz
esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/pn532_spi/* @OttoWinter @jesserockz

View file

View file

@ -0,0 +1,100 @@
#include "pmsa003i.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pmsa003i {
static const char *const TAG = "pmsa003i";
void PMSA003IComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up pmsa003i...");
PM25AQIData data;
bool successful_read = this->read_data_(&data);
if (!successful_read) {
this->mark_failed();
return;
}
}
void PMSA003IComponent::dump_config() { LOG_I2C_DEVICE(this); }
void PMSA003IComponent::update() {
PM25AQIData data;
bool successful_read = this->read_data_(&data);
// Update sensors
if (successful_read) {
this->status_clear_warning();
ESP_LOGV(TAG, "Read success. Updating sensors.");
if (this->standard_units_) {
if (this->pm_1_0_sensor_ != nullptr)
this->pm_1_0_sensor_->publish_state(data.pm10_standard);
if (this->pm_2_5_sensor_ != nullptr)
this->pm_2_5_sensor_->publish_state(data.pm25_standard);
if (this->pm_10_0_sensor_ != nullptr)
this->pm_10_0_sensor_->publish_state(data.pm100_standard);
} else {
if (this->pm_1_0_sensor_ != nullptr)
this->pm_1_0_sensor_->publish_state(data.pm10_env);
if (this->pm_2_5_sensor_ != nullptr)
this->pm_2_5_sensor_->publish_state(data.pm25_env);
if (this->pm_10_0_sensor_ != nullptr)
this->pm_10_0_sensor_->publish_state(data.pm100_env);
}
if (this->pmc_0_3_sensor_ != nullptr)
this->pmc_0_3_sensor_->publish_state(data.particles_03um);
if (this->pmc_0_5_sensor_ != nullptr)
this->pmc_0_5_sensor_->publish_state(data.particles_05um);
if (this->pmc_1_0_sensor_ != nullptr)
this->pmc_1_0_sensor_->publish_state(data.particles_10um);
if (this->pmc_2_5_sensor_ != nullptr)
this->pmc_2_5_sensor_->publish_state(data.particles_25um);
if (this->pmc_5_0_sensor_ != nullptr)
this->pmc_5_0_sensor_->publish_state(data.particles_50um);
if (this->pmc_10_0_sensor_ != nullptr)
this->pmc_10_0_sensor_->publish_state(data.particles_100um);
} else {
this->status_set_warning();
ESP_LOGV(TAG, "Read failure. Skipping update.");
}
}
bool PMSA003IComponent::read_data_(PM25AQIData *data) {
const uint8_t num_bytes = 32;
uint8_t buffer[num_bytes];
this->read_bytes_raw(buffer, num_bytes);
// https://github.com/adafruit/Adafruit_PM25AQI
// Check that start byte is correct!
if (buffer[0] != 0x42) {
return false;
}
// get checksum ready
int16_t sum = 0;
for (uint8_t i = 0; i < 30; i++) {
sum += buffer[i];
}
// The data comes in endian'd, this solves it so it works on all platforms
uint16_t buffer_u16[15];
for (uint8_t i = 0; i < 15; i++) {
buffer_u16[i] = buffer[2 + i * 2 + 1];
buffer_u16[i] += (buffer[2 + i * 2] << 8);
}
// put it into a nice struct :)
memcpy((void *) data, (void *) buffer_u16, 30);
return (sum == data->checksum);
}
} // namespace pmsa003i
} // namespace esphome

View file

@ -0,0 +1,68 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace pmsa003i {
/**! Structure holding Plantower's standard packet **/
// From https://github.com/adafruit/Adafruit_PM25AQI
struct PM25AQIData {
uint16_t framelen; ///< How long this data chunk is
uint16_t pm10_standard, ///< Standard PM1.0
pm25_standard, ///< Standard PM2.5
pm100_standard; ///< Standard PM10.0
uint16_t pm10_env, ///< Environmental PM1.0
pm25_env, ///< Environmental PM2.5
pm100_env; ///< Environmental PM10.0
uint16_t particles_03um, ///< 0.3um Particle Count
particles_05um, ///< 0.5um Particle Count
particles_10um, ///< 1.0um Particle Count
particles_25um, ///< 2.5um Particle Count
particles_50um, ///< 5.0um Particle Count
particles_100um; ///< 10.0um Particle Count
uint16_t unused; ///< Unused
uint16_t checksum; ///< Packet checksum
};
class PMSA003IComponent : public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void update() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_standard_units(bool standard_units) { standard_units_ = standard_units; }
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0) { pm_10_0_sensor_ = pm_10_0; }
void set_pmc_0_3_sensor(sensor::Sensor *pmc_0_3) { pmc_0_3_sensor_ = pmc_0_3; }
void set_pmc_0_5_sensor(sensor::Sensor *pmc_0_5) { pmc_0_5_sensor_ = pmc_0_5; }
void set_pmc_1_0_sensor(sensor::Sensor *pmc_1_0) { pmc_1_0_sensor_ = pmc_1_0; }
void set_pmc_2_5_sensor(sensor::Sensor *pmc_2_5) { pmc_2_5_sensor_ = pmc_2_5; }
void set_pmc_5_0_sensor(sensor::Sensor *pmc_5_0) { pmc_5_0_sensor_ = pmc_5_0; }
void set_pmc_10_0_sensor(sensor::Sensor *pmc_10_0) { pmc_10_0_sensor_ = pmc_10_0; }
protected:
bool read_data_(PM25AQIData *data);
bool standard_units_;
sensor::Sensor *pm_1_0_sensor_{nullptr};
sensor::Sensor *pm_2_5_sensor_{nullptr};
sensor::Sensor *pm_10_0_sensor_{nullptr};
sensor::Sensor *pmc_0_3_sensor_{nullptr};
sensor::Sensor *pmc_0_5_sensor_{nullptr};
sensor::Sensor *pmc_1_0_sensor_{nullptr};
sensor::Sensor *pmc_2_5_sensor_{nullptr};
sensor::Sensor *pmc_5_0_sensor_{nullptr};
sensor::Sensor *pmc_10_0_sensor_{nullptr};
};
} // namespace pmsa003i
} // namespace esphome

View file

@ -0,0 +1,104 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_PM_1_0,
CONF_PM_2_5,
CONF_PM_10_0,
CONF_PMC_0_5,
CONF_PMC_1_0,
CONF_PMC_2_5,
CONF_PMC_10_0,
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
ICON_COUNTER,
DEVICE_CLASS_EMPTY,
)
CODEOWNERS = ["@sjtrny"]
DEPENDENCIES = ["i2c"]
pmsa003i_ns = cg.esphome_ns.namespace("pmsa003i")
PMSA003IComponent = pmsa003i_ns.class_(
"PMSA003IComponent", cg.PollingComponent, i2c.I2CDevice
)
CONF_STANDARD_UNITS = "standard_units"
UNIT_COUNTS_PER_100ML = "#/0.1L"
CONF_PMC_0_3 = "pmc_0_3"
CONF_PMC_5_0 = "pmc_5_0"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(PMSA003IComponent),
cv.Optional(CONF_STANDARD_UNITS, default=True): cv.boolean,
cv.Optional(CONF_PM_1_0): sensor.sensor_schema(
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
2,
DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_PM_2_5): sensor.sensor_schema(
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
2,
DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_PM_10_0): sensor.sensor_schema(
UNIT_MICROGRAMS_PER_CUBIC_METER,
ICON_CHEMICAL_WEAPON,
2,
DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_PMC_0_3): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_PMC_0_5): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_PMC_1_0): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_PMC_2_5): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_PMC_5_0): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
cv.Optional(CONF_PMC_10_0): sensor.sensor_schema(
UNIT_COUNTS_PER_100ML, ICON_COUNTER, 0, DEVICE_CLASS_EMPTY
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x12))
)
TYPES = {
CONF_PM_1_0: "set_pm_1_0_sensor",
CONF_PM_2_5: "set_pm_2_5_sensor",
CONF_PM_10_0: "set_pm_10_0_sensor",
CONF_PMC_0_3: "set_pmc_0_3_sensor",
CONF_PMC_0_5: "set_pmc_0_5_sensor",
CONF_PMC_1_0: "set_pmc_1_0_sensor",
CONF_PMC_2_5: "set_pmc_2_5_sensor",
CONF_PMC_5_0: "set_pmc_5_0_sensor",
CONF_PMC_10_0: "set_pmc_10_0_sensor",
}
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
cg.add(var.set_standard_units(config[CONF_STANDARD_UNITS]))
for key, funcName in TYPES.items():
if key in config:
sens = yield sensor.new_sensor(config[key])
cg.add(getattr(var, funcName)(sens))

View file

@ -675,6 +675,27 @@ sensor:
name: 'Outside Pressure' name: 'Outside Pressure'
address: 0x77 address: 0x77
update_interval: 15s update_interval: 15s
- platform: pmsa003i
pm_1_0:
name: "PMSA003i PM1.0"
pm_2_5:
name: "PMSA003i PM2.5"
pm_10_0:
name: "PMSA003i PM10.0"
pmc_0_3:
name: "PMSA003i PMC <0.3µm"
pmc_0_5:
name: "PMSA003i PMC <0.5µm"
pmc_1_0:
name: "PMSA003i PMC <1µm"
pmc_2_5:
name: "PMSA003i PMC <2.5µm"
pmc_5_0:
name: "PMSA003i PMC <5µm"
pmc_10_0:
name: "PMSA003i PMC <10µm"
address: 0x12
standard_units: True
- platform: pulse_counter - platform: pulse_counter
name: 'Pulse Counter' name: 'Pulse Counter'
pin: GPIO12 pin: GPIO12