Refactor Sensirion Sensors (#3374)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Martin 2022-04-13 00:19:48 +02:00 committed by GitHub
parent 99335d986e
commit d620b6dd5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 484 additions and 718 deletions

View file

@ -170,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core
esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny

View file

@ -15,6 +15,7 @@ enum ErrorCode {
ERROR_NOT_INITIALIZED = 4,
ERROR_TOO_LARGE = 5,
ERROR_UNKNOWN = 6,
ERROR_CRC = 7,
};
struct ReadBuffer {

View file

@ -33,14 +33,8 @@ void SCD30Component::setup() {
#endif
/// 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];
if (!this->read_data_(raw_firmware_version, 3)) {
if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
this->mark_failed();
return;
@ -49,7 +43,7 @@ void SCD30Component::setup() {
uint16_t(raw_firmware_version[0] & 0xFF));
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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -69,7 +63,7 @@ void SCD30Component::setup() {
delay(30);
#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.");
this->error_code_ = MEASUREMENT_INIT_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
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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -92,7 +86,7 @@ void SCD30Component::setup() {
delay(30);
#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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -103,7 +97,7 @@ void SCD30Component::setup() {
#endif
/// 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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -151,14 +145,14 @@ void SCD30Component::dump_config() {
}
void SCD30Component::update() {
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
uint16_t raw_read_status;
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
this->status_set_warning();
ESP_LOGW(TAG, "Data not ready yet!");
return;
}
if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement!");
this->status_set_warning();
return;
@ -166,7 +160,7 @@ void SCD30Component::update() {
this->set_timeout(50, [this]() {
uint16_t raw_data[6];
if (!this->read_data_(raw_data, 6)) {
if (!this->read_data(raw_data, 6)) {
this->status_set_warning();
return;
}
@ -197,77 +191,16 @@ void SCD30Component::update() {
}
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;
}
delay(4);
uint16_t is_data_ready;
if (!this->read_data_(&is_data_ready, 1)) {
if (!this->read_data(&is_data_ready, 1)) {
return false;
}
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 esphome

View file

@ -2,13 +2,13 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace scd30 {
/// 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:
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
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; }
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_();
enum ErrorCode {

View file

@ -2,6 +2,7 @@ from esphome import core
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
CONF_ID,
CONF_HUMIDITY,
@ -18,9 +19,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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_ALTITUDE_COMPENSATION = "altitude_compensation"

View file

@ -25,15 +25,8 @@ void SCD4XComponent::setup() {
// the sensor needs 1000 ms to enter the idle state
this->set_timeout(1000, [this]() {
// Check if measurement is ready before reading the value
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_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)) {
uint16_t raw_read_status;
if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) {
ESP_LOGE(TAG, "Failed to read data ready status");
this->mark_failed();
return;
@ -41,9 +34,9 @@ void SCD4XComponent::setup() {
uint32_t stop_measurement_delay = 0;
// 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");
if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) {
if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
ESP_LOGE(TAG, "Failed to stop measurements");
this->mark_failed();
return;
@ -53,15 +46,8 @@ void SCD4XComponent::setup() {
stop_measurement_delay = 500;
}
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];
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");
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
this->mark_failed();
@ -70,7 +56,7 @@ void SCD4XComponent::setup() {
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));
if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET,
if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET,
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
ESP_LOGE(TAG, "Error setting temperature offset.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
@ -88,7 +74,7 @@ void SCD4XComponent::setup() {
return;
}
} 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.");
this->error_code_ = MEASUREMENT_INIT_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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -104,7 +90,7 @@ void SCD4XComponent::setup() {
}
// 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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -164,19 +150,19 @@ void SCD4XComponent::update() {
}
// 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();
return;
}
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
uint16_t raw_read_status;
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
this->status_set_warning();
ESP_LOGW(TAG, "Data not ready yet!");
return;
}
if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement!");
this->status_set_warning();
return;
@ -184,7 +170,7 @@ void SCD4XComponent::update() {
// Read off sensor data
uint16_t raw_data[3];
if (!this->read_data_(raw_data, 3)) {
if (!this->read_data(raw_data, 3)) {
this->status_set_warning();
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) {
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);
return true;
} 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 esphome

View file

@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace scd4x {
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:
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;
@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
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);
ERRORCODE error_code_;

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
CONF_ID,
CONF_CO2,
@ -21,9 +21,12 @@ from esphome.const import (
CODEOWNERS = ["@sjtrny"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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_ALTITUDE_COMPENSATION = "altitude_compensation"

View file

@ -7,55 +7,50 @@ namespace esphome {
namespace sdp3x {
static const char *const TAG = "sdp3x.sensor";
static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
static const uint16_t SDP3X_SOFT_RESET = 0x0006;
static const uint16_t SDP3X_READ_ID1 = 0x367C;
static const uint16_t SDP3X_READ_ID2 = 0xE102;
static const uint16_t SDP3X_START_DP_AVG = 0x3615;
static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603;
static const uint16_t SDP3X_STOP_MEAS = 0x3FF9;
void SDP3XComponent::update() { this->read_pressure_(); }
void SDP3XComponent::setup() {
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
}
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
}
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!");
this->mark_failed();
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!");
this->mark_failed();
return;
}
uint8_t data[18];
if (this->read(data, 18) != i2c::ERROR_OK) {
uint16_t data[6];
if (this->read_data(data, 6) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID SDP3X failed!");
this->mark_failed();
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
// ref:
// 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) {
switch (data[3]) {
if (data[1] >> 8 == 0x02) {
switch (data[1] & 0xFF) {
case 0x01: // SDP800-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
break;
@ -75,15 +70,16 @@ void SDP3XComponent::setup() {
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
break;
}
} else if (data[2] == 0x01) {
if (data[3] == 0x01) {
} else if (data[1] >> 8 == 0x01) {
if ((data[1] & 0xFF) == 0x01) {
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");
}
}
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!");
this->mark_failed();
return;
@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() {
}
void SDP3XComponent::read_pressure_() {
uint8_t data[9];
if (this->read(data, 9) != i2c::ERROR_OK) {
uint16_t data[3];
if (this->read_data(data, 3) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Couldn't read SDP3X data!");
this->status_set_warning();
return;
}
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) {
ESP_LOGW(TAG, "Invalid SDP3X data!");
this->status_set_warning();
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]);
int16_t pressure_raw = data[0];
int16_t temperature_raw = data[1];
int16_t scale_factor_raw = data[2];
// scale factor is in Pa - convert to hPa
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,
@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() {
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 esphome

View file

@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sdp3x {
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:
/// Schedule temperature+pressure readings.
void update() override;
@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
protected:
/// Internal method to read the pressure from the component after it has been scheduled.
void read_pressure_();
bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
MeasurementMode measurement_mode_;
};

View file

@ -1,6 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import sensirion_common
from esphome.const import (
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
@ -8,10 +9,13 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
CODEOWNERS = ["@Azimath"]
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")

View 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)

View 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

View 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

View file

@ -1,10 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_BASELINE,
CONF_ECO2,
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
CONF_TVOC,
ICON_RADIATOR,
DEVICE_CLASS_CARBON_DIOXIDE,
@ -17,17 +20,19 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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_TVOC_BASELINE = "tvoc_baseline"
CONF_STORE_BASELINE = "store_baseline"
CONF_UPTIME = "uptime"
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONFIG_SCHEMA = (
cv.Schema(

View file

@ -36,14 +36,8 @@ void SGP30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
// 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];
if (!this->read_data_(raw_serial_number, 3)) {
if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) {
this->mark_failed();
return;
}
@ -52,16 +46,12 @@ void SGP30Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// 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();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if (uint16_t(this->featureset_ >> 12) != 0x0) {
if (uint16_t(this->featureset_ >> 12) == 0x1) {
// 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));
// 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.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() {
void SGP30Component::read_iaq_baseline_() {
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");
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@ -274,14 +264,14 @@ void SGP30Component::dump_config() {
}
void SGP30Component::update() {
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) {
this->status_set_warning();
return;
}
this->seconds_since_last_store_ += this->update_interval_ / 1000;
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
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 esphome

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.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 <cmath>
@ -15,7 +15,7 @@ struct SGP30Baselines {
} PACKED;
/// 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:
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
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; }
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
void send_env_data_();
void read_iaq_baseline_();
bool is_sensor_baseline_reliable_();
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_;
uint16_t featureset_;
uint32_t required_warm_up_time_;

View file

@ -1,25 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
ICON_RADIATOR,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
CODEOWNERS = ["@SenexCrenshaw"]
sgp40_ns = cg.esphome_ns.namespace("sgp40")
SGP40Component = sgp40_ns.class_(
"SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
"SGP40Component",
sensor.Sensor,
cg.PollingComponent,
sensirion_common.SensirionI2CDevice,
)
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONF_STORE_BASELINE = "store_baseline"
CONF_VOC_BASELINE = "voc_baseline"
CONFIG_SCHEMA = (

View file

@ -12,14 +12,14 @@ void SGP40Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
// 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->mark_failed();
return;
}
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();
return;
}
@ -28,19 +28,19 @@ void SGP40Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// 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");
this->mark_failed();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
uint16_t raw_featureset;
if (!this->read_data(raw_featureset)) {
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
SGP40_FEATURESET);
@ -95,21 +95,21 @@ void SGP40Component::setup() {
void SGP40Component::self_test_() {
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;
ESP_LOGD(TAG, "Self-test communication failed");
this->mark_failed();
}
this->set_timeout(250, [this]() {
uint16_t reply[1];
if (!this->read_data_(reply, 1)) {
uint16_t reply;
if (!this->read_data(reply)) {
ESP_LOGD(TAG, "Self-test read_data_ failed");
this->mark_failed();
return;
}
if (reply[0] == 0xD400) {
if (reply == 0xD400) {
this->self_test_complete_ = true;
ESP_LOGD(TAG, "Self-test completed");
return;
@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() {
temperature = 25;
}
uint8_t command[8];
command[0] = 0x26;
command[1] = 0x0F;
uint16_t data[2];
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);
command[5] = tempticks >> 8;
command[6] = tempticks & 0xFF;
command[7] = generate_crc_(command + 5, 2);
// first paramater is the relative humidity ticks
data[0] = rhticks;
// 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();
ESP_LOGD(TAG, "write error");
return UINT16_MAX;
ESP_LOGD(TAG, "write error (%d)", this->last_error_);
return false;
}
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();
ESP_LOGD(TAG, "read_data_ error");
return UINT16_MAX;
}
return raw_data[0];
}
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;
return raw_data;
}
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 esphome

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.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/preferences.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_FEATURESET = 0x202f;
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.
// Prevents wear of the flash because of too many write operations
@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50;
class SGP40Component;
/// 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:
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
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.
sensor::Sensor *humidity_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 sgp40_probe_();
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_;
uint16_t featureset_;
int32_t measure_voc_index_();

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
@ -13,10 +13,11 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sht3xd_ns = cg.esphome_ns.namespace("sht3xd")
SHT3XDComponent = sht3xd_ns.class_(
"SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice
"SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONFIG_SCHEMA = (

View file

@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000;
void SHT3XDComponent::setup() {
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];
if (!this->read_data_(raw_serial_number, 2)) {
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
this->mark_failed();
return;
}
@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA;
void SHT3XDComponent::update() {
if (this->status_has_warning()) {
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();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
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 esphome

View file

@ -2,13 +2,13 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sht3xd {
/// 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:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_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;
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
sensor::Sensor *temperature_sensor_;
sensor::Sensor *humidity_sensor_;
};

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_TEMPERATURE,
@ -16,10 +16,13 @@ from esphome.const import (
CODEOWNERS = ["@sjtrny"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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"
SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION")

View file

@ -50,31 +50,28 @@ void SHT4XComponent::setup() {
void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); }
void SHT4XComponent::update() {
uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]};
// Send command
this->write(cmd, 1);
this->write_command(MEASURECOMMANDS[this->precision_]);
this->set_timeout(10, [this]() {
const uint8_t num_bytes = 6;
uint8_t buffer[num_bytes];
uint16_t buffer[2];
// Read measurement
bool read_status = this->read_bytes_raw(buffer, num_bytes);
bool read_status = this->read_data(buffer, 2);
if (read_status) {
// Evaluate and publish measurements
if (this->temp_sensor_ != nullptr) {
// Temp is contained in the first 16 bits
float sensor_value_temp = (buffer[0] << 8) + buffer[1];
// Temp is contained in the first result word
float sensor_value_temp = buffer[0];
float temp = -45 + 175 * sensor_value_temp / 65535;
this->temp_sensor_->publish_state(temp);
}
if (this->humidity_sensor_ != nullptr) {
// Relative humidity is in the last 16 bits
float sensor_value_rh = (buffer[3] << 8) + buffer[4];
// Relative humidity is in the second result word
float sensor_value_rh = buffer[1];
float rh = -6 + 125 * sensor_value_rh / 65535;
this->humidity_sensor_->publish_state(rh);

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
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 };
class SHT4XComponent : public PollingComponent, public i2c::I2CDevice {
class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
@ -13,9 +13,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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")

View file

@ -29,14 +29,14 @@ void SHTCXComponent::setup() {
this->wake_up();
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");
this->mark_failed();
return;
}
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");
this->mark_failed();
return;
@ -76,7 +76,7 @@ void SHTCXComponent::update() {
if (this->type_ != SHTCX_TYPE_SHTC1) {
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");
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(NAN);
@ -90,7 +90,7 @@ void SHTCXComponent::update() {
float temperature = NAN;
float humidity = NAN;
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");
this->status_set_warning();
} 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() {
this->write_command_(SHTCX_COMMAND_SOFT_RESET);
this->write_command(SHTCX_COMMAND_SOFT_RESET);
delayMicroseconds(200);
}
void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); }
void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); }
void SHTCXComponent::wake_up() {
this->write_command_(SHTCX_COMMAND_WAKEUP);
this->write_command(SHTCX_COMMAND_WAKEUP);
delayMicroseconds(200);
}

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace shtcx {
@ -10,7 +10,7 @@ namespace shtcx {
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.
class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_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();
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
SHTCXType type_;
uint16_t sensor_id_;
sensor::Sensor *temperature_sensor_;

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_PM_1_0,
@ -26,9 +26,12 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
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 = (
cv.Schema(

View file

@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
void SPS30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up sps30...");
this->write_command_(SPS30_CMD_SOFT_RESET);
this->write_command(SPS30_CMD_SOFT_RESET);
/// Deferred Sensor initialization
this->set_timeout(500, [this]() {
/// Firmware version identification
if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) {
this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED;
this->mark_failed();
return;
}
if (!this->read_data_(&raw_firmware_version_, 1)) {
if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
this->mark_failed();
return;
}
/// 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];
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->mark_failed();
return;
@ -109,7 +97,7 @@ void SPS30Component::update() {
/// Check if warning flag active (sensor reconnected?)
if (this->status_has_warning()) {
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...");
this->set_timeout(500, [this]() {
this->start_continuous_measurement_();
@ -124,13 +112,13 @@ void SPS30Component::update() {
return;
}
/// 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();
return;
}
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.");
this->skipped_data_read_cycles_++;
/// 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;
}
if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) {
if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement status!");
this->status_set_warning();
return;
@ -150,7 +138,7 @@ void SPS30Component::update() {
this->set_timeout(50, [this]() {
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!");
this->status_set_warning();
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_() {
uint8_t data[4];
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
data[1] = 0x03;
data[2] = 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");
return false;
}
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 esphome

View file

@ -2,14 +2,14 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sps30 {
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
/// 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:
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; }
@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice {
float get_setup_priority() const override { return setup_priority::DATA; }
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
uint16_t raw_firmware_version_;
bool start_continuous_measurement_();

View file

@ -8,6 +8,7 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sts3x_ns = cg.esphome_ns.namespace("sts3x")

View file

@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000;
void STS3XComponent::setup() {
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();
return;
}
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();
return;
}
@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA;
void STS3XComponent::update() {
if (this->status_has_warning()) {
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();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[1];
if (!this->read_data_(raw_data, 1)) {
if (!this->read_data(raw_data, 1)) {
this->status_set_warning();
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 esphome

View file

@ -2,22 +2,18 @@
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sts3x {
/// 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:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
};
} // namespace sts3x

View file

@ -644,6 +644,7 @@ CONF_STEP_MODE = "step_mode"
CONF_STEP_PIN = "step_pin"
CONF_STOP = "stop"
CONF_STOP_ACTION = "stop_action"
CONF_STORE_BASELINE = "store_baseline"
CONF_SUBNET = "subnet"
CONF_SUBSTITUTIONS = "substitutions"
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_STATE_TOPIC = "target_temperature_state_topic"
CONF_TEMPERATURE = "temperature"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONF_TEMPERATURE_STEP = "temperature_step"
CONF_TEXT_SENSORS = "text_sensors"
CONF_THEN = "then"