mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	| @@ -17,6 +17,7 @@ esphome/components/adc/* @esphome/core | ||||
| esphome/components/adc128s102/* @DeerMaximum | ||||
| esphome/components/addressable_light/* @justfalter | ||||
| esphome/components/airthings_ble/* @jeromelaban | ||||
| esphome/components/airthings_wave_base/* @jeromelaban @ncareau | ||||
| esphome/components/airthings_wave_mini/* @ncareau | ||||
| esphome/components/airthings_wave_plus/* @jeromelaban | ||||
| esphome/components/alarm_control_panel/* @grahambrown11 | ||||
|   | ||||
							
								
								
									
										83
									
								
								esphome/components/airthings_wave_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								esphome/components/airthings_wave_base/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, ble_client | ||||
|  | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PERCENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_TVOC, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     ICON_RADIATOR, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@ncareau", "@jeromelaban"] | ||||
|  | ||||
| DEPENDENCIES = ["ble_client"] | ||||
|  | ||||
| airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base") | ||||
| AirthingsWaveBase = airthings_wave_base_ns.class_( | ||||
|     "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode | ||||
| ) | ||||
|  | ||||
|  | ||||
| BASE_SCHEMA = ( | ||||
|     sensor.SENSOR_SCHEMA.extend( | ||||
|         { | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PERCENT, | ||||
|                 device_class=DEVICE_CLASS_HUMIDITY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 accuracy_decimals=0, | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_PRESSURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TVOC): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PARTS_PER_BILLION, | ||||
|                 icon=ICON_RADIATOR, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("5min")) | ||||
|     .extend(ble_client.BLE_CLIENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def wave_base_to_code(var, config): | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     await ble_client.register_ble_node(var, config) | ||||
|  | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_PRESSURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_PRESSURE]) | ||||
|         cg.add(var.set_pressure(sens)) | ||||
|     if CONF_TVOC in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TVOC]) | ||||
|         cg.add(var.set_tvoc(sens)) | ||||
| @@ -0,0 +1,83 @@ | ||||
| #include "airthings_wave_base.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace airthings_wave_base { | ||||
|  | ||||
| static const char *const TAG = "airthings_wave_base"; | ||||
|  | ||||
| void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                                             esp_ble_gattc_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       if (param->open.status == ESP_GATT_OK) { | ||||
|         ESP_LOGI(TAG, "Connected successfully!"); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|       ESP_LOGW(TAG, "Disconnected!"); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       this->handle_ = 0; | ||||
|       auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); | ||||
|       if (chr == nullptr) { | ||||
|         ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), | ||||
|                  this->sensors_data_characteristic_uuid_.to_string().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       this->handle_ = chr->handle; | ||||
|       this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; | ||||
|  | ||||
|       this->request_read_values_(); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (param->read.conn_id != this->parent()->get_conn_id()) | ||||
|         break; | ||||
|       if (param->read.status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||||
|         break; | ||||
|       } | ||||
|       if (param->read.handle == this->handle_) { | ||||
|         this->read_sensors(param->read.value, param->read.value_len); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } | ||||
|  | ||||
| void AirthingsWaveBase::update() { | ||||
|   if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { | ||||
|     if (!this->parent()->enabled) { | ||||
|       ESP_LOGW(TAG, "Reconnecting to device"); | ||||
|       this->parent()->set_enabled(true); | ||||
|       this->parent()->connect(); | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Connection in progress"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWaveBase::request_read_values_() { | ||||
|   auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, | ||||
|                                         ESP_GATT_AUTH_REQ_NONE); | ||||
|   if (status) { | ||||
|     ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace airthings_wave_base | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
							
								
								
									
										50
									
								
								esphome/components/airthings_wave_base/airthings_wave_base.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								esphome/components/airthings_wave_base/airthings_wave_base.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <esp_gattc_api.h> | ||||
| #include <algorithm> | ||||
| #include <iterator> | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace airthings_wave_base { | ||||
|  | ||||
| class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode { | ||||
|  public: | ||||
|   AirthingsWaveBase() = default; | ||||
|  | ||||
|   void update() override; | ||||
|  | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|  | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||||
|   void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } | ||||
|   void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_valid_voc_value_(uint16_t voc); | ||||
|  | ||||
|   virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0; | ||||
|   void request_read_values_(); | ||||
|  | ||||
|   sensor::Sensor *temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *humidity_sensor_{nullptr}; | ||||
|   sensor::Sensor *pressure_sensor_{nullptr}; | ||||
|   sensor::Sensor *tvoc_sensor_{nullptr}; | ||||
|  | ||||
|   uint16_t handle_; | ||||
|   esp32_ble_tracker::ESPBTUUID service_uuid_; | ||||
|   esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; | ||||
| }; | ||||
|  | ||||
| }  // namespace airthings_wave_base | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif  // USE_ESP32 | ||||
| @@ -7,105 +7,47 @@ namespace airthings_wave_mini { | ||||
|  | ||||
| static const char *const TAG = "airthings_wave_mini"; | ||||
|  | ||||
| void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                                             esp_ble_gattc_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       if (param->open.status == ESP_GATT_OK) { | ||||
|         ESP_LOGI(TAG, "Connected successfully!"); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|       ESP_LOGW(TAG, "Disconnected!"); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       this->handle_ = 0; | ||||
|       auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); | ||||
|       if (chr == nullptr) { | ||||
|         ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), | ||||
|                  sensors_data_characteristic_uuid_.to_string().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       this->handle_ = chr->handle; | ||||
|       this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; | ||||
|  | ||||
|       request_read_values_(); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (param->read.conn_id != this->parent()->get_conn_id()) | ||||
|         break; | ||||
|       if (param->read.status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||||
|         break; | ||||
|       } | ||||
|       if (param->read.handle == this->handle_) { | ||||
|         read_sensors_(param->read.value, param->read.value_len); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { | ||||
| void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) { | ||||
|   auto *value = (WaveMiniReadings *) raw_value; | ||||
|  | ||||
|   if (sizeof(WaveMiniReadings) <= value_len) { | ||||
|     if (this->humidity_sensor_ != nullptr) { | ||||
|       this->humidity_sensor_->publish_state(value->humidity / 100.0f); | ||||
|     } | ||||
|  | ||||
|     if (this->pressure_sensor_ != nullptr) { | ||||
|       this->pressure_sensor_->publish_state(value->pressure / 50.0f); | ||||
|     } | ||||
|  | ||||
|     if (this->temperature_sensor_ != nullptr) { | ||||
|       this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); | ||||
|     if (is_valid_voc_value_(value->voc)) { | ||||
|     } | ||||
|  | ||||
|     if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { | ||||
|       this->tvoc_sensor_->publish_state(value->voc); | ||||
|     } | ||||
|  | ||||
|     // This instance must not stay connected | ||||
|     // so other clients can connect to it (e.g. the | ||||
|     // mobile app). | ||||
|     parent()->set_enabled(false); | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } | ||||
|  | ||||
| void AirthingsWaveMini::update() { | ||||
|   if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { | ||||
|     if (!parent()->enabled) { | ||||
|       ESP_LOGW(TAG, "Reconnecting to device"); | ||||
|       parent()->set_enabled(true); | ||||
|       parent()->connect(); | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Connection in progress"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWaveMini::request_read_values_() { | ||||
|   auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, | ||||
|                                         ESP_GATT_AUTH_REQ_NONE); | ||||
|   if (status) { | ||||
|     ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); | ||||
|     this->parent()->set_enabled(false); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWaveMini::dump_config() { | ||||
|   // these really don't belong here, but there doesn't seem to be a | ||||
|   // practical way to have the base class use LOG_SENSOR and include | ||||
|   // the TAG from this component | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
|   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||
|   LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_); | ||||
| } | ||||
|  | ||||
| AirthingsWaveMini::AirthingsWaveMini() | ||||
|     : PollingComponent(10000), | ||||
|       service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), | ||||
|       sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} | ||||
| AirthingsWaveMini::AirthingsWaveMini() { | ||||
|   this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); | ||||
|   this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); | ||||
| } | ||||
|  | ||||
| }  // namespace airthings_wave_mini | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,14 +2,7 @@ | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <esp_gattc_api.h> | ||||
| #include <algorithm> | ||||
| #include <iterator> | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/components/airthings_wave_base/airthings_wave_base.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace airthings_wave_mini { | ||||
| @@ -17,35 +10,14 @@ namespace airthings_wave_mini { | ||||
| static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; | ||||
| static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; | ||||
|  | ||||
| class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { | ||||
| class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { | ||||
|  public: | ||||
|   AirthingsWaveMini(); | ||||
|  | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|  | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|  | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||||
|   void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } | ||||
|   void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_valid_voc_value_(uint16_t voc); | ||||
|  | ||||
|   void read_sensors_(uint8_t *value, uint16_t value_len); | ||||
|   void request_read_values_(); | ||||
|  | ||||
|   sensor::Sensor *temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *humidity_sensor_{nullptr}; | ||||
|   sensor::Sensor *pressure_sensor_{nullptr}; | ||||
|   sensor::Sensor *tvoc_sensor_{nullptr}; | ||||
|  | ||||
|   uint16_t handle_; | ||||
|   esp32_ble_tracker::ESPBTUUID service_uuid_; | ||||
|   esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; | ||||
|   void read_sensors(uint8_t *value, uint16_t value_len) override; | ||||
|  | ||||
|   struct WaveMiniReadings { | ||||
|     uint16_t unused01; | ||||
|   | ||||
| @@ -1,82 +1,28 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, ble_client | ||||
| from esphome.components import airthings_wave_base | ||||
|  | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PERCENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     CONF_ID, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_TVOC, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     ICON_RADIATOR, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["ble_client"] | ||||
| DEPENDENCIES = airthings_wave_base.DEPENDENCIES | ||||
|  | ||||
| AUTO_LOAD = ["airthings_wave_base"] | ||||
|  | ||||
| airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini") | ||||
| AirthingsWaveMini = airthings_wave_mini_ns.class_( | ||||
|     "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode | ||||
|     "AirthingsWaveMini", airthings_wave_base.AirthingsWaveBase | ||||
| ) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
| CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(AirthingsWaveMini), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PERCENT, | ||||
|                 device_class=DEVICE_CLASS_HUMIDITY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 accuracy_decimals=2, | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_PRESSURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TVOC): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PARTS_PER_BILLION, | ||||
|                 icon=ICON_RADIATOR, | ||||
|                 accuracy_decimals=0, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|     } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("5min")) | ||||
|     .extend(ble_client.BLE_CLIENT_SCHEMA), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     await ble_client.register_ble_node(var, config) | ||||
|  | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_PRESSURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_PRESSURE]) | ||||
|         cg.add(var.set_pressure(sens)) | ||||
|     if CONF_TVOC in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TVOC]) | ||||
|         cg.add(var.set_tvoc(sens)) | ||||
|     await airthings_wave_base.wave_base_to_code(var, config) | ||||
|   | ||||
| @@ -7,55 +7,7 @@ namespace airthings_wave_plus { | ||||
|  | ||||
| static const char *const TAG = "airthings_wave_plus"; | ||||
|  | ||||
| void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                                             esp_ble_gattc_cb_param_t *param) { | ||||
|   switch (event) { | ||||
|     case ESP_GATTC_OPEN_EVT: { | ||||
|       if (param->open.status == ESP_GATT_OK) { | ||||
|         ESP_LOGI(TAG, "Connected successfully!"); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_DISCONNECT_EVT: { | ||||
|       ESP_LOGW(TAG, "Disconnected!"); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_SEARCH_CMPL_EVT: { | ||||
|       this->handle_ = 0; | ||||
|       auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); | ||||
|       if (chr == nullptr) { | ||||
|         ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), | ||||
|                  sensors_data_characteristic_uuid_.to_string().c_str()); | ||||
|         break; | ||||
|       } | ||||
|       this->handle_ = chr->handle; | ||||
|       this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; | ||||
|  | ||||
|       request_read_values_(); | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     case ESP_GATTC_READ_CHAR_EVT: { | ||||
|       if (param->read.conn_id != this->parent()->get_conn_id()) | ||||
|         break; | ||||
|       if (param->read.status != ESP_GATT_OK) { | ||||
|         ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); | ||||
|         break; | ||||
|       } | ||||
|       if (param->read.handle == this->handle_) { | ||||
|         read_sensors_(param->read.value, param->read.value_len); | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { | ||||
| void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { | ||||
|   auto *value = (WavePlusReadings *) raw_value; | ||||
|  | ||||
|   if (sizeof(WavePlusReadings) <= value_len) { | ||||
| @@ -64,26 +16,38 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { | ||||
|     if (value->version == 1) { | ||||
|       ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); | ||||
|  | ||||
|       if (this->humidity_sensor_ != nullptr) { | ||||
|         this->humidity_sensor_->publish_state(value->humidity / 2.0f); | ||||
|       if (is_valid_radon_value_(value->radon)) { | ||||
|       } | ||||
|  | ||||
|       if ((this->radon_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon)) { | ||||
|         this->radon_sensor_->publish_state(value->radon); | ||||
|       } | ||||
|       if (is_valid_radon_value_(value->radon_lt)) { | ||||
|  | ||||
|       if ((this->radon_long_term_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon_lt)) { | ||||
|         this->radon_long_term_sensor_->publish_state(value->radon_lt); | ||||
|       } | ||||
|  | ||||
|       if (this->temperature_sensor_ != nullptr) { | ||||
|         this->temperature_sensor_->publish_state(value->temperature / 100.0f); | ||||
|       } | ||||
|  | ||||
|       if (this->pressure_sensor_ != nullptr) { | ||||
|         this->pressure_sensor_->publish_state(value->pressure / 50.0f); | ||||
|       if (is_valid_co2_value_(value->co2)) { | ||||
|       } | ||||
|  | ||||
|       if ((this->co2_sensor_ != nullptr) && this->is_valid_co2_value_(value->co2)) { | ||||
|         this->co2_sensor_->publish_state(value->co2); | ||||
|       } | ||||
|       if (is_valid_voc_value_(value->voc)) { | ||||
|  | ||||
|       if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { | ||||
|         this->tvoc_sensor_->publish_state(value->voc); | ||||
|       } | ||||
|  | ||||
|       // This instance must not stay connected | ||||
|       // so other clients can connect to it (e.g. the | ||||
|       // mobile app). | ||||
|       parent()->set_enabled(false); | ||||
|       this->parent()->set_enabled(false); | ||||
|     } else { | ||||
|       ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); | ||||
|     } | ||||
| @@ -92,44 +56,26 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { | ||||
|  | ||||
| bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } | ||||
|  | ||||
| bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } | ||||
|  | ||||
| bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } | ||||
|  | ||||
| void AirthingsWavePlus::update() { | ||||
|   if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { | ||||
|     if (!parent()->enabled) { | ||||
|       ESP_LOGW(TAG, "Reconnecting to device"); | ||||
|       parent()->set_enabled(true); | ||||
|       parent()->connect(); | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Connection in progress"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWavePlus::request_read_values_() { | ||||
|   auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle_, | ||||
|                                         ESP_GATT_AUTH_REQ_NONE); | ||||
|   if (status) { | ||||
|     ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AirthingsWavePlus::dump_config() { | ||||
|   // these really don't belong here, but there doesn't seem to be a | ||||
|   // practical way to have the base class use LOG_SENSOR and include | ||||
|   // the TAG from this component | ||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||
|   LOG_SENSOR("  ", "Radon", this->radon_sensor_); | ||||
|   LOG_SENSOR("  ", "Radon Long Term", this->radon_long_term_sensor_); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
|   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||
|   LOG_SENSOR("  ", "CO2", this->co2_sensor_); | ||||
|   LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Radon", this->radon_sensor_); | ||||
|   LOG_SENSOR("  ", "Radon Long Term", this->radon_long_term_sensor_); | ||||
|   LOG_SENSOR("  ", "CO2", this->co2_sensor_); | ||||
| } | ||||
|  | ||||
| AirthingsWavePlus::AirthingsWavePlus() | ||||
|     : PollingComponent(10000), | ||||
|       service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), | ||||
|       sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} | ||||
| AirthingsWavePlus::AirthingsWavePlus() { | ||||
|   this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); | ||||
|   this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); | ||||
| } | ||||
|  | ||||
| }  // namespace airthings_wave_plus | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,14 +2,7 @@ | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <esp_gattc_api.h> | ||||
| #include <algorithm> | ||||
| #include <iterator> | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/components/airthings_wave_base/airthings_wave_base.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace airthings_wave_plus { | ||||
| @@ -17,43 +10,25 @@ namespace airthings_wave_plus { | ||||
| static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; | ||||
| static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; | ||||
|  | ||||
| class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { | ||||
| class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { | ||||
|  public: | ||||
|   AirthingsWavePlus(); | ||||
|  | ||||
|   void dump_config() override; | ||||
|   void update() override; | ||||
|  | ||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|  | ||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } | ||||
|   void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } | ||||
|   void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } | ||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||||
|   void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } | ||||
|   void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||||
|   void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } | ||||
|  | ||||
|  protected: | ||||
|   bool is_valid_radon_value_(uint16_t radon); | ||||
|   bool is_valid_voc_value_(uint16_t voc); | ||||
|   bool is_valid_co2_value_(uint16_t co2); | ||||
|  | ||||
|   void read_sensors_(uint8_t *value, uint16_t value_len); | ||||
|   void request_read_values_(); | ||||
|   void read_sensors(uint8_t *value, uint16_t value_len) override; | ||||
|  | ||||
|   sensor::Sensor *temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *radon_sensor_{nullptr}; | ||||
|   sensor::Sensor *radon_long_term_sensor_{nullptr}; | ||||
|   sensor::Sensor *humidity_sensor_{nullptr}; | ||||
|   sensor::Sensor *pressure_sensor_{nullptr}; | ||||
|   sensor::Sensor *co2_sensor_{nullptr}; | ||||
|   sensor::Sensor *tvoc_sensor_{nullptr}; | ||||
|  | ||||
|   uint16_t handle_; | ||||
|   esp32_ble_tracker::ESPBTUUID service_uuid_; | ||||
|   esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; | ||||
|  | ||||
|   struct WavePlusReadings { | ||||
|     uint8_t version; | ||||
|   | ||||
| @@ -1,49 +1,32 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, ble_client | ||||
| from esphome.components import sensor, airthings_wave_base | ||||
|  | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|     DEVICE_CLASS_HUMIDITY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PERCENT, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HECTOPASCAL, | ||||
|     ICON_RADIOACTIVE, | ||||
|     CONF_ID, | ||||
|     CONF_RADON, | ||||
|     CONF_RADON_LONG_TERM, | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_TVOC, | ||||
|     CONF_CO2, | ||||
|     CONF_PRESSURE, | ||||
|     CONF_TEMPERATURE, | ||||
|     UNIT_BECQUEREL_PER_CUBIC_METER, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
|     ICON_RADIATOR, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["ble_client"] | ||||
| DEPENDENCIES = airthings_wave_base.DEPENDENCIES | ||||
|  | ||||
| AUTO_LOAD = ["airthings_wave_base"] | ||||
|  | ||||
| airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus") | ||||
| AirthingsWavePlus = airthings_wave_plus_ns.class_( | ||||
|     "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode | ||||
|     "AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase | ||||
| ) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
| CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(AirthingsWavePlus), | ||||
|             cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PERCENT, | ||||
|                 device_class=DEVICE_CLASS_HUMIDITY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 accuracy_decimals=0, | ||||
|             ), | ||||
|         cv.Optional(CONF_RADON): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, | ||||
|             icon=ICON_RADIOACTIVE, | ||||
| @@ -56,61 +39,26 @@ CONFIG_SCHEMA = cv.All( | ||||
|             accuracy_decimals=0, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_PRESSURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_PRESSURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|         cv.Optional(CONF_CO2): sensor.sensor_schema( | ||||
|             unit_of_measurement=UNIT_PARTS_PER_MILLION, | ||||
|             accuracy_decimals=0, | ||||
|             device_class=DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|             state_class=STATE_CLASS_MEASUREMENT, | ||||
|         ), | ||||
|             cv.Optional(CONF_TVOC): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PARTS_PER_BILLION, | ||||
|                 icon=ICON_RADIATOR, | ||||
|                 accuracy_decimals=0, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|     } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("5min")) | ||||
|     .extend(ble_client.BLE_CLIENT_SCHEMA), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await airthings_wave_base.wave_base_to_code(var, config) | ||||
|  | ||||
|     await ble_client.register_ble_node(var, config) | ||||
|  | ||||
|     if CONF_HUMIDITY in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_HUMIDITY]) | ||||
|         cg.add(var.set_humidity(sens)) | ||||
|     if CONF_RADON in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_RADON]) | ||||
|         cg.add(var.set_radon(sens)) | ||||
|     if CONF_RADON_LONG_TERM in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) | ||||
|         cg.add(var.set_radon_long_term(sens)) | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) | ||||
|         cg.add(var.set_temperature(sens)) | ||||
|     if CONF_PRESSURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_PRESSURE]) | ||||
|         cg.add(var.set_pressure(sens)) | ||||
|     if CONF_CO2 in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_CO2]) | ||||
|         cg.add(var.set_co2(sens)) | ||||
|     if CONF_TVOC in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_TVOC]) | ||||
|         cg.add(var.set_tvoc(sens)) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from esphome.const import ( | ||||
|     ICON_RADIATOR, | ||||
|     ICON_RESTART, | ||||
|     DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
| @@ -43,7 +43,7 @@ CONFIG_SCHEMA = ( | ||||
|                 unit_of_measurement=UNIT_PARTS_PER_BILLION, | ||||
|                 icon=ICON_RADIATOR, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|                 device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( | ||||
|   | ||||
| @@ -11,7 +11,7 @@ from esphome.const import ( | ||||
|     CONF_TVOC, | ||||
|     ICON_RADIATOR, | ||||
|     DEVICE_CLASS_CARBON_DIOXIDE, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|     DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_PARTS_PER_MILLION, | ||||
|     UNIT_PARTS_PER_BILLION, | ||||
| @@ -49,7 +49,7 @@ CONFIG_SCHEMA = ( | ||||
|                 unit_of_measurement=UNIT_PARTS_PER_BILLION, | ||||
|                 icon=ICON_RADIATOR, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, | ||||
|                 device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| """Constants used by esphome.""" | ||||
|  | ||||
| __version__ = "2023.6.0b6" | ||||
| __version__ = "2023.6.0b7" | ||||
|  | ||||
| ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" | ||||
| VALID_SUBSTITUTIONS_CHARACTERS = ( | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import binascii | ||||
| import codecs | ||||
| import collections | ||||
| import functools | ||||
| import gzip | ||||
| import hashlib | ||||
| import hmac | ||||
| import json | ||||
| @@ -485,6 +486,7 @@ class DownloadBinaryRequestHandler(BaseHandler): | ||||
|     @bind_config | ||||
|     def get(self, configuration=None): | ||||
|         type = self.get_argument("type", "firmware.bin") | ||||
|         compressed = self.get_argument("compressed", "0") == "1" | ||||
|  | ||||
|         storage_path = ext_storage_path(settings.config_dir, configuration) | ||||
|         storage_json = StorageJSON.load(storage_path) | ||||
| @@ -534,6 +536,8 @@ class DownloadBinaryRequestHandler(BaseHandler): | ||||
|                 self.send_error(404) | ||||
|                 return | ||||
|  | ||||
|         filename = filename + ".gz" if compressed else filename | ||||
|  | ||||
|         self.set_header("Content-Type", "application/octet-stream") | ||||
|         self.set_header("Content-Disposition", f'attachment; filename="{filename}"') | ||||
|         self.set_header("Cache-Control", "no-cache") | ||||
| @@ -543,9 +547,20 @@ class DownloadBinaryRequestHandler(BaseHandler): | ||||
|  | ||||
|         with open(path, "rb") as f: | ||||
|             while True: | ||||
|                 data = f.read(16384) | ||||
|                 # For a 528KB image used as benchmark: | ||||
|                 #   - using 256KB blocks resulted in the smallest file size. | ||||
|                 #   - blocks larger than 256KB didn't improve the size of compressed file. | ||||
|                 #   - blocks smaller than 256KB hindered compression, making the output file larger. | ||||
|  | ||||
|                 # Read file in blocks of 256KB. | ||||
|                 data = f.read(256 * 1024) | ||||
|  | ||||
|                 if not data: | ||||
|                     break | ||||
|  | ||||
|                 if compressed: | ||||
|                     data = gzip.compress(data, 9) | ||||
|  | ||||
|                 self.write(data) | ||||
|         self.finish() | ||||
|  | ||||
|   | ||||
| @@ -312,7 +312,8 @@ sensor: | ||||
|     id: freezer_temp_source | ||||
|     reference_voltage: 3.19 | ||||
|     number: 0 | ||||
|   - platform: airthings_wave_plus | ||||
|   - id: airthingswp | ||||
|     platform: airthings_wave_plus | ||||
|     ble_client_id: airthings01 | ||||
|     update_interval: 5min | ||||
|     temperature: | ||||
| @@ -329,7 +330,8 @@ sensor: | ||||
|       name: Wave Plus CO2 | ||||
|     tvoc: | ||||
|       name: Wave Plus VOC | ||||
|   - platform: airthings_wave_mini | ||||
|   - id: airthingswm | ||||
|     platform: airthings_wave_mini | ||||
|     ble_client_id: airthingsmini01 | ||||
|     update_interval: 5min | ||||
|     temperature: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user