Add Support for Sensirion SFA30 sensor (#5519)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
ghsensdev 2023-10-15 20:49:57 +02:00 committed by GitHub
parent 7ddcdab351
commit 6143099f60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 0 deletions

View file

@ -254,6 +254,7 @@ esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras esphome/components/sen5x/* @martgras
esphome/components/sensirion_common/* @martgras esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core esphome/components/sensor/* @esphome/core
esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @SenexCrenshaw @martgras esphome/components/sgp4x/* @SenexCrenshaw @martgras
esphome/components/shelly_dimmer/* @edge90 @rnauber esphome/components/shelly_dimmer/* @edge90 @rnauber

View file

@ -0,0 +1 @@
CODEOWNERS = ["@ghsensdev"]

View file

@ -0,0 +1,78 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_FORMALDEHYDE,
CONF_HUMIDITY,
CONF_TEMPERATURE,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_RADIATOR,
ICON_WATER_PERCENT,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_BILLION,
UNIT_PERCENT,
UNIT_CELSIUS,
)
CODEOWNERS = ["@ghsensdev"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sfa30_ns = cg.esphome_ns.namespace("sfa30")
SFA30Component = sfa30_ns.class_(
"SFA30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(SFA30Component),
cv.Optional(CONF_FORMALDEHYDE): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
icon=ICON_RADIATOR,
accuracy_decimals=1,
device_class=DEVICE_CLASS_GAS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x5D))
)
SENSOR_MAP = {
CONF_FORMALDEHYDE: "set_formaldehyde_sensor",
CONF_HUMIDITY: "set_humidity_sensor",
CONF_TEMPERATURE: "set_temperature_sensor",
}
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
for key, funcName in SENSOR_MAP.items():
if sensor_config := config.get(key):
sens = await sensor.new_sensor(sensor_config)
cg.add(getattr(var, funcName)(sens))

View file

@ -0,0 +1,99 @@
#include "sfa30.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sfa30 {
static const char *const TAG = "sfa30";
static const uint16_t SFA30_CMD_GET_DEVICE_MARKING = 0xD060;
static const uint16_t SFA30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0006;
static const uint16_t SFA30_CMD_READ_MEASUREMENT = 0x0327;
void SFA30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up sfa30...");
// Serial Number identification
uint16_t raw_device_marking[16];
if (!this->get_register(SFA30_CMD_GET_DEVICE_MARKING, raw_device_marking, 16, 5)) {
ESP_LOGE(TAG, "Failed to read device marking");
this->error_code_ = DEVICE_MARKING_READ_FAILED;
this->mark_failed();
return;
}
for (size_t i = 0; i < 16; i++) {
this->device_marking_[i * 2] = static_cast<char>(raw_device_marking[i] >> 8);
this->device_marking_[i * 2 + 1] = static_cast<char>(raw_device_marking[i] & 0xFF);
}
ESP_LOGD(TAG, "Device Marking: '%s'", this->device_marking_);
if (!this->write_command(SFA30_CMD_START_CONTINUOUS_MEASUREMENTS)) {
ESP_LOGE(TAG, "Error starting measurements.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
return;
}
ESP_LOGD(TAG, "Sensor initialized");
}
void SFA30Component::dump_config() {
ESP_LOGCONFIG(TAG, "sfa30:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
switch (this->error_code_) {
case DEVICE_MARKING_READ_FAILED:
ESP_LOGW(TAG, "Unable to read device marking!");
break;
case MEASUREMENT_INIT_FAILED:
ESP_LOGW(TAG, "Measurement initialization failed!");
break;
default:
ESP_LOGW(TAG, "Unknown setup error!");
break;
}
}
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Device Marking: '%s'", this->device_marking_);
LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
void SFA30Component::update() {
if (!this->write_command(SFA30_CMD_READ_MEASUREMENT)) {
ESP_LOGW(TAG, "Error reading measurement!");
this->status_set_warning();
return;
}
this->set_timeout(5, [this]() {
uint16_t raw_data[3];
if (!this->read_data(raw_data, 3)) {
ESP_LOGW(TAG, "Error reading measurement data!");
this->status_set_warning();
return;
}
if (this->formaldehyde_sensor_ != nullptr) {
const float formaldehyde = raw_data[0] / 5.0f;
this->formaldehyde_sensor_->publish_state(formaldehyde);
}
if (this->humidity_sensor_ != nullptr) {
const float humidity = raw_data[1] / 100.0f;
this->humidity_sensor_->publish_state(humidity);
}
if (this->temperature_sensor_ != nullptr) {
const float temperature = raw_data[2] / 200.0f;
this->temperature_sensor_->publish_state(temperature);
}
this->status_clear_warning();
});
}
} // namespace sfa30
} // namespace esphome

View file

@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/sensirion_common/i2c_sensirion.h"
namespace esphome {
namespace sfa30 {
class SFA30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
enum ErrorCode { DEVICE_MARKING_READ_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN };
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;
void dump_config() override;
void update() override;
void set_formaldehyde_sensor(sensor::Sensor *formaldehyde) { this->formaldehyde_sensor_ = formaldehyde; }
void set_humidity_sensor(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
void set_temperature_sensor(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
protected:
char device_marking_[32] = {0};
ErrorCode error_code_{UNKNOWN};
sensor::Sensor *formaldehyde_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
};
} // namespace sfa30
} // namespace esphome

View file

@ -1083,6 +1083,16 @@ sensor:
ambient_pressure_compensation: 961mBar ambient_pressure_compensation: 961mBar
temperature_offset: 4.2C temperature_offset: 4.2C
i2c_id: i2c_bus i2c_id: i2c_bus
- platform: sfa30
formaldehyde:
name: "SFA30 formaldehyde"
temperature:
name: "SFA30 temperature"
humidity:
name: "SFA30 humidity"
i2c_id: i2c_bus
address: 0x5D
update_interval: 30s
- platform: sen0321 - platform: sen0321
name: Workshop Ozone Sensor name: Workshop Ozone Sensor
id: sen0321_ozone id: sen0321_ozone