mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add Support for Sensirion SFA30 sensor (#5519)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/sfa30/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/sfa30/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@ghsensdev"] | ||||||
							
								
								
									
										78
									
								
								esphome/components/sfa30/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								esphome/components/sfa30/sensor.py
									
									
									
									
									
										Normal 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)) | ||||||
							
								
								
									
										99
									
								
								esphome/components/sfa30/sfa30.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								esphome/components/sfa30/sfa30.cpp
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										34
									
								
								esphome/components/sfa30/sfa30.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/sfa30/sfa30.h
									
									
									
									
									
										Normal 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 | ||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user