mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 00:48:19 +01:00
pm1006: add rx-only support (#2038)
This commit is contained in:
parent
b9259a0238
commit
f97cfe9916
5 changed files with 176 additions and 0 deletions
0
esphome/components/pm1006/__init__.py
Normal file
0
esphome/components/pm1006/__init__.py
Normal file
96
esphome/components/pm1006/pm1006.cpp
Normal file
96
esphome/components/pm1006/pm1006.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
#include "pm1006.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pm1006 {
|
||||
|
||||
static const char *const TAG = "pm1006";
|
||||
|
||||
static const uint8_t PM1006_RESPONSE_HEADER[] = {0x16, 0x11, 0x0B};
|
||||
|
||||
void PM1006Component::setup() {
|
||||
// because this implementation is currently rx-only, there is nothing to setup
|
||||
}
|
||||
|
||||
void PM1006Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PM1006:");
|
||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
}
|
||||
|
||||
void PM1006Component::loop() {
|
||||
while (this->available() != 0) {
|
||||
this->read_byte(&this->data_[this->data_index_]);
|
||||
auto check = this->check_byte_();
|
||||
if (!check.has_value()) {
|
||||
// finished
|
||||
this->parse_data_();
|
||||
this->data_index_ = 0;
|
||||
} else if (!*check) {
|
||||
// wrong data
|
||||
ESP_LOGV(TAG, "Byte %i of received data frame is invalid.", this->data_index_);
|
||||
this->data_index_ = 0;
|
||||
} else {
|
||||
// next byte
|
||||
this->data_index_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float PM1006Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
uint8_t PM1006Component::pm1006_checksum_(const uint8_t *command_data, uint8_t length) const {
|
||||
uint8_t sum = 0;
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
sum += command_data[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
optional<bool> PM1006Component::check_byte_() const {
|
||||
uint8_t index = this->data_index_;
|
||||
uint8_t byte = this->data_[index];
|
||||
|
||||
// index 0..2 are the fixed header
|
||||
if (index < sizeof(PM1006_RESPONSE_HEADER)) {
|
||||
return byte == PM1006_RESPONSE_HEADER[index];
|
||||
}
|
||||
|
||||
// just some additional notes here:
|
||||
// index 3..4 is unused
|
||||
// index 5..6 is our PM2.5 reading (3..6 is called DF1-DF4 in the datasheet at
|
||||
// http://www.jdscompany.co.kr/download.asp?gubun=07&filename=PM1006_LED_PARTICLE_SENSOR_MODULE_SPECIFICATIONS.pdf
|
||||
// that datasheet goes on up to DF16, which is unused for PM1006 but used in PM1006K
|
||||
// so this code should be trivially extensible to support that one later
|
||||
if (index < (sizeof(PM1006_RESPONSE_HEADER) + 16))
|
||||
return true;
|
||||
|
||||
// checksum
|
||||
if (index == (sizeof(PM1006_RESPONSE_HEADER) + 16)) {
|
||||
uint8_t checksum = pm1006_checksum_(this->data_, sizeof(PM1006_RESPONSE_HEADER) + 17);
|
||||
if (checksum != 0) {
|
||||
ESP_LOGW(TAG, "PM1006 checksum is wrong: %02x, expected zero", checksum);
|
||||
return false;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PM1006Component::parse_data_() {
|
||||
const int pm_2_5_concentration = this->get_16_bit_uint_(5);
|
||||
|
||||
ESP_LOGD(TAG, "Got PM2.5 Concentration: %d µg/m³", pm_2_5_concentration);
|
||||
|
||||
if (this->pm_2_5_sensor_ != nullptr) {
|
||||
this->pm_2_5_sensor_->publish_state(pm_2_5_concentration);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t PM1006Component::get_16_bit_uint_(uint8_t start_index) const {
|
||||
return encode_uint16(this->data_[start_index], this->data_[start_index + 1]);
|
||||
}
|
||||
|
||||
} // namespace pm1006
|
||||
} // namespace esphome
|
35
esphome/components/pm1006/pm1006.h
Normal file
35
esphome/components/pm1006/pm1006.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pm1006 {
|
||||
|
||||
class PM1006Component : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
PM1006Component() = default;
|
||||
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5_sensor) { this->pm_2_5_sensor_ = pm_2_5_sensor; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
optional<bool> check_byte_() const;
|
||||
void parse_data_();
|
||||
uint16_t get_16_bit_uint_(uint8_t start_index) const;
|
||||
uint8_t pm1006_checksum_(const uint8_t *command_data, uint8_t length) const;
|
||||
|
||||
sensor::Sensor *pm_2_5_sensor_{nullptr};
|
||||
|
||||
uint8_t data_[20];
|
||||
uint8_t data_index_{0};
|
||||
uint32_t last_transmission_{0};
|
||||
};
|
||||
|
||||
} // namespace pm1006
|
||||
} // namespace esphome
|
44
esphome/components/pm1006/sensor.py
Normal file
44
esphome/components/pm1006/sensor.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PM_2_5,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_BLUR,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
pm1006_ns = cg.esphome_ns.namespace("pm1006")
|
||||
PM1006Component = pm1006_ns.class_("PM1006Component", uart.UARTDevice, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PM1006Component),
|
||||
cv.Optional(CONF_PM_2_5): sensor.sensor_schema(
|
||||
UNIT_MICROGRAMS_PER_CUBIC_METER,
|
||||
ICON_BLUR,
|
||||
0,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_PM_2_5 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PM_2_5])
|
||||
cg.add(var.set_pm_2_5_sensor(sens))
|
|
@ -654,6 +654,7 @@ ICON_ACCOUNT_CHECK = "mdi:account-check"
|
|||
ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical"
|
||||
ICON_BATTERY = "mdi:battery"
|
||||
ICON_BLUETOOTH = "mdi:bluetooth"
|
||||
ICON_BLUR = "mdi:blur"
|
||||
ICON_BRIEFCASE_DOWNLOAD = "mdi:briefcase-download"
|
||||
ICON_BRIGHTNESS_5 = "mdi:brightness-5"
|
||||
ICON_BUG = "mdi:bug"
|
||||
|
|
Loading…
Reference in a new issue