mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Refactor Sensirion Sensors (#3374)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
99335d986e
commit
d620b6dd5e
36 changed files with 484 additions and 718 deletions
|
@ -170,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||||
esphome/components/sdp3x/* @Azimath
|
esphome/components/sdp3x/* @Azimath
|
||||||
esphome/components/selec_meter/* @sourabhjaiswal
|
esphome/components/selec_meter/* @sourabhjaiswal
|
||||||
esphome/components/select/* @esphome/core
|
esphome/components/select/* @esphome/core
|
||||||
|
esphome/components/sensirion_common/* @martgras
|
||||||
esphome/components/sensor/* @esphome/core
|
esphome/components/sensor/* @esphome/core
|
||||||
esphome/components/sgp40/* @SenexCrenshaw
|
esphome/components/sgp40/* @SenexCrenshaw
|
||||||
esphome/components/sht4x/* @sjtrny
|
esphome/components/sht4x/* @sjtrny
|
||||||
|
|
|
@ -15,6 +15,7 @@ enum ErrorCode {
|
||||||
ERROR_NOT_INITIALIZED = 4,
|
ERROR_NOT_INITIALIZED = 4,
|
||||||
ERROR_TOO_LARGE = 5,
|
ERROR_TOO_LARGE = 5,
|
||||||
ERROR_UNKNOWN = 6,
|
ERROR_UNKNOWN = 6,
|
||||||
|
ERROR_CRC = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ReadBuffer {
|
struct ReadBuffer {
|
||||||
|
|
|
@ -33,14 +33,8 @@ void SCD30Component::setup() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Firmware version identification
|
/// Firmware version identification
|
||||||
if (!this->write_command_(SCD30_CMD_GET_FIRMWARE_VERSION)) {
|
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint16_t raw_firmware_version[3];
|
uint16_t raw_firmware_version[3];
|
||||||
|
if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
|
||||||
if (!this->read_data_(raw_firmware_version, 3)) {
|
|
||||||
this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
|
this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -49,7 +43,7 @@ void SCD30Component::setup() {
|
||||||
uint16_t(raw_firmware_version[0] & 0xFF));
|
uint16_t(raw_firmware_version[0] & 0xFF));
|
||||||
|
|
||||||
if (this->temperature_offset_ != 0) {
|
if (this->temperature_offset_ != 0) {
|
||||||
if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
|
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
|
||||||
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
|
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -69,7 +63,7 @@ void SCD30Component::setup() {
|
||||||
delay(30);
|
delay(30);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
|
if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
|
||||||
ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
|
ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -81,7 +75,7 @@ void SCD30Component::setup() {
|
||||||
|
|
||||||
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
|
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
|
||||||
if (this->altitude_compensation_ != 0xFFFF) {
|
if (this->altitude_compensation_ != 0xFFFF) {
|
||||||
if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||||
ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
|
ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -92,7 +86,7 @@ void SCD30Component::setup() {
|
||||||
delay(30);
|
delay(30);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!this->write_command_(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||||
ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
|
ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -103,7 +97,7 @@ void SCD30Component::setup() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Sensor initialization
|
/// Sensor initialization
|
||||||
if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
|
if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
|
||||||
ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
|
ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -151,14 +145,14 @@ void SCD30Component::dump_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SCD30Component::update() {
|
void SCD30Component::update() {
|
||||||
uint16_t raw_read_status[1];
|
uint16_t raw_read_status;
|
||||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
ESP_LOGW(TAG, "Data not ready yet!");
|
ESP_LOGW(TAG, "Data not ready yet!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) {
|
if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
|
||||||
ESP_LOGW(TAG, "Error reading measurement!");
|
ESP_LOGW(TAG, "Error reading measurement!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
@ -166,7 +160,7 @@ void SCD30Component::update() {
|
||||||
|
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[6];
|
uint16_t raw_data[6];
|
||||||
if (!this->read_data_(raw_data, 6)) {
|
if (!this->read_data(raw_data, 6)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -197,77 +191,16 @@ void SCD30Component::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SCD30Component::is_data_ready_() {
|
bool SCD30Component::is_data_ready_() {
|
||||||
if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
|
if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delay(4);
|
delay(4);
|
||||||
uint16_t is_data_ready;
|
uint16_t is_data_ready;
|
||||||
if (!this->read_data_(&is_data_ready, 1)) {
|
if (!this->read_data(&is_data_ready, 1)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return is_data_ready == 1;
|
return is_data_ready == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SCD30Component::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SCD30Component::write_command_(uint16_t command, uint16_t data) {
|
|
||||||
uint8_t raw[5];
|
|
||||||
raw[0] = command >> 8;
|
|
||||||
raw[1] = command & 0xFF;
|
|
||||||
raw[2] = data >> 8;
|
|
||||||
raw[3] = data & 0xFF;
|
|
||||||
raw[4] = sht_crc_(raw[2], raw[3]);
|
|
||||||
return this->write(raw, 5) == i2c::ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SCD30Component::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace scd30
|
} // namespace scd30
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace scd30 {
|
namespace scd30 {
|
||||||
|
|
||||||
/// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors.
|
/// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors.
|
||||||
class SCD30Component : public Component, public i2c::I2CDevice {
|
class SCD30Component : public Component, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||||
|
@ -27,10 +27,6 @@ class SCD30Component : public Component, public i2c::I2CDevice {
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool write_command_(uint16_t command, uint16_t data);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
|
||||||
bool is_data_ready_();
|
bool is_data_ready_();
|
||||||
|
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
|
|
|
@ -2,6 +2,7 @@ from esphome import core
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.components import sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
|
@ -18,9 +19,12 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
scd30_ns = cg.esphome_ns.namespace("scd30")
|
scd30_ns = cg.esphome_ns.namespace("scd30")
|
||||||
SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice)
|
SCD30Component = scd30_ns.class_(
|
||||||
|
"SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||||
|
|
|
@ -25,15 +25,8 @@ void SCD4XComponent::setup() {
|
||||||
|
|
||||||
// the sensor needs 1000 ms to enter the idle state
|
// the sensor needs 1000 ms to enter the idle state
|
||||||
this->set_timeout(1000, [this]() {
|
this->set_timeout(1000, [this]() {
|
||||||
// Check if measurement is ready before reading the value
|
uint16_t raw_read_status;
|
||||||
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) {
|
||||||
ESP_LOGE(TAG, "Failed to write data ready status command");
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t raw_read_status[1];
|
|
||||||
if (!this->read_data_(raw_read_status, 1)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to read data ready status");
|
ESP_LOGE(TAG, "Failed to read data ready status");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -41,9 +34,9 @@ void SCD4XComponent::setup() {
|
||||||
|
|
||||||
uint32_t stop_measurement_delay = 0;
|
uint32_t stop_measurement_delay = 0;
|
||||||
// In order to query the device periodic measurement must be ceased
|
// In order to query the device periodic measurement must be ceased
|
||||||
if (raw_read_status[0]) {
|
if (raw_read_status) {
|
||||||
ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement");
|
ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement");
|
||||||
if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) {
|
if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
|
||||||
ESP_LOGE(TAG, "Failed to stop measurements");
|
ESP_LOGE(TAG, "Failed to stop measurements");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -53,15 +46,8 @@ void SCD4XComponent::setup() {
|
||||||
stop_measurement_delay = 500;
|
stop_measurement_delay = 500;
|
||||||
}
|
}
|
||||||
this->set_timeout(stop_measurement_delay, [this]() {
|
this->set_timeout(stop_measurement_delay, [this]() {
|
||||||
if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) {
|
|
||||||
ESP_LOGE(TAG, "Failed to write get serial command");
|
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t raw_serial_number[3];
|
uint16_t raw_serial_number[3];
|
||||||
if (!this->read_data_(raw_serial_number, 3)) {
|
if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) {
|
||||||
ESP_LOGE(TAG, "Failed to read serial number");
|
ESP_LOGE(TAG, "Failed to read serial number");
|
||||||
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -70,8 +56,8 @@ void SCD4XComponent::setup() {
|
||||||
ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8),
|
ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8),
|
||||||
uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8));
|
uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8));
|
||||||
|
|
||||||
if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET,
|
if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET,
|
||||||
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
|
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
|
||||||
ESP_LOGE(TAG, "Error setting temperature offset.");
|
ESP_LOGE(TAG, "Error setting temperature offset.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -88,7 +74,7 @@ void SCD4XComponent::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||||
ESP_LOGE(TAG, "Error setting altitude compensation.");
|
ESP_LOGE(TAG, "Error setting altitude compensation.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -96,7 +82,7 @@ void SCD4XComponent::setup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||||
ESP_LOGE(TAG, "Error setting automatic self calibration.");
|
ESP_LOGE(TAG, "Error setting automatic self calibration.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -104,7 +90,7 @@ void SCD4XComponent::setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally start sensor measurements
|
// Finally start sensor measurements
|
||||||
if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
|
if (!this->write_command(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
|
||||||
ESP_LOGE(TAG, "Error starting continuous measurements.");
|
ESP_LOGE(TAG, "Error starting continuous measurements.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -164,19 +150,19 @@ void SCD4XComponent::update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if data is ready
|
// Check if data is ready
|
||||||
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t raw_read_status[1];
|
uint16_t raw_read_status;
|
||||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
ESP_LOGW(TAG, "Data not ready yet!");
|
ESP_LOGW(TAG, "Data not ready yet!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) {
|
if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) {
|
||||||
ESP_LOGW(TAG, "Error reading measurement!");
|
ESP_LOGW(TAG, "Error reading measurement!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
@ -184,7 +170,7 @@ void SCD4XComponent::update() {
|
||||||
|
|
||||||
// Read off sensor data
|
// Read off sensor data
|
||||||
uint16_t raw_data[3];
|
uint16_t raw_data[3];
|
||||||
if (!this->read_data_(raw_data, 3)) {
|
if (!this->read_data(raw_data, 3)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +204,7 @@ void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) {
|
bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) {
|
||||||
if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
|
if (this->write_command(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
|
||||||
ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa);
|
ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -227,70 +213,5 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SCD4XComponent::write_command_(uint16_t command) {
|
|
||||||
const uint8_t num_bytes = 2;
|
|
||||||
uint8_t buffer[num_bytes];
|
|
||||||
|
|
||||||
buffer[0] = (command >> 8);
|
|
||||||
buffer[1] = command & 0xff;
|
|
||||||
|
|
||||||
return this->write(buffer, num_bytes) == i2c::ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) {
|
|
||||||
uint8_t raw[5];
|
|
||||||
raw[0] = command >> 8;
|
|
||||||
raw[1] = command & 0xFF;
|
|
||||||
raw[2] = data >> 8;
|
|
||||||
raw[3] = data & 0xFF;
|
|
||||||
raw[4] = sht_crc_(raw[2], raw[3]);
|
|
||||||
return this->write(raw, 5) == i2c::ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace scd4x
|
} // namespace scd4x
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace scd4x {
|
namespace scd4x {
|
||||||
|
|
||||||
enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN };
|
enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN };
|
||||||
|
|
||||||
class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
|
class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool write_command_(uint16_t command, uint16_t data);
|
|
||||||
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa);
|
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa);
|
||||||
|
|
||||||
ERRORCODE error_code_;
|
ERRORCODE error_code_;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.components import sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_CO2,
|
CONF_CO2,
|
||||||
|
@ -21,9 +21,12 @@ from esphome.const import (
|
||||||
|
|
||||||
CODEOWNERS = ["@sjtrny"]
|
CODEOWNERS = ["@sjtrny"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
scd4x_ns = cg.esphome_ns.namespace("scd4x")
|
scd4x_ns = cg.esphome_ns.namespace("scd4x")
|
||||||
SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice)
|
SCD4XComponent = scd4x_ns.class_(
|
||||||
|
"SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||||
|
|
|
@ -7,55 +7,50 @@ namespace esphome {
|
||||||
namespace sdp3x {
|
namespace sdp3x {
|
||||||
|
|
||||||
static const char *const TAG = "sdp3x.sensor";
|
static const char *const TAG = "sdp3x.sensor";
|
||||||
static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
|
static const uint16_t SDP3X_SOFT_RESET = 0x0006;
|
||||||
static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
|
static const uint16_t SDP3X_READ_ID1 = 0x367C;
|
||||||
static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
|
static const uint16_t SDP3X_READ_ID2 = 0xE102;
|
||||||
static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
|
static const uint16_t SDP3X_START_DP_AVG = 0x3615;
|
||||||
static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
|
static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603;
|
||||||
static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
|
static const uint16_t SDP3X_STOP_MEAS = 0x3FF9;
|
||||||
|
|
||||||
void SDP3XComponent::update() { this->read_pressure_(); }
|
void SDP3XComponent::update() { this->read_pressure_(); }
|
||||||
|
|
||||||
void SDP3XComponent::setup() {
|
void SDP3XComponent::setup() {
|
||||||
ESP_LOGD(TAG, "Setting up SDP3X...");
|
ESP_LOGD(TAG, "Setting up SDP3X...");
|
||||||
|
|
||||||
if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) {
|
if (!this->write_command(SDP3X_STOP_MEAS)) {
|
||||||
ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason
|
ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) {
|
if (!this->write_command(SDP3X_SOFT_RESET)) {
|
||||||
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
|
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
|
||||||
}
|
}
|
||||||
|
|
||||||
this->set_timeout(20, [this] {
|
this->set_timeout(20, [this] {
|
||||||
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
|
if (!this->write_command(SDP3X_READ_ID1)) {
|
||||||
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
|
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
|
if (!this->write_command(SDP3X_READ_ID2)) {
|
||||||
ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
|
ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t data[18];
|
uint16_t data[6];
|
||||||
if (this->read(data, 18) != i2c::ERROR_OK) {
|
if (this->read_data(data, 6) != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Read ID SDP3X failed!");
|
ESP_LOGE(TAG, "Read ID SDP3X failed!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
|
|
||||||
ESP_LOGE(TAG, "CRC ID SDP3X failed!");
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SDP8xx
|
// SDP8xx
|
||||||
// ref:
|
// ref:
|
||||||
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
|
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
|
||||||
if (data[2] == 0x02) {
|
if (data[1] >> 8 == 0x02) {
|
||||||
switch (data[3]) {
|
switch (data[1] & 0xFF) {
|
||||||
case 0x01: // SDP800-500Pa
|
case 0x01: // SDP800-500Pa
|
||||||
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
|
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
|
||||||
break;
|
break;
|
||||||
|
@ -75,15 +70,16 @@ void SDP3XComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
|
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (data[2] == 0x01) {
|
} else if (data[1] >> 8 == 0x01) {
|
||||||
if (data[3] == 0x01) {
|
if ((data[1] & 0xFF) == 0x01) {
|
||||||
ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
|
ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
|
||||||
} else if (data[3] == 0x02) {
|
} else if ((data[1] & 0xFF) == 0x02) {
|
||||||
ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
|
ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
|
if (this->write_command(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG) !=
|
||||||
|
i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
|
ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDP3XComponent::read_pressure_() {
|
void SDP3XComponent::read_pressure_() {
|
||||||
uint8_t data[9];
|
uint16_t data[3];
|
||||||
if (this->read(data, 9) != i2c::ERROR_OK) {
|
if (this->read_data(data, 3) != i2c::ERROR_OK) {
|
||||||
ESP_LOGW(TAG, "Couldn't read SDP3X data!");
|
ESP_LOGW(TAG, "Couldn't read SDP3X data!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) {
|
int16_t pressure_raw = data[0];
|
||||||
ESP_LOGW(TAG, "Invalid SDP3X data!");
|
int16_t temperature_raw = data[1];
|
||||||
this->status_set_warning();
|
int16_t scale_factor_raw = data[2];
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t pressure_raw = encode_uint16(data[0], data[1]);
|
|
||||||
int16_t temperature_raw = encode_uint16(data[3], data[4]);
|
|
||||||
int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
|
|
||||||
// scale factor is in Pa - convert to hPa
|
// scale factor is in Pa - convert to hPa
|
||||||
float pressure = pressure_raw / (scale_factor_raw * 100.0f);
|
float pressure = pressure_raw / (scale_factor_raw * 100.0f);
|
||||||
ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
|
ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
|
||||||
|
@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() {
|
||||||
|
|
||||||
float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; }
|
float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
// Check CRC function from SDP3X sample code provided by sensirion
|
|
||||||
// Returns true if a checksum is OK
|
|
||||||
bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) {
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
// calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1)
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
crc ^= (data[i]);
|
|
||||||
for (uint8_t bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x31;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify checksum
|
|
||||||
return (crc == checksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sdp3x
|
} // namespace sdp3x
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sdp3x {
|
namespace sdp3x {
|
||||||
|
|
||||||
enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
|
enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
|
||||||
|
|
||||||
class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
class SDP3XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice, public sensor::Sensor {
|
||||||
public:
|
public:
|
||||||
/// Schedule temperature+pressure readings.
|
/// Schedule temperature+pressure readings.
|
||||||
void update() override;
|
void update() override;
|
||||||
|
@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
|
||||||
protected:
|
protected:
|
||||||
/// Internal method to read the pressure from the component after it has been scheduled.
|
/// Internal method to read the pressure from the component after it has been scheduled.
|
||||||
void read_pressure_();
|
void read_pressure_();
|
||||||
|
|
||||||
bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
|
|
||||||
MeasurementMode measurement_mode_;
|
MeasurementMode measurement_mode_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.components import sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
@ -8,10 +9,13 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
CODEOWNERS = ["@Azimath"]
|
CODEOWNERS = ["@Azimath"]
|
||||||
|
|
||||||
sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
|
sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
|
||||||
SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
|
SDP3XComponent = sdp3x_ns.class_(
|
||||||
|
"SDP3XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
MeasurementMode = sdp3x_ns.enum("MeasurementMode")
|
MeasurementMode = sdp3x_ns.enum("MeasurementMode")
|
||||||
|
|
10
esphome/components/sensirion_common/__init__.py
Normal file
10
esphome/components/sensirion_common/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
from esphome.components import i2c
|
||||||
|
|
||||||
|
|
||||||
|
CODEOWNERS = ["@martgras"]
|
||||||
|
|
||||||
|
sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common")
|
||||||
|
|
||||||
|
SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice)
|
128
esphome/components/sensirion_common/i2c_sensirion.cpp
Normal file
128
esphome/components/sensirion_common/i2c_sensirion.cpp
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
#include "i2c_sensirion.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace sensirion_common {
|
||||||
|
|
||||||
|
static const char *const TAG = "sensirion_i2c";
|
||||||
|
// To avoid memory allocations for small writes a stack buffer is used
|
||||||
|
static const size_t BUFFER_STACK_SIZE = 16;
|
||||||
|
|
||||||
|
bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) {
|
||||||
|
const uint8_t num_bytes = len * 3;
|
||||||
|
std::vector<uint8_t> buf(num_bytes);
|
||||||
|
|
||||||
|
last_error_ = this->read(buf.data(), num_bytes);
|
||||||
|
if (last_error_ != i2c::ERROR_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < len; i++) {
|
||||||
|
const uint8_t j = 3 * i;
|
||||||
|
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||||
|
if (crc != buf[j + 2]) {
|
||||||
|
ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc);
|
||||||
|
last_error_ = i2c::ERROR_CRC;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data[i] = encode_uint16(buf[j], buf[j + 1]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/***
|
||||||
|
* write command with parameters and insert crc
|
||||||
|
* use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters
|
||||||
|
*/
|
||||||
|
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
|
||||||
|
uint8_t data_len) {
|
||||||
|
uint8_t temp_stack[BUFFER_STACK_SIZE];
|
||||||
|
std::unique_ptr<uint8_t[]> temp_heap;
|
||||||
|
uint8_t *temp;
|
||||||
|
size_t required_buffer_len = data_len * 3 + 2;
|
||||||
|
|
||||||
|
// Is a dynamic allocation required ?
|
||||||
|
if (required_buffer_len >= BUFFER_STACK_SIZE) {
|
||||||
|
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
|
||||||
|
temp = temp_heap.get();
|
||||||
|
} else {
|
||||||
|
temp = temp_stack;
|
||||||
|
}
|
||||||
|
// First byte or word is the command
|
||||||
|
uint8_t raw_idx = 0;
|
||||||
|
if (command_len == 1) {
|
||||||
|
temp[raw_idx++] = command & 0xFF;
|
||||||
|
} else {
|
||||||
|
// command is 2 bytes
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
temp[raw_idx++] = command >> 8;
|
||||||
|
temp[raw_idx++] = command & 0xFF;
|
||||||
|
#else
|
||||||
|
temp[raw_idx++] = command & 0xFF;
|
||||||
|
temp[raw_idx++] = command >> 8;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// add parameters folllowed by crc
|
||||||
|
// skipped if len == 0
|
||||||
|
for (size_t i = 0; i < data_len; i++) {
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
temp[raw_idx++] = data[i] >> 8;
|
||||||
|
temp[raw_idx++] = data[i] & 0xFF;
|
||||||
|
#else
|
||||||
|
temp[raw_idx++] = data[i] & 0xFF;
|
||||||
|
temp[raw_idx++] = data[i] >> 8;
|
||||||
|
#endif
|
||||||
|
temp[raw_idx++] = sht_crc_(data[i]);
|
||||||
|
}
|
||||||
|
last_error_ = this->write(temp, raw_idx);
|
||||||
|
return last_error_ == i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len,
|
||||||
|
uint8_t delay_ms) {
|
||||||
|
if (!this->write_command_(reg, command_len, nullptr, 0)) {
|
||||||
|
ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delay(delay_ms);
|
||||||
|
bool result = this->read_data(data, len);
|
||||||
|
if (!result) {
|
||||||
|
ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 8-bit CRC checksum is transmitted after each data word
|
||||||
|
uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) {
|
||||||
|
uint8_t bit;
|
||||||
|
uint8_t crc = 0xFF;
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
crc ^= data >> 8;
|
||||||
|
#else
|
||||||
|
crc ^= data & 0xFF;
|
||||||
|
#endif
|
||||||
|
for (bit = 8; bit > 0; --bit) {
|
||||||
|
if (crc & 0x80) {
|
||||||
|
crc = (crc << 1) ^ crc_polynomial_;
|
||||||
|
} else {
|
||||||
|
crc = (crc << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
crc ^= data & 0xFF;
|
||||||
|
#else
|
||||||
|
crc ^= data >> 8;
|
||||||
|
#endif
|
||||||
|
for (bit = 8; bit > 0; --bit) {
|
||||||
|
if (crc & 0x80) {
|
||||||
|
crc = (crc << 1) ^ crc_polynomial_;
|
||||||
|
} else {
|
||||||
|
crc = (crc << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sensirion_common
|
||||||
|
} // namespace esphome
|
155
esphome/components/sensirion_common/i2c_sensirion.h
Normal file
155
esphome/components/sensirion_common/i2c_sensirion.h
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
#pragma once
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace sensirion_common {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a i2c functions for Sensirion sensors
|
||||||
|
* Sensirion data requires crc checking.
|
||||||
|
* Each 16 bit word is/must be followed 8 bit CRC code
|
||||||
|
* (Applies to read and write - note the i2c command code doesn't need a CRC)
|
||||||
|
* Format:
|
||||||
|
* | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | ..
|
||||||
|
*/
|
||||||
|
class SensirionI2CDevice : public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
|
||||||
|
|
||||||
|
/** Read data words from i2c device.
|
||||||
|
* handles crc check used by Sensirion sensors
|
||||||
|
* @param data pointer to raw result
|
||||||
|
* @param len number of words to read
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool read_data(uint16_t *data, uint8_t len);
|
||||||
|
|
||||||
|
/** Read 1 data word from i2c device.
|
||||||
|
* @param data reference to raw result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool read_data(uint16_t &data) { return this->read_data(&data, 1); }
|
||||||
|
|
||||||
|
/** get data words from i2c register.
|
||||||
|
* handles crc check used by Sensirion sensors
|
||||||
|
* @param i2c register
|
||||||
|
* @param data pointer to raw result
|
||||||
|
* @param len number of words to read
|
||||||
|
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) {
|
||||||
|
return get_register_(command, ADDR_16_BIT, data, len, delay);
|
||||||
|
}
|
||||||
|
/** Read 1 data word from 16 bit i2c register.
|
||||||
|
* @param i2c register
|
||||||
|
* @param data reference to raw result
|
||||||
|
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) {
|
||||||
|
return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** get data words from i2c register.
|
||||||
|
* handles crc check used by Sensirion sensors
|
||||||
|
* @param i2c register
|
||||||
|
* @param data pointer to raw result
|
||||||
|
* @param len number of words to read
|
||||||
|
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) {
|
||||||
|
return get_register_(i2c_register, ADDR_8_BIT, data, len, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read 1 data word from 8 bit i2c register.
|
||||||
|
* @param i2c register
|
||||||
|
* @param data reference to raw result
|
||||||
|
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) {
|
||||||
|
return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write a command to the i2c device.
|
||||||
|
* @param command i2c command to send
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
template<class T> bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); }
|
||||||
|
|
||||||
|
/** Write a command and one data word to the i2c device .
|
||||||
|
* @param command i2c command to send
|
||||||
|
* @param data argument for the i2c command
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
template<class T> bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); }
|
||||||
|
|
||||||
|
/** Write a command with arguments as words
|
||||||
|
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
|
||||||
|
* @param data vector<uint16> arguments for the i2c command
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
template<class T> bool write_command(T i2c_register, const std::vector<uint16_t> &data) {
|
||||||
|
return write_command_(i2c_register, sizeof(T), data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write a command with arguments as words
|
||||||
|
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
|
||||||
|
* @param data arguments for the i2c command
|
||||||
|
* @param len number of arguments (words)
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
template<class T> bool write_command(T i2c_register, const uint16_t *data, uint8_t len) {
|
||||||
|
// limit to 8 or 16 bit only
|
||||||
|
static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2,
|
||||||
|
"only 8 or 16 bit command types are supported.");
|
||||||
|
return write_command_(i2c_register, CommandLen(sizeof(T)), data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t crc_polynomial_{0x31u}; // default for sensirion
|
||||||
|
/** Write a command with arguments as words
|
||||||
|
* @param command i2c command to send can be uint8_t or uint16_t
|
||||||
|
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
|
||||||
|
* @param data arguments for the i2c command
|
||||||
|
* @param data_len number of arguments (words)
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len);
|
||||||
|
|
||||||
|
/** get data words from i2c register.
|
||||||
|
* handles crc check used by Sensirion sensors
|
||||||
|
* @param i2c register
|
||||||
|
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
|
||||||
|
* @param data pointer to raw result
|
||||||
|
* @param len number of words to read
|
||||||
|
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||||
|
* @return true if reading succeded
|
||||||
|
*/
|
||||||
|
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
|
||||||
|
|
||||||
|
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
|
||||||
|
* @param command i2c command to send
|
||||||
|
* @param data data word for which the crc8 checksum is calculated
|
||||||
|
* @param len number of arguments (words)
|
||||||
|
* @return 8 Bit CRC
|
||||||
|
*/
|
||||||
|
uint8_t sht_crc_(uint16_t data);
|
||||||
|
|
||||||
|
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
|
||||||
|
* @param command i2c command to send
|
||||||
|
* @param data1 high byte of data word
|
||||||
|
* @param data2 low byte of data word
|
||||||
|
* @return 8 Bit CRC
|
||||||
|
*/
|
||||||
|
uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); }
|
||||||
|
|
||||||
|
/** last error code from i2c operation
|
||||||
|
*/
|
||||||
|
i2c::ErrorCode last_error_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sensirion_common
|
||||||
|
} // namespace esphome
|
|
@ -1,10 +1,13 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_BASELINE,
|
CONF_BASELINE,
|
||||||
CONF_ECO2,
|
CONF_ECO2,
|
||||||
|
CONF_STORE_BASELINE,
|
||||||
|
CONF_TEMPERATURE_SOURCE,
|
||||||
CONF_TVOC,
|
CONF_TVOC,
|
||||||
ICON_RADIATOR,
|
ICON_RADIATOR,
|
||||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
|
@ -17,17 +20,19 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
sgp30_ns = cg.esphome_ns.namespace("sgp30")
|
sgp30_ns = cg.esphome_ns.namespace("sgp30")
|
||||||
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
|
SGP30Component = sgp30_ns.class_(
|
||||||
|
"SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONF_ECO2_BASELINE = "eco2_baseline"
|
CONF_ECO2_BASELINE = "eco2_baseline"
|
||||||
CONF_TVOC_BASELINE = "tvoc_baseline"
|
CONF_TVOC_BASELINE = "tvoc_baseline"
|
||||||
CONF_STORE_BASELINE = "store_baseline"
|
|
||||||
CONF_UPTIME = "uptime"
|
CONF_UPTIME = "uptime"
|
||||||
CONF_COMPENSATION = "compensation"
|
CONF_COMPENSATION = "compensation"
|
||||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
|
|
@ -36,14 +36,8 @@ void SGP30Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
||||||
|
|
||||||
// Serial Number identification
|
// Serial Number identification
|
||||||
if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) {
|
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint16_t raw_serial_number[3];
|
uint16_t raw_serial_number[3];
|
||||||
|
if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) {
|
||||||
if (!this->read_data_(raw_serial_number, 3)) {
|
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -52,16 +46,12 @@ void SGP30Component::setup() {
|
||||||
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
||||||
|
|
||||||
// Featureset identification for future use
|
// Featureset identification for future use
|
||||||
if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) {
|
uint16_t raw_featureset;
|
||||||
|
if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t raw_featureset[1];
|
this->featureset_ = raw_featureset;
|
||||||
if (!this->read_data_(raw_featureset, 1)) {
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->featureset_ = raw_featureset[0];
|
|
||||||
if (uint16_t(this->featureset_ >> 12) != 0x0) {
|
if (uint16_t(this->featureset_ >> 12) != 0x0) {
|
||||||
if (uint16_t(this->featureset_ >> 12) == 0x1) {
|
if (uint16_t(this->featureset_ >> 12) == 0x1) {
|
||||||
// ID matching a different sensor: SGPC3
|
// ID matching a different sensor: SGPC3
|
||||||
|
@ -76,7 +66,7 @@ void SGP30Component::setup() {
|
||||||
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
|
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
|
||||||
|
|
||||||
// Sensor initialization
|
// Sensor initialization
|
||||||
if (!this->write_command_(SGP30_CMD_IAQ_INIT)) {
|
if (!this->write_command(SGP30_CMD_IAQ_INIT)) {
|
||||||
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
|
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
|
||||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() {
|
||||||
|
|
||||||
void SGP30Component::read_iaq_baseline_() {
|
void SGP30Component::read_iaq_baseline_() {
|
||||||
if (this->is_sensor_baseline_reliable_()) {
|
if (this->is_sensor_baseline_reliable_()) {
|
||||||
if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) {
|
if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) {
|
||||||
ESP_LOGD(TAG, "Error getting baseline");
|
ESP_LOGD(TAG, "Error getting baseline");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[2];
|
uint16_t raw_data[2];
|
||||||
if (!this->read_data_(raw_data, 2)) {
|
if (!this->read_data(raw_data, 2)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -274,14 +264,14 @@ void SGP30Component::dump_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SGP30Component::update() {
|
void SGP30Component::update() {
|
||||||
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
|
if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->seconds_since_last_store_ += this->update_interval_ / 1000;
|
this->seconds_since_last_store_ += this->update_interval_ / 1000;
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[2];
|
uint16_t raw_data[2];
|
||||||
if (!this->read_data_(raw_data, 2)) {
|
if (!this->read_data(raw_data, 2)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -305,56 +295,5 @@ void SGP30Component::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SGP30Component::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SGP30Component::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sgp30
|
} // namespace sgp30
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ struct SGP30Baselines {
|
||||||
} PACKED;
|
} PACKED;
|
||||||
|
|
||||||
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
|
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
|
||||||
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
||||||
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||||
|
@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
void send_env_data_();
|
void send_env_data_();
|
||||||
void read_iaq_baseline_();
|
void read_iaq_baseline_();
|
||||||
bool is_sensor_baseline_reliable_();
|
bool is_sensor_baseline_reliable_();
|
||||||
void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
|
void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
|
||||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
|
||||||
uint64_t serial_number_;
|
uint64_t serial_number_;
|
||||||
uint16_t featureset_;
|
uint16_t featureset_;
|
||||||
uint32_t required_warm_up_time_;
|
uint32_t required_warm_up_time_;
|
||||||
|
|
|
@ -1,25 +1,30 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_STORE_BASELINE,
|
||||||
|
CONF_TEMPERATURE_SOURCE,
|
||||||
ICON_RADIATOR,
|
ICON_RADIATOR,
|
||||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
CODEOWNERS = ["@SenexCrenshaw"]
|
CODEOWNERS = ["@SenexCrenshaw"]
|
||||||
|
|
||||||
sgp40_ns = cg.esphome_ns.namespace("sgp40")
|
sgp40_ns = cg.esphome_ns.namespace("sgp40")
|
||||||
SGP40Component = sgp40_ns.class_(
|
SGP40Component = sgp40_ns.class_(
|
||||||
"SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
"SGP40Component",
|
||||||
|
sensor.Sensor,
|
||||||
|
cg.PollingComponent,
|
||||||
|
sensirion_common.SensirionI2CDevice,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_COMPENSATION = "compensation"
|
CONF_COMPENSATION = "compensation"
|
||||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
|
||||||
CONF_STORE_BASELINE = "store_baseline"
|
|
||||||
CONF_VOC_BASELINE = "voc_baseline"
|
CONF_VOC_BASELINE = "voc_baseline"
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
|
|
|
@ -12,14 +12,14 @@ void SGP40Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
|
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
|
||||||
|
|
||||||
// Serial Number identification
|
// Serial Number identification
|
||||||
if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) {
|
if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t raw_serial_number[3];
|
uint16_t raw_serial_number[3];
|
||||||
|
|
||||||
if (!this->read_data_(raw_serial_number, 3)) {
|
if (!this->read_data(raw_serial_number, 3)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -28,19 +28,19 @@ void SGP40Component::setup() {
|
||||||
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
||||||
|
|
||||||
// Featureset identification for future use
|
// Featureset identification for future use
|
||||||
if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) {
|
if (!this->write_command(SGP40_CMD_GET_FEATURESET)) {
|
||||||
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
|
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t raw_featureset[1];
|
uint16_t raw_featureset;
|
||||||
if (!this->read_data_(raw_featureset, 1)) {
|
if (!this->read_data(raw_featureset)) {
|
||||||
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
|
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->featureset_ = raw_featureset[0];
|
this->featureset_ = raw_featureset;
|
||||||
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
|
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
|
||||||
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
|
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
|
||||||
SGP40_FEATURESET);
|
SGP40_FEATURESET);
|
||||||
|
@ -95,21 +95,21 @@ void SGP40Component::setup() {
|
||||||
|
|
||||||
void SGP40Component::self_test_() {
|
void SGP40Component::self_test_() {
|
||||||
ESP_LOGD(TAG, "Self-test started");
|
ESP_LOGD(TAG, "Self-test started");
|
||||||
if (!this->write_command_(SGP40_CMD_SELF_TEST)) {
|
if (!this->write_command(SGP40_CMD_SELF_TEST)) {
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
ESP_LOGD(TAG, "Self-test communication failed");
|
ESP_LOGD(TAG, "Self-test communication failed");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->set_timeout(250, [this]() {
|
this->set_timeout(250, [this]() {
|
||||||
uint16_t reply[1];
|
uint16_t reply;
|
||||||
if (!this->read_data_(reply, 1)) {
|
if (!this->read_data(reply)) {
|
||||||
ESP_LOGD(TAG, "Self-test read_data_ failed");
|
ESP_LOGD(TAG, "Self-test read_data_ failed");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply[0] == 0xD400) {
|
if (reply == 0xD400) {
|
||||||
this->self_test_complete_ = true;
|
this->self_test_complete_ = true;
|
||||||
ESP_LOGD(TAG, "Self-test completed");
|
ESP_LOGD(TAG, "Self-test completed");
|
||||||
return;
|
return;
|
||||||
|
@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() {
|
||||||
temperature = 25;
|
temperature = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t command[8];
|
uint16_t data[2];
|
||||||
|
|
||||||
command[0] = 0x26;
|
|
||||||
command[1] = 0x0F;
|
|
||||||
|
|
||||||
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
|
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
|
||||||
command[2] = rhticks >> 8;
|
|
||||||
command[3] = rhticks & 0xFF;
|
|
||||||
command[4] = generate_crc_(command + 2, 2);
|
|
||||||
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
|
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
|
||||||
command[5] = tempticks >> 8;
|
// first paramater is the relative humidity ticks
|
||||||
command[6] = tempticks & 0xFF;
|
data[0] = rhticks;
|
||||||
command[7] = generate_crc_(command + 5, 2);
|
// second paramater is the temperature ticks
|
||||||
|
data[1] = tempticks;
|
||||||
|
|
||||||
if (this->write(command, 8) != i2c::ERROR_OK) {
|
if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
ESP_LOGD(TAG, "write error");
|
ESP_LOGD(TAG, "write error (%d)", this->last_error_);
|
||||||
return UINT16_MAX;
|
return false;
|
||||||
}
|
}
|
||||||
delay(30);
|
delay(30);
|
||||||
uint16_t raw_data[1];
|
|
||||||
|
|
||||||
if (!this->read_data_(raw_data, 1)) {
|
uint16_t raw_data;
|
||||||
|
if (!this->read_data(raw_data)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
ESP_LOGD(TAG, "read_data_ error");
|
ESP_LOGD(TAG, "read_data_ error");
|
||||||
return UINT16_MAX;
|
return UINT16_MAX;
|
||||||
}
|
}
|
||||||
return raw_data[0];
|
return raw_data;
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) {
|
|
||||||
// calculates 8-Bit checksum with given polynomial
|
|
||||||
uint8_t crc = SGP40_CRC8_INIT;
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < datalen; i++) {
|
|
||||||
crc ^= data[i];
|
|
||||||
for (uint8_t b = 0; b < 8; b++) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
|
|
||||||
} else {
|
|
||||||
crc <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SGP40Component::update_voc_index() {
|
void SGP40Component::update_voc_index() {
|
||||||
|
@ -293,56 +270,5 @@ void SGP40Component::dump_config() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SGP40Component::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SGP40Component::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sgp40
|
} // namespace sgp40
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/preferences.h"
|
#include "esphome/core/preferences.h"
|
||||||
#include "sensirion_voc_algorithm.h"
|
#include "sensirion_voc_algorithm.h"
|
||||||
|
@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word
|
||||||
static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682;
|
static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682;
|
||||||
static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f;
|
static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f;
|
||||||
static const uint16_t SGP40_CMD_SELF_TEST = 0x280e;
|
static const uint16_t SGP40_CMD_SELF_TEST = 0x280e;
|
||||||
|
static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F;
|
||||||
|
|
||||||
// Shortest time interval of 3H for storing baseline values.
|
// Shortest time interval of 3H for storing baseline values.
|
||||||
// Prevents wear of the flash because of too many write operations
|
// Prevents wear of the flash because of too many write operations
|
||||||
|
@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50;
|
||||||
class SGP40Component;
|
class SGP40Component;
|
||||||
|
|
||||||
/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors.
|
/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors.
|
||||||
class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice {
|
class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
||||||
|
@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2
|
||||||
/// Input sensor for humidity and temperature compensation.
|
/// Input sensor for humidity and temperature compensation.
|
||||||
sensor::Sensor *humidity_sensor_{nullptr};
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
sensor::Sensor *temperature_sensor_{nullptr};
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
int16_t sensirion_init_sensors_();
|
int16_t sensirion_init_sensors_();
|
||||||
int16_t sgp40_probe_();
|
int16_t sgp40_probe_();
|
||||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
|
||||||
uint64_t serial_number_;
|
uint64_t serial_number_;
|
||||||
uint16_t featureset_;
|
uint16_t featureset_;
|
||||||
int32_t measure_voc_index_();
|
int32_t measure_voc_index_();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
@ -13,10 +13,11 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
sht3xd_ns = cg.esphome_ns.namespace("sht3xd")
|
sht3xd_ns = cg.esphome_ns.namespace("sht3xd")
|
||||||
SHT3XDComponent = sht3xd_ns.class_(
|
SHT3XDComponent = sht3xd_ns.class_(
|
||||||
"SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice
|
"SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
|
|
|
@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000;
|
||||||
|
|
||||||
void SHT3XDComponent::setup() {
|
void SHT3XDComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up SHT3xD...");
|
ESP_LOGCONFIG(TAG, "Setting up SHT3xD...");
|
||||||
if (!this->write_command_(SHT3XD_COMMAND_READ_SERIAL_NUMBER)) {
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t raw_serial_number[2];
|
uint16_t raw_serial_number[2];
|
||||||
if (!this->read_data_(raw_serial_number, 2)) {
|
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA;
|
||||||
void SHT3XDComponent::update() {
|
void SHT3XDComponent::update() {
|
||||||
if (this->status_has_warning()) {
|
if (this->status_has_warning()) {
|
||||||
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
||||||
this->write_command_(SHT3XD_COMMAND_SOFT_RESET);
|
this->write_command(SHT3XD_COMMAND_SOFT_RESET);
|
||||||
}
|
}
|
||||||
if (!this->write_command_(SHT3XD_COMMAND_POLLING_H)) {
|
if (!this->write_command(SHT3XD_COMMAND_POLLING_H)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[2];
|
uint16_t raw_data[2];
|
||||||
if (!this->read_data_(raw_data, 2)) {
|
if (!this->read_data(raw_data, 2)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -71,56 +66,5 @@ void SHT3XDComponent::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SHT3XDComponent::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sht3xd
|
} // namespace sht3xd
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sht3xd {
|
namespace sht3xd {
|
||||||
|
|
||||||
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
||||||
class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
|
class SHT3XDComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||||
|
@ -19,9 +19,6 @@ class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
|
|
||||||
sensor::Sensor *temperature_sensor_;
|
sensor::Sensor *temperature_sensor_;
|
||||||
sensor::Sensor *humidity_sensor_;
|
sensor::Sensor *humidity_sensor_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
|
@ -16,10 +16,13 @@ from esphome.const import (
|
||||||
|
|
||||||
CODEOWNERS = ["@sjtrny"]
|
CODEOWNERS = ["@sjtrny"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
sht4x_ns = cg.esphome_ns.namespace("sht4x")
|
sht4x_ns = cg.esphome_ns.namespace("sht4x")
|
||||||
|
|
||||||
SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice)
|
SHT4XComponent = sht4x_ns.class_(
|
||||||
|
"SHT4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONF_PRECISION = "precision"
|
CONF_PRECISION = "precision"
|
||||||
SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION")
|
SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION")
|
||||||
|
|
|
@ -50,31 +50,28 @@ void SHT4XComponent::setup() {
|
||||||
void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); }
|
void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); }
|
||||||
|
|
||||||
void SHT4XComponent::update() {
|
void SHT4XComponent::update() {
|
||||||
uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]};
|
|
||||||
|
|
||||||
// Send command
|
// Send command
|
||||||
this->write(cmd, 1);
|
this->write_command(MEASURECOMMANDS[this->precision_]);
|
||||||
|
|
||||||
this->set_timeout(10, [this]() {
|
this->set_timeout(10, [this]() {
|
||||||
const uint8_t num_bytes = 6;
|
uint16_t buffer[2];
|
||||||
uint8_t buffer[num_bytes];
|
|
||||||
|
|
||||||
// Read measurement
|
// Read measurement
|
||||||
bool read_status = this->read_bytes_raw(buffer, num_bytes);
|
bool read_status = this->read_data(buffer, 2);
|
||||||
|
|
||||||
if (read_status) {
|
if (read_status) {
|
||||||
// Evaluate and publish measurements
|
// Evaluate and publish measurements
|
||||||
if (this->temp_sensor_ != nullptr) {
|
if (this->temp_sensor_ != nullptr) {
|
||||||
// Temp is contained in the first 16 bits
|
// Temp is contained in the first result word
|
||||||
float sensor_value_temp = (buffer[0] << 8) + buffer[1];
|
float sensor_value_temp = buffer[0];
|
||||||
float temp = -45 + 175 * sensor_value_temp / 65535;
|
float temp = -45 + 175 * sensor_value_temp / 65535;
|
||||||
|
|
||||||
this->temp_sensor_->publish_state(temp);
|
this->temp_sensor_->publish_state(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->humidity_sensor_ != nullptr) {
|
if (this->humidity_sensor_ != nullptr) {
|
||||||
// Relative humidity is in the last 16 bits
|
// Relative humidity is in the second result word
|
||||||
float sensor_value_rh = (buffer[3] << 8) + buffer[4];
|
float sensor_value_rh = buffer[1];
|
||||||
float rh = -6 + 125 * sensor_value_rh / 65535;
|
float rh = -6 + 125 * sensor_value_rh / 65535;
|
||||||
|
|
||||||
this->humidity_sensor_->publish_state(rh);
|
this->humidity_sensor_->publish_state(rh);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sht4x {
|
namespace sht4x {
|
||||||
|
@ -13,7 +13,7 @@ enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEA
|
||||||
|
|
||||||
enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 };
|
enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 };
|
||||||
|
|
||||||
class SHT4XComponent : public PollingComponent, public i2c::I2CDevice {
|
class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_HUMIDITY,
|
CONF_HUMIDITY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
@ -13,9 +13,12 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
shtcx_ns = cg.esphome_ns.namespace("shtcx")
|
shtcx_ns = cg.esphome_ns.namespace("shtcx")
|
||||||
SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice)
|
SHTCXComponent = shtcx_ns.class_(
|
||||||
|
"SHTCXComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
SHTCXType = shtcx_ns.enum("SHTCXType")
|
SHTCXType = shtcx_ns.enum("SHTCXType")
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,14 @@ void SHTCXComponent::setup() {
|
||||||
this->wake_up();
|
this->wake_up();
|
||||||
this->soft_reset();
|
this->soft_reset();
|
||||||
|
|
||||||
if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) {
|
if (!this->write_command(SHTCX_COMMAND_READ_ID_REGISTER)) {
|
||||||
ESP_LOGE(TAG, "Error requesting Device ID");
|
ESP_LOGE(TAG, "Error requesting Device ID");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t device_id_register;
|
uint16_t device_id_register;
|
||||||
if (!this->read_data_(&device_id_register, 1)) {
|
if (!this->read_data(&device_id_register, 1)) {
|
||||||
ESP_LOGE(TAG, "Error reading Device ID");
|
ESP_LOGE(TAG, "Error reading Device ID");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -76,7 +76,7 @@ void SHTCXComponent::update() {
|
||||||
if (this->type_ != SHTCX_TYPE_SHTC1) {
|
if (this->type_ != SHTCX_TYPE_SHTC1) {
|
||||||
this->wake_up();
|
this->wake_up();
|
||||||
}
|
}
|
||||||
if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) {
|
if (!this->write_command(SHTCX_COMMAND_POLLING_H)) {
|
||||||
ESP_LOGE(TAG, "sensor polling failed");
|
ESP_LOGE(TAG, "sensor polling failed");
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
this->temperature_sensor_->publish_state(NAN);
|
this->temperature_sensor_->publish_state(NAN);
|
||||||
|
@ -90,7 +90,7 @@ void SHTCXComponent::update() {
|
||||||
float temperature = NAN;
|
float temperature = NAN;
|
||||||
float humidity = NAN;
|
float humidity = NAN;
|
||||||
uint16_t raw_data[2];
|
uint16_t raw_data[2];
|
||||||
if (!this->read_data_(raw_data, 2)) {
|
if (!this->read_data(raw_data, 2)) {
|
||||||
ESP_LOGE(TAG, "sensor read failed");
|
ESP_LOGE(TAG, "sensor read failed");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,65 +110,14 @@ void SHTCXComponent::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SHTCXComponent::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SHTCXComponent::soft_reset() {
|
void SHTCXComponent::soft_reset() {
|
||||||
this->write_command_(SHTCX_COMMAND_SOFT_RESET);
|
this->write_command(SHTCX_COMMAND_SOFT_RESET);
|
||||||
delayMicroseconds(200);
|
delayMicroseconds(200);
|
||||||
}
|
}
|
||||||
void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); }
|
void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); }
|
||||||
|
|
||||||
void SHTCXComponent::wake_up() {
|
void SHTCXComponent::wake_up() {
|
||||||
this->write_command_(SHTCX_COMMAND_WAKEUP);
|
this->write_command(SHTCX_COMMAND_WAKEUP);
|
||||||
delayMicroseconds(200);
|
delayMicroseconds(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace shtcx {
|
namespace shtcx {
|
||||||
|
@ -10,7 +10,7 @@ namespace shtcx {
|
||||||
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
|
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
|
||||||
|
|
||||||
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
||||||
class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
|
class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||||
|
@ -24,8 +24,6 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
|
||||||
void wake_up();
|
void wake_up();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
SHTCXType type_;
|
SHTCXType type_;
|
||||||
uint16_t sensor_id_;
|
uint16_t sensor_id_;
|
||||||
sensor::Sensor *temperature_sensor_;
|
sensor::Sensor *temperature_sensor_;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor, sensirion_common
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_PM_1_0,
|
CONF_PM_1_0,
|
||||||
|
@ -26,9 +26,12 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
sps30_ns = cg.esphome_ns.namespace("sps30")
|
sps30_ns = cg.esphome_ns.namespace("sps30")
|
||||||
SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice)
|
SPS30Component = sps30_ns.class_(
|
||||||
|
"SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
|
|
|
@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
|
||||||
|
|
||||||
void SPS30Component::setup() {
|
void SPS30Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up sps30...");
|
ESP_LOGCONFIG(TAG, "Setting up sps30...");
|
||||||
this->write_command_(SPS30_CMD_SOFT_RESET);
|
this->write_command(SPS30_CMD_SOFT_RESET);
|
||||||
/// Deferred Sensor initialization
|
/// Deferred Sensor initialization
|
||||||
this->set_timeout(500, [this]() {
|
this->set_timeout(500, [this]() {
|
||||||
/// Firmware version identification
|
/// Firmware version identification
|
||||||
if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) {
|
if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
|
||||||
this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED;
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->read_data_(&raw_firmware_version_, 1)) {
|
|
||||||
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
|
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/// Serial number identification
|
/// Serial number identification
|
||||||
if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) {
|
|
||||||
this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED;
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t raw_serial_number[8];
|
uint16_t raw_serial_number[8];
|
||||||
if (!this->read_data_(raw_serial_number, 8)) {
|
if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) {
|
||||||
this->error_code_ = SERIAL_NUMBER_READ_FAILED;
|
this->error_code_ = SERIAL_NUMBER_READ_FAILED;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -109,7 +97,7 @@ void SPS30Component::update() {
|
||||||
/// Check if warning flag active (sensor reconnected?)
|
/// Check if warning flag active (sensor reconnected?)
|
||||||
if (this->status_has_warning()) {
|
if (this->status_has_warning()) {
|
||||||
ESP_LOGD(TAG, "Trying to reconnect the sensor...");
|
ESP_LOGD(TAG, "Trying to reconnect the sensor...");
|
||||||
if (this->write_command_(SPS30_CMD_SOFT_RESET)) {
|
if (this->write_command(SPS30_CMD_SOFT_RESET)) {
|
||||||
ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
|
ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
|
||||||
this->set_timeout(500, [this]() {
|
this->set_timeout(500, [this]() {
|
||||||
this->start_continuous_measurement_();
|
this->start_continuous_measurement_();
|
||||||
|
@ -124,13 +112,13 @@ void SPS30Component::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/// Check if measurement is ready before reading the value
|
/// Check if measurement is ready before reading the value
|
||||||
if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) {
|
if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t raw_read_status;
|
uint16_t raw_read_status;
|
||||||
if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) {
|
if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
|
||||||
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
|
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
|
||||||
this->skipped_data_read_cycles_++;
|
this->skipped_data_read_cycles_++;
|
||||||
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
|
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
|
||||||
|
@ -142,7 +130,7 @@ void SPS30Component::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) {
|
if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
|
||||||
ESP_LOGW(TAG, "Error reading measurement status!");
|
ESP_LOGW(TAG, "Error reading measurement status!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
@ -150,7 +138,7 @@ void SPS30Component::update() {
|
||||||
|
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[20];
|
uint16_t raw_data[20];
|
||||||
if (!this->read_data_(raw_data, 20)) {
|
if (!this->read_data(raw_data, 20)) {
|
||||||
ESP_LOGW(TAG, "Error reading measurement data!");
|
ESP_LOGW(TAG, "Error reading measurement data!");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
@ -205,69 +193,18 @@ void SPS30Component::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SPS30Component::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SPS30Component::start_continuous_measurement_() {
|
bool SPS30Component::start_continuous_measurement_() {
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
|
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
|
||||||
data[1] = 0x03;
|
data[1] = 0x03;
|
||||||
data[2] = 0x00;
|
data[2] = 0x00;
|
||||||
data[3] = sht_crc_(0x03, 0x00);
|
data[3] = sht_crc_(0x03, 0x00);
|
||||||
if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) {
|
if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
|
||||||
ESP_LOGE(TAG, "Error initiating measurements");
|
ESP_LOGE(TAG, "Error initiating measurements");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SPS30Component::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sps30
|
} // namespace sps30
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sps30 {
|
namespace sps30 {
|
||||||
|
|
||||||
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
|
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
|
||||||
/// PM1.0, PM2.5, PM4, PM10 Air Quality sensors.
|
/// PM1.0, PM2.5, PM4, PM10 Air Quality sensors.
|
||||||
class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
class SPS30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
|
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_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
|
||||||
|
@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
|
||||||
char serial_number_[17] = {0}; /// Terminating NULL character
|
char serial_number_[17] = {0}; /// Terminating NULL character
|
||||||
uint16_t raw_firmware_version_;
|
uint16_t raw_firmware_version_;
|
||||||
bool start_continuous_measurement_();
|
bool start_continuous_measurement_();
|
||||||
|
|
|
@ -8,6 +8,7 @@ from esphome.const import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensirion_common"]
|
||||||
|
|
||||||
sts3x_ns = cg.esphome_ns.namespace("sts3x")
|
sts3x_ns = cg.esphome_ns.namespace("sts3x")
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000;
|
||||||
|
|
||||||
void STS3XComponent::setup() {
|
void STS3XComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up STS3x...");
|
ESP_LOGCONFIG(TAG, "Setting up STS3x...");
|
||||||
if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
|
if (!this->write_command(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t raw_serial_number[2];
|
uint16_t raw_serial_number[2];
|
||||||
if (!this->read_data_(raw_serial_number, 1)) {
|
if (!this->read_data(raw_serial_number, 1)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA;
|
||||||
void STS3XComponent::update() {
|
void STS3XComponent::update() {
|
||||||
if (this->status_has_warning()) {
|
if (this->status_has_warning()) {
|
||||||
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
||||||
this->write_command_(STS3X_COMMAND_SOFT_RESET);
|
this->write_command(STS3X_COMMAND_SOFT_RESET);
|
||||||
}
|
}
|
||||||
if (!this->write_command_(STS3X_COMMAND_POLLING_H)) {
|
if (!this->write_command(STS3X_COMMAND_POLLING_H)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->set_timeout(50, [this]() {
|
this->set_timeout(50, [this]() {
|
||||||
uint16_t raw_data[1];
|
uint16_t raw_data[1];
|
||||||
if (!this->read_data_(raw_data, 1)) {
|
if (!this->read_data(raw_data, 1)) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,56 +67,5 @@ void STS3XComponent::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool STS3XComponent::write_command_(uint16_t command) {
|
|
||||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
|
||||||
return this->write_byte(command >> 8, command & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t sts3x_crc(uint8_t data1, uint8_t data2) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t crc = 0xFF;
|
|
||||||
|
|
||||||
crc ^= data1;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crc ^= data2;
|
|
||||||
for (bit = 8; bit > 0; --bit) {
|
|
||||||
if (crc & 0x80) {
|
|
||||||
crc = (crc << 1) ^ 0x131;
|
|
||||||
} else {
|
|
||||||
crc = (crc << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) {
|
|
||||||
const uint8_t num_bytes = len * 3;
|
|
||||||
std::vector<uint8_t> buf(num_bytes);
|
|
||||||
|
|
||||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < len; i++) {
|
|
||||||
const uint8_t j = 3 * i;
|
|
||||||
uint8_t crc = sts3x_crc(buf[j], buf[j + 1]);
|
|
||||||
if (crc != buf[j + 2]) {
|
|
||||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sts3x
|
} // namespace sts3x
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -2,22 +2,18 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace sts3x {
|
namespace sts3x {
|
||||||
|
|
||||||
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
|
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
|
||||||
class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
protected:
|
|
||||||
bool write_command_(uint16_t command);
|
|
||||||
bool read_data_(uint16_t *data, uint8_t len);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sts3x
|
} // namespace sts3x
|
||||||
|
|
|
@ -644,6 +644,7 @@ CONF_STEP_MODE = "step_mode"
|
||||||
CONF_STEP_PIN = "step_pin"
|
CONF_STEP_PIN = "step_pin"
|
||||||
CONF_STOP = "stop"
|
CONF_STOP = "stop"
|
||||||
CONF_STOP_ACTION = "stop_action"
|
CONF_STOP_ACTION = "stop_action"
|
||||||
|
CONF_STORE_BASELINE = "store_baseline"
|
||||||
CONF_SUBNET = "subnet"
|
CONF_SUBNET = "subnet"
|
||||||
CONF_SUBSTITUTIONS = "substitutions"
|
CONF_SUBSTITUTIONS = "substitutions"
|
||||||
CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action"
|
CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action"
|
||||||
|
@ -680,6 +681,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi
|
||||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic"
|
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic"
|
||||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic"
|
CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic"
|
||||||
CONF_TEMPERATURE = "temperature"
|
CONF_TEMPERATURE = "temperature"
|
||||||
|
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
||||||
CONF_TEMPERATURE_STEP = "temperature_step"
|
CONF_TEMPERATURE_STEP = "temperature_step"
|
||||||
CONF_TEXT_SENSORS = "text_sensors"
|
CONF_TEXT_SENSORS = "text_sensors"
|
||||||
CONF_THEN = "then"
|
CONF_THEN = "then"
|
||||||
|
|
Loading…
Reference in a new issue