mirror of
https://github.com/esphome/esphome.git
synced 2025-07-24 12:16:35 +00:00
Add CUBIC PM2005/PM2105 Laser Particle Sensor Module (#8292)
Co-authored-by: Djordje <6750655+DjordjeMandic@users.noreply.github.com>
This commit is contained in:
parent
a7b676231a
commit
248dbd32a5
@ -324,6 +324,7 @@ esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
esphome/components/pm2005/* @andrewjswan
|
||||
esphome/components/pmsa003i/* @sjtrny
|
||||
esphome/components/pmwcs3/* @SeByDocKy
|
||||
esphome/components/pn532/* @OttoWinter @jesserockz
|
||||
|
1
esphome/components/pm2005/__init__.py
Normal file
1
esphome/components/pm2005/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""PM2005/2105 component for ESPHome."""
|
123
esphome/components/pm2005/pm2005.cpp
Normal file
123
esphome/components/pm2005/pm2005.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "pm2005.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pm2005 {
|
||||
|
||||
static const char *const TAG = "pm2005";
|
||||
|
||||
// Converts a sensor situation to a human readable string
|
||||
static const LogString *pm2005_get_situation_string(int status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return LOG_STR("Close");
|
||||
case 2:
|
||||
return LOG_STR("Malfunction");
|
||||
case 3:
|
||||
return LOG_STR("Under detecting");
|
||||
case 0x80:
|
||||
return LOG_STR("Detecting completed");
|
||||
default:
|
||||
return LOG_STR("Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a sensor measuring mode to a human readable string
|
||||
static const LogString *pm2005_get_measuring_mode_string(int status) {
|
||||
switch (status) {
|
||||
case 2:
|
||||
return LOG_STR("Single");
|
||||
case 3:
|
||||
return LOG_STR("Continuous");
|
||||
case 5:
|
||||
return LOG_STR("Dynamic");
|
||||
default:
|
||||
return LOG_STR("Timing");
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t get_sensor_value(const uint8_t *data, uint8_t i) { return data[i] * 0x100 + data[i + 1]; }
|
||||
|
||||
void PM2005Component::setup() {
|
||||
if (this->sensor_type_ == PM2005) {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PM2005...");
|
||||
|
||||
this->situation_value_index_ = 3;
|
||||
this->pm_1_0_value_index_ = 4;
|
||||
this->pm_2_5_value_index_ = 6;
|
||||
this->pm_10_0_value_index_ = 8;
|
||||
this->measuring_value_index_ = 10;
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PM2105...");
|
||||
|
||||
this->situation_value_index_ = 2;
|
||||
this->pm_1_0_value_index_ = 3;
|
||||
this->pm_2_5_value_index_ = 5;
|
||||
this->pm_10_0_value_index_ = 7;
|
||||
this->measuring_value_index_ = 9;
|
||||
}
|
||||
|
||||
if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PM2005Component::update() {
|
||||
if (this->read(this->data_buffer_, 12) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read result failed.");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->sensor_situation_ == this->data_buffer_[this->situation_value_index_]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->sensor_situation_ = this->data_buffer_[this->situation_value_index_];
|
||||
ESP_LOGD(TAG, "Sensor situation: %s.", LOG_STR_ARG(pm2005_get_situation_string(this->sensor_situation_)));
|
||||
if (this->sensor_situation_ == 2) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (this->sensor_situation_ != 0x80) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t pm1 = get_sensor_value(this->data_buffer_, this->pm_1_0_value_index_);
|
||||
uint16_t pm25 = get_sensor_value(this->data_buffer_, this->pm_2_5_value_index_);
|
||||
uint16_t pm10 = get_sensor_value(this->data_buffer_, this->pm_10_0_value_index_);
|
||||
uint16_t sensor_measuring_mode = get_sensor_value(this->data_buffer_, this->measuring_value_index_);
|
||||
ESP_LOGD(TAG, "PM1.0: %d, PM2.5: %d, PM10: %d, Measuring mode: %s.", pm1, pm25, pm10,
|
||||
LOG_STR_ARG(pm2005_get_measuring_mode_string(sensor_measuring_mode)));
|
||||
|
||||
if (this->pm_1_0_sensor_ != nullptr) {
|
||||
this->pm_1_0_sensor_->publish_state(pm1);
|
||||
}
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
this->pm_2_5_sensor_->publish_state(pm25);
|
||||
}
|
||||
if (this->pm_10_0_sensor_ != nullptr) {
|
||||
this->pm_10_0_sensor_->publish_state(pm10);
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void PM2005Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PM2005:");
|
||||
ESP_LOGCONFIG(TAG, " Type: PM2%u05", this->sensor_type_ == PM2105);
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with PM2%u05 failed!", this->sensor_type_ == PM2105);
|
||||
}
|
||||
|
||||
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM10 ", this->pm_10_0_sensor_);
|
||||
}
|
||||
|
||||
} // namespace pm2005
|
||||
} // namespace esphome
|
46
esphome/components/pm2005/pm2005.h
Normal file
46
esphome/components/pm2005/pm2005.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pm2005 {
|
||||
|
||||
enum SensorType {
|
||||
PM2005,
|
||||
PM2105,
|
||||
};
|
||||
|
||||
class PM2005Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||
|
||||
void set_sensor_type(SensorType sensor_type) { this->sensor_type_ = sensor_type; }
|
||||
|
||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0_sensor) { this->pm_1_0_sensor_ = pm_1_0_sensor; }
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { this->pm_2_5_sensor_ = pm_2_5_sensor; }
|
||||
void set_pm_10_0_sensor(sensor::Sensor *pm_10_0_sensor) { this->pm_10_0_sensor_ = pm_10_0_sensor; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
uint8_t sensor_situation_{0};
|
||||
uint8_t data_buffer_[12];
|
||||
SensorType sensor_type_{PM2005};
|
||||
|
||||
sensor::Sensor *pm_1_0_sensor_{nullptr};
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
sensor::Sensor *pm_10_0_sensor_{nullptr};
|
||||
|
||||
uint8_t situation_value_index_{3};
|
||||
uint8_t pm_1_0_value_index_{4};
|
||||
uint8_t pm_2_5_value_index_{6};
|
||||
uint8_t pm_10_0_value_index_{8};
|
||||
uint8_t measuring_value_index_{10};
|
||||
};
|
||||
|
||||
} // namespace pm2005
|
||||
} // namespace esphome
|
86
esphome/components/pm2005/sensor.py
Normal file
86
esphome/components/pm2005/sensor.py
Normal file
@ -0,0 +1,86 @@
|
||||
"""PM2005/2105 Sensor component for ESPHome."""
|
||||
|
||||
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_TYPE,
|
||||
DEVICE_CLASS_PM1,
|
||||
DEVICE_CLASS_PM10,
|
||||
DEVICE_CLASS_PM25,
|
||||
ICON_CHEMICAL_WEAPON,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
CODEOWNERS = ["@andrewjswan"]
|
||||
|
||||
pm2005_ns = cg.esphome_ns.namespace("pm2005")
|
||||
PM2005Component = pm2005_ns.class_(
|
||||
"PM2005Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
SensorType = pm2005_ns.enum("SensorType")
|
||||
SENSOR_TYPE = {
|
||||
"PM2005": SensorType.PM2005,
|
||||
"PM2105": SensorType.PM2105,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PM2005Component),
|
||||
cv.Optional(CONF_TYPE, default="PM2005"): cv.enum(SENSOR_TYPE, upper=True),
|
||||
cv.Optional(CONF_PM_1_0): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
icon=ICON_CHEMICAL_WEAPON,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_PM1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PM_2_5): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
icon=ICON_CHEMICAL_WEAPON,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_PM25,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PM_10_0): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
icon=ICON_CHEMICAL_WEAPON,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_PM10,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x28)),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config) -> None:
|
||||
"""Code generation entry point."""
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
cg.add(var.set_sensor_type(config[CONF_TYPE]))
|
||||
|
||||
if pm_1_0_config := config.get(CONF_PM_1_0):
|
||||
sens = await sensor.new_sensor(pm_1_0_config)
|
||||
cg.add(var.set_pm_1_0_sensor(sens))
|
||||
|
||||
if pm_2_5_config := config.get(CONF_PM_2_5):
|
||||
sens = await sensor.new_sensor(pm_2_5_config)
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
||||
|
||||
if pm_10_0_config := config.get(CONF_PM_10_0):
|
||||
sens = await sensor.new_sensor(pm_10_0_config)
|
||||
cg.add(var.set_pm_10_0_sensor(sens))
|
13
tests/components/pm2005/common.yaml
Normal file
13
tests/components/pm2005/common.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
i2c:
|
||||
- id: i2c_pm2005
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
sensor:
|
||||
- platform: pm2005
|
||||
pm_1_0:
|
||||
name: PM1.0
|
||||
pm_2_5:
|
||||
name: PM2.5
|
||||
pm_10_0:
|
||||
name: PM10.0
|
5
tests/components/pm2005/test.esp32-ard.yaml
Normal file
5
tests/components/pm2005/test.esp32-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-c3-ard.yaml
Normal file
5
tests/components/pm2005/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-c3-idf.yaml
Normal file
5
tests/components/pm2005/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp32-idf.yaml
Normal file
5
tests/components/pm2005/test.esp32-idf.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.esp8266-ard.yaml
Normal file
5
tests/components/pm2005/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/pm2005/test.rp2040-ard.yaml
Normal file
5
tests/components/pm2005/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
Loading…
x
Reference in New Issue
Block a user