mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +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/sensirion_common/* @martgras | ||||
| esphome/components/sensor/* @esphome/core | ||||
| esphome/components/sfa30/* @ghsensdev | ||||
| esphome/components/sgp40/* @SenexCrenshaw | ||||
| esphome/components/sgp4x/* @SenexCrenshaw @martgras | ||||
| 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 | ||||
|     temperature_offset: 4.2C | ||||
|     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 | ||||
|     name: Workshop Ozone Sensor | ||||
|     id: sen0321_ozone | ||||
|   | ||||
		Reference in New Issue
	
	Block a user