mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +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/output/* @esphome/core
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
esphome/components/pn532_i2c/* @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'
|
||||
address: 0x77
|
||||
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
|
||||
name: 'Pulse Counter'
|
||||
pin: GPIO12
|
||||
|
|
Loading…
Reference in a new issue