mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
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:
parent
183e2a8471
commit
6a2f0f5143
6 changed files with 294 additions and 0 deletions
|
@ -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
|
||||||
|
|
0
esphome/components/pmsa003i/__init__.py
Normal file
0
esphome/components/pmsa003i/__init__.py
Normal file
100
esphome/components/pmsa003i/pmsa003i.cpp
Normal file
100
esphome/components/pmsa003i/pmsa003i.cpp
Normal 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
|
68
esphome/components/pmsa003i/pmsa003i.h
Normal file
68
esphome/components/pmsa003i/pmsa003i.h
Normal 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
|
104
esphome/components/pmsa003i/sensor.py
Normal file
104
esphome/components/pmsa003i/sensor.py
Normal 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))
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue