mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-2022.4.0b1
This commit is contained in:
		| @@ -2,7 +2,7 @@ | |||||||
| # See https://pre-commit.com/hooks.html for more hooks | # See https://pre-commit.com/hooks.html for more hooks | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/ambv/black |   - repo: https://github.com/ambv/black | ||||||
|     rev: 22.1.0 |     rev: 22.3.0 | ||||||
|     hooks: |     hooks: | ||||||
|     - id: black |     - id: black | ||||||
|       args: |       args: | ||||||
| @@ -26,7 +26,7 @@ repos: | |||||||
|           - --branch=release |           - --branch=release | ||||||
|           - --branch=beta |           - --branch=beta | ||||||
|   - repo: https://github.com/asottile/pyupgrade |   - repo: https://github.com/asottile/pyupgrade | ||||||
|     rev: v2.31.0 |     rev: v2.31.1 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: pyupgrade |       - id: pyupgrade | ||||||
|         args: [--py38-plus] |         args: [--py38-plus] | ||||||
|   | |||||||
| @@ -82,6 +82,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal | |||||||
| esphome/components/homeassistant/* @OttoWinter | esphome/components/homeassistant/* @OttoWinter | ||||||
| esphome/components/honeywellabp/* @RubyBailey | esphome/components/honeywellabp/* @RubyBailey | ||||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||||
|  | esphome/components/hydreon_rgxx/* @functionpointer | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
| esphome/components/improv_serial/* @esphome/core | esphome/components/improv_serial/* @esphome/core | ||||||
| esphome/components/ina260/* @MrEditor97 | esphome/components/ina260/* @MrEditor97 | ||||||
| @@ -151,6 +152,7 @@ esphome/components/preferences/* @esphome/core | |||||||
| esphome/components/psram/* @esphome/core | esphome/components/psram/* @esphome/core | ||||||
| esphome/components/pulse_meter/* @cstaahl @stevebaxter | esphome/components/pulse_meter/* @cstaahl @stevebaxter | ||||||
| esphome/components/pvvx_mithermometer/* @pasiz | esphome/components/pvvx_mithermometer/* @pasiz | ||||||
|  | esphome/components/qmp6988/* @andrewpc | ||||||
| esphome/components/qr_code/* @wjtje | esphome/components/qr_code/* @wjtje | ||||||
| esphome/components/radon_eye_ble/* @jeffeb3 | esphome/components/radon_eye_ble/* @jeffeb3 | ||||||
| esphome/components/radon_eye_rd200/* @jeffeb3 | esphome/components/radon_eye_rd200/* @jeffeb3 | ||||||
| @@ -168,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces | |||||||
| esphome/components/sdp3x/* @Azimath | esphome/components/sdp3x/* @Azimath | ||||||
| esphome/components/selec_meter/* @sourabhjaiswal | esphome/components/selec_meter/* @sourabhjaiswal | ||||||
| esphome/components/select/* @esphome/core | esphome/components/select/* @esphome/core | ||||||
|  | esphome/components/sensirion_common/* @martgras | ||||||
| esphome/components/sensor/* @esphome/core | esphome/components/sensor/* @esphome/core | ||||||
| esphome/components/sgp40/* @SenexCrenshaw | esphome/components/sgp40/* @SenexCrenshaw | ||||||
| esphome/components/sht4x/* @sjtrny | esphome/components/sht4x/* @sjtrny | ||||||
| @@ -175,6 +178,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet | |||||||
| esphome/components/sim800l/* @glmnet | esphome/components/sim800l/* @glmnet | ||||||
| esphome/components/sm2135/* @BoukeHaarsma23 | esphome/components/sm2135/* @BoukeHaarsma23 | ||||||
| esphome/components/socket/* @esphome/core | esphome/components/socket/* @esphome/core | ||||||
|  | esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||||
| esphome/components/spi/* @esphome/core | esphome/components/spi/* @esphome/core | ||||||
| esphome/components/ssd1322_base/* @kbx81 | esphome/components/ssd1322_base/* @kbx81 | ||||||
| esphome/components/ssd1322_spi/* @kbx81 | esphome/components/ssd1322_spi/* @kbx81 | ||||||
| @@ -222,4 +226,5 @@ esphome/components/whirlpool/* @glmnet | |||||||
| esphome/components/xiaomi_lywsd03mmc/* @ahpohl | esphome/components/xiaomi_lywsd03mmc/* @ahpohl | ||||||
| esphome/components/xiaomi_mhoc303/* @drug123 | esphome/components/xiaomi_mhoc303/* @drug123 | ||||||
| esphome/components/xiaomi_mhoc401/* @vevsvevs | esphome/components/xiaomi_mhoc401/* @vevsvevs | ||||||
|  | esphome/components/xiaomi_rtcgq02lm/* @jesserockz | ||||||
| esphome/components/xpt2046/* @numo68 | esphome/components/xpt2046/* @numo68 | ||||||
|   | |||||||
| @@ -6,13 +6,13 @@ | |||||||
| ARG BASEIMGTYPE=docker | ARG BASEIMGTYPE=docker | ||||||
|  |  | ||||||
| # https://github.com/hassio-addons/addon-debian-base/releases | # https://github.com/hassio-addons/addon-debian-base/releases | ||||||
| FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64 | FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64 | ||||||
| FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64 | FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64 | ||||||
| FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7 | FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7 | ||||||
| # https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye | # https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye | ||||||
| FROM debian:bullseye-20220125-slim AS base-docker-amd64 | FROM debian:bullseye-20220328-slim AS base-docker-amd64 | ||||||
| FROM debian:bullseye-20220125-slim AS base-docker-arm64 | FROM debian:bullseye-20220328-slim AS base-docker-arm64 | ||||||
| FROM debian:bullseye-20220125-slim AS base-docker-armv7 | FROM debian:bullseye-20220328-slim AS base-docker-armv7 | ||||||
|  |  | ||||||
| # Use TARGETARCH/TARGETVARIANT defined by docker | # Use TARGETARCH/TARGETVARIANT defined by docker | ||||||
| # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope | # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope | ||||||
| @@ -23,7 +23,7 @@ RUN \ | |||||||
|     # Use pinned versions so that we get updates with build caching |     # Use pinned versions so that we get updates with build caching | ||||||
|     && apt-get install -y --no-install-recommends \ |     && apt-get install -y --no-install-recommends \ | ||||||
|         python3=3.9.2-3 \ |         python3=3.9.2-3 \ | ||||||
|         python3-pip=20.3.4-4 \ |         python3-pip=20.3.4-4+deb11u1 \ | ||||||
|         python3-setuptools=52.0.0-4 \ |         python3-setuptools=52.0.0-4 \ | ||||||
|         python3-pil=8.1.2+dfsg-0.3+deb11u1 \ |         python3-pil=8.1.2+dfsg-0.3+deb11u1 \ | ||||||
|         python3-cryptography=3.3.2-1 \ |         python3-cryptography=3.3.2-1 \ | ||||||
|   | |||||||
| @@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args): | |||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_wait_until(value): | _validate_wait_until = cv.maybe_simple_value( | ||||||
|     schema = cv.Schema( |     { | ||||||
|         { |         cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, |         cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds), | ||||||
|             cv.Optional(CONF_TIMEOUT): cv.templatable( |     }, | ||||||
|                 cv.positive_time_period_milliseconds |     key=CONF_CONDITION, | ||||||
|             ), | ) | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     if isinstance(value, dict) and CONF_CONDITION in value: |  | ||||||
|         return schema(value) |  | ||||||
|     return validate_wait_until({CONF_CONDITION: value}) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_action("wait_until", WaitUntilAction, validate_wait_until) | @register_action("wait_until", WaitUntilAction, _validate_wait_until) | ||||||
| async def wait_until_action_to_code(config, action_id, template_arg, args): | async def wait_until_action_to_code(config, action_id, template_arg, args): | ||||||
|     conditions = await build_condition(config[CONF_CONDITION], template_arg, args) |     conditions = await build_condition(config[CONF_CONDITION], template_arg, args) | ||||||
|     var = cg.new_Pvariable(action_id, template_arg, conditions) |     var = cg.new_Pvariable(action_id, template_arg, conditions) | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ static const char *const TAG = "api.connection"; | |||||||
| static const int ESP32_CAMERA_STOP_STREAM = 5000; | static const int ESP32_CAMERA_STOP_STREAM = 5000; | ||||||
|  |  | ||||||
| APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | ||||||
|     : parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { |     : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { | ||||||
|   this->proto_write_buffer_.reserve(64); |   this->proto_write_buffer_.reserve(64); | ||||||
|  |  | ||||||
| #if defined(USE_API_PLAINTEXT) | #if defined(USE_API_PLAINTEXT) | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ | |||||||
| #include "esphome/components/socket/socket.h" | #include "esphome/components/socket/socket.h" | ||||||
| #include "api_pb2.h" | #include "api_pb2.h" | ||||||
| #include "api_pb2_service.h" | #include "api_pb2_service.h" | ||||||
| #include "util.h" |  | ||||||
| #include "list_entities.h" | #include "list_entities.h" | ||||||
| #include "subscribe_state.h" | #include "subscribe_state.h" | ||||||
| #include "user_services.h" | #include "user_services.h" | ||||||
|   | |||||||
| @@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||||
| ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) | ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} | ||||||
|     : ComponentIterator(server), client_(client) {} |  | ||||||
| bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | ||||||
|   auto resp = service->encode_list_service_response(); |   auto resp = service->encode_list_service_response(); | ||||||
|   return this->client_->send_list_entities_services_response(resp); |   return this->client_->send_list_entities_services_response(resp); | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/component_iterator.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "util.h" |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
| @@ -11,7 +11,7 @@ class APIConnection; | |||||||
|  |  | ||||||
| class ListEntitiesIterator : public ComponentIterator { | class ListEntitiesIterator : public ComponentIterator { | ||||||
|  public: |  public: | ||||||
|   ListEntitiesIterator(APIServer *server, APIConnection *client); |   ListEntitiesIterator(APIConnection *client); | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; |   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||||
| #endif | #endif | ||||||
| @@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator { | |||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #include "api_server.h" |  | ||||||
|   | |||||||
| @@ -1,5 +1,4 @@ | |||||||
| #include "proto.h" | #include "proto.h" | ||||||
| #include "util.h" |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
|   | |||||||
| @@ -195,6 +195,20 @@ class ProtoWriteBuffer { | |||||||
|     this->write((value >> 16) & 0xFF); |     this->write((value >> 16) & 0xFF); | ||||||
|     this->write((value >> 24) & 0xFF); |     this->write((value >> 24) & 0xFF); | ||||||
|   } |   } | ||||||
|  |   void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) { | ||||||
|  |     if (value == 0 && !force) | ||||||
|  |       return; | ||||||
|  |  | ||||||
|  |     this->encode_field_raw(field_id, 5); | ||||||
|  |     this->write((value >> 0) & 0xFF); | ||||||
|  |     this->write((value >> 8) & 0xFF); | ||||||
|  |     this->write((value >> 16) & 0xFF); | ||||||
|  |     this->write((value >> 24) & 0xFF); | ||||||
|  |     this->write((value >> 32) & 0xFF); | ||||||
|  |     this->write((value >> 40) & 0xFF); | ||||||
|  |     this->write((value >> 48) & 0xFF); | ||||||
|  |     this->write((value >> 56) & 0xFF); | ||||||
|  |   } | ||||||
|   template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) { |   template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) { | ||||||
|     this->encode_uint32(field_id, static_cast<uint32_t>(value), force); |     this->encode_uint32(field_id, static_cast<uint32_t>(value), force); | ||||||
|   } |   } | ||||||
| @@ -229,6 +243,15 @@ class ProtoWriteBuffer { | |||||||
|     } |     } | ||||||
|     this->encode_uint32(field_id, uvalue, force); |     this->encode_uint32(field_id, uvalue, force); | ||||||
|   } |   } | ||||||
|  |   void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { | ||||||
|  |     uint64_t uvalue; | ||||||
|  |     if (value < 0) { | ||||||
|  |       uvalue = ~(value << 1); | ||||||
|  |     } else { | ||||||
|  |       uvalue = value << 1; | ||||||
|  |     } | ||||||
|  |     this->encode_uint64(field_id, uvalue, force); | ||||||
|  |   } | ||||||
|   template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) { |   template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) { | ||||||
|     this->encode_field_raw(field_id, 2); |     this->encode_field_raw(field_id, 2); | ||||||
|     size_t begin = this->buffer_->size(); |     size_t begin = this->buffer_->size(); | ||||||
|   | |||||||
| @@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) { | |||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } | bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } | ||||||
| #endif | #endif | ||||||
| InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) | InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} | ||||||
|     : ComponentIterator(server), client_(client) {} |  | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/component_iterator.h" | ||||||
| #include "esphome/core/controller.h" | #include "esphome/core/controller.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "util.h" |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
| @@ -12,7 +12,7 @@ class APIConnection; | |||||||
|  |  | ||||||
| class InitialStateIterator : public ComponentIterator { | class InitialStateIterator : public ComponentIterator { | ||||||
|  public: |  public: | ||||||
|   InitialStateIterator(APIServer *server, APIConnection *client); |   InitialStateIterator(APIConnection *client); | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; |   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||||
| #endif | #endif | ||||||
| @@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator { | |||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #include "api_server.h" |  | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | |||||||
|   const auto &data = service_data.data; |   const auto &data = service_data.data; | ||||||
|  |  | ||||||
|   const uint8_t protocol_version = data[0] >> 4; |   const uint8_t protocol_version = data[0] >> 4; | ||||||
|   if (protocol_version != 1) { |   if (protocol_version != 1 && protocol_version != 2) { | ||||||
|     ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version); |     ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -57,9 +57,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | |||||||
|   uint16_t battery_millivolt = data[2] << 8 | data[3]; |   uint16_t battery_millivolt = data[2] << 8 | data[3]; | ||||||
|   float battery_voltage = battery_millivolt / 1000.0f; |   float battery_voltage = battery_millivolt / 1000.0f; | ||||||
|  |  | ||||||
|   // Temperature in 1000 * Celsius. |   // Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2). | ||||||
|   uint16_t temp_millicelcius = data[4] << 8 | data[5]; |   float temp_celsius; | ||||||
|   float temp_celcius = temp_millicelcius / 1000.0f; |   if (protocol_version == 1) { | ||||||
|  |     uint16_t temp_millicelsius = data[4] << 8 | data[5]; | ||||||
|  |     temp_celsius = temp_millicelsius / 1000.0f; | ||||||
|  |   } else { | ||||||
|  |     int16_t temp_centicelsius = data[4] << 8 | data[5]; | ||||||
|  |     temp_celsius = temp_centicelsius / 100.0f; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Relative air humidity in the range [0, 2^16). |   // Relative air humidity in the range [0, 2^16). | ||||||
|   uint16_t humidity = data[6] << 8 | data[7]; |   uint16_t humidity = data[6] << 8 | data[7]; | ||||||
| @@ -76,7 +82,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { | |||||||
|     battery_voltage_->publish_state(battery_voltage); |     battery_voltage_->publish_state(battery_voltage); | ||||||
|   } |   } | ||||||
|   if (temperature_ != nullptr) { |   if (temperature_ != nullptr) { | ||||||
|     temperature_->publish_state(temp_celcius); |     temperature_->publish_state(temp_celsius); | ||||||
|   } |   } | ||||||
|   if (humidity_ != nullptr) { |   if (humidity_ != nullptr) { | ||||||
|     humidity_->publish_state(humidity_percent); |     humidity_->publish_state(humidity_percent); | ||||||
|   | |||||||
| @@ -118,16 +118,21 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es | |||||||
|         this->set_states_(espbt::ClientState::IDLE); |         this->set_states_(espbt::ClientState::IDLE); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|       this->conn_id = param->open.conn_id; |       break; | ||||||
|       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id); |     } | ||||||
|  |     case ESP_GATTC_CONNECT_EVT: { | ||||||
|  |       ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str()); | ||||||
|  |       this->conn_id = param->connect.conn_id; | ||||||
|  |       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id); | ||||||
|       if (ret) { |       if (ret) { | ||||||
|         ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret); |         ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTC_CFG_MTU_EVT: { |     case ESP_GATTC_CFG_MTU_EVT: { | ||||||
|       if (param->cfg_mtu.status != ESP_GATT_OK) { |       if (param->cfg_mtu.status != ESP_GATT_OK) { | ||||||
|         ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status); |         ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu, | ||||||
|  |                  param->cfg_mtu.status); | ||||||
|         this->set_states_(espbt::ClientState::IDLE); |         this->set_states_(espbt::ClientState::IDLE); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @@ -139,7 +144,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es | |||||||
|       if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { |       if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str()); |       ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason); | ||||||
|       for (auto &svc : this->services_) |       for (auto &svc : this->services_) | ||||||
|         delete svc;  // NOLINT(cppcoreguidelines-owning-memory) |         delete svc;  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|       this->services_.clear(); |       this->services_.clear(); | ||||||
| @@ -201,6 +206,32 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|  |   switch (event) { | ||||||
|  |     // This event is sent by the server when it requests security | ||||||
|  |     case ESP_GAP_BLE_SEC_REQ_EVT: | ||||||
|  |       ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event); | ||||||
|  |       esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); | ||||||
|  |       break; | ||||||
|  |     // This event is sent once authentication has completed | ||||||
|  |     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||||
|  |       esp_bd_addr_t bd_addr; | ||||||
|  |       memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); | ||||||
|  |       ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str()); | ||||||
|  |       if (!param->ble_security.auth_cmpl.success) { | ||||||
|  |         ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason); | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type, | ||||||
|  |                  param->ble_security.auth_cmpl.auth_mode); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     // There are other events we'll want to implement at some point to support things like pass key | ||||||
|  |     // https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| // Parse GATT values into a float for a sensor. | // Parse GATT values into a float for a sensor. | ||||||
| // Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ | // Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ | ||||||
| float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { | float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ | |||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
| #include <esp_gattc_api.h> | #include <esp_gattc_api.h> | ||||||
| #include <esp_bt_defs.h> | #include <esp_bt_defs.h> | ||||||
|  | #include <esp_gatt_common_api.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ble_client { | namespace ble_client { | ||||||
| @@ -86,6 +87,7 @@ class BLEClient : public espbt::ESPBTClient, public Component { | |||||||
|  |  | ||||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override; |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|  |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||||
|   bool parse_device(const espbt::ESPBTDevice &device) override; |   bool parse_device(const espbt::ESPBTDevice &device) override; | ||||||
|   void on_scan_end() override {} |   void on_scan_end() override {} | ||||||
|   void connect() override; |   void connect() override; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True | |||||||
| CONF_CAN_ID = "can_id" | CONF_CAN_ID = "can_id" | ||||||
| CONF_CAN_ID_MASK = "can_id_mask" | CONF_CAN_ID_MASK = "can_id_mask" | ||||||
| CONF_USE_EXTENDED_ID = "use_extended_id" | CONF_USE_EXTENDED_ID = "use_extended_id" | ||||||
|  | CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request" | ||||||
| CONF_CANBUS_ID = "canbus_id" | CONF_CANBUS_ID = "canbus_id" | ||||||
| CONF_BIT_RATE = "bit_rate" | CONF_BIT_RATE = "bit_rate" | ||||||
| CONF_ON_FRAME = "on_frame" | CONF_ON_FRAME = "on_frame" | ||||||
| @@ -122,6 +123,7 @@ async def register_canbus(var, config): | |||||||
|             cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), |             cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), | ||||||
|             cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), |             cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||||
|             cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, |             cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean, | ||||||
|             cv.Required(CONF_DATA): cv.templatable(validate_raw_data), |             cv.Required(CONF_DATA): cv.templatable(validate_raw_data), | ||||||
|         }, |         }, | ||||||
|         validate_id, |         validate_id, | ||||||
| @@ -140,6 +142,11 @@ async def canbus_action_to_code(config, action_id, template_arg, args): | |||||||
|     ) |     ) | ||||||
|     cg.add(var.set_use_extended_id(use_extended_id)) |     cg.add(var.set_use_extended_id(use_extended_id)) | ||||||
|  |  | ||||||
|  |     remote_transmission_request = await cg.templatable( | ||||||
|  |         config[CONF_REMOTE_TRANSMISSION_REQUEST], args, bool | ||||||
|  |     ) | ||||||
|  |     cg.add(var.set_remote_transmission_request(remote_transmission_request)) | ||||||
|  |  | ||||||
|     data = config[CONF_DATA] |     data = config[CONF_DATA] | ||||||
|     if isinstance(data, bytes): |     if isinstance(data, bytes): | ||||||
|         data = [int(x) for x in data] |         data = [int(x) for x in data] | ||||||
|   | |||||||
| @@ -22,20 +22,22 @@ void Canbus::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) { | void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, | ||||||
|  |                        const std::vector<uint8_t> &data) { | ||||||
|   struct CanFrame can_message; |   struct CanFrame can_message; | ||||||
|  |  | ||||||
|   uint8_t size = static_cast<uint8_t>(data.size()); |   uint8_t size = static_cast<uint8_t>(data.size()); | ||||||
|   if (use_extended_id) { |   if (use_extended_id) { | ||||||
|     ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size); |     ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size); |     ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); | ||||||
|   } |   } | ||||||
|   if (size > CAN_MAX_DATA_LENGTH) |   if (size > CAN_MAX_DATA_LENGTH) | ||||||
|     size = CAN_MAX_DATA_LENGTH; |     size = CAN_MAX_DATA_LENGTH; | ||||||
|   can_message.can_data_length_code = size; |   can_message.can_data_length_code = size; | ||||||
|   can_message.can_id = can_id; |   can_message.can_id = can_id; | ||||||
|   can_message.use_extended_id = use_extended_id; |   can_message.use_extended_id = use_extended_id; | ||||||
|  |   can_message.remote_transmission_request = remote_transmission_request; | ||||||
|  |  | ||||||
|   for (int i = 0; i < size; i++) { |   for (int i = 0; i < size; i++) { | ||||||
|     can_message.data[i] = data[i]; |     can_message.data[i] = data[i]; | ||||||
|   | |||||||
| @@ -62,7 +62,12 @@ class Canbus : public Component { | |||||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } |   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data); |   void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, | ||||||
|  |                  const std::vector<uint8_t> &data); | ||||||
|  |   void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) { | ||||||
|  |     // for backwards compatibility only | ||||||
|  |     this->send_data(can_id, use_extended_id, false, data); | ||||||
|  |   } | ||||||
|   void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } |   void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } | ||||||
|   void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } |   void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } | ||||||
|   void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; } |   void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; } | ||||||
| @@ -96,21 +101,26 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P | |||||||
|  |  | ||||||
|   void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } |   void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; } | ||||||
|  |  | ||||||
|  |   void set_remote_transmission_request(bool remote_transmission_request) { | ||||||
|  |     this->remote_transmission_request_ = remote_transmission_request; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; |     auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; | ||||||
|     auto use_extended_id = |     auto use_extended_id = | ||||||
|         this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; |         this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; | ||||||
|     if (this->static_) { |     if (this->static_) { | ||||||
|       this->parent_->send_data(can_id, use_extended_id, this->data_static_); |       this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_); | ||||||
|     } else { |     } else { | ||||||
|       auto val = this->data_func_(x...); |       auto val = this->data_func_(x...); | ||||||
|       this->parent_->send_data(can_id, use_extended_id, val); |       this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   optional<uint32_t> can_id_{}; |   optional<uint32_t> can_id_{}; | ||||||
|   optional<bool> use_extended_id_{}; |   optional<bool> use_extended_id_{}; | ||||||
|  |   bool remote_transmission_request_{false}; | ||||||
|   bool static_{false}; |   bool static_{false}; | ||||||
|   std::function<std::vector<uint8_t>(Ts...)> data_func_{}; |   std::function<std::vector<uint8_t>(Ts...)> data_func_{}; | ||||||
|   std::vector<uint8_t> data_static_{}; |   std::vector<uint8_t> data_static_{}; | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t { | |||||||
|   CLIMATE_SWING_HORIZONTAL = 3, |   CLIMATE_SWING_HORIZONTAL = 3, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// Enum for all modes a climate swing can be in | /// Enum for all preset modes | ||||||
| enum ClimatePreset : uint8_t { | enum ClimatePreset : uint8_t { | ||||||
|   /// No preset is active |   /// No preset is active | ||||||
|   CLIMATE_PRESET_NONE = 0, |   CLIMATE_PRESET_NONE = 0, | ||||||
| @@ -108,7 +108,7 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode mode); | |||||||
| /// Convert the given ClimateSwingMode to a human-readable string. | /// Convert the given ClimateSwingMode to a human-readable string. | ||||||
| const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); | const LogString *climate_swing_mode_to_string(ClimateSwingMode mode); | ||||||
|  |  | ||||||
| /// Convert the given ClimateSwingMode to a human-readable string. | /// Convert the given PresetMode to a human-readable string. | ||||||
| const LogString *climate_preset_to_string(ClimatePreset preset); | const LogString *climate_preset_to_string(ClimatePreset preset); | ||||||
|  |  | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
|   | |||||||
| @@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) { | |||||||
| void IRAM_ATTR ESPOneWire::reset_search() { | void IRAM_ATTR ESPOneWire::reset_search() { | ||||||
|   this->last_discrepancy_ = 0; |   this->last_discrepancy_ = 0; | ||||||
|   this->last_device_flag_ = false; |   this->last_device_flag_ = false; | ||||||
|   this->last_family_discrepancy_ = 0; |  | ||||||
|   this->rom_number_ = 0; |   this->rom_number_ = 0; | ||||||
| } | } | ||||||
| uint64_t IRAM_ATTR ESPOneWire::search() { | uint64_t IRAM_ATTR ESPOneWire::search() { | ||||||
| @@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() { | |||||||
|  |  | ||||||
|         if (!branch) { |         if (!branch) { | ||||||
|           last_zero = id_bit_number; |           last_zero = id_bit_number; | ||||||
|           if (last_zero < 9) { |  | ||||||
|             this->last_discrepancy_ = last_zero; |  | ||||||
|           } |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -60,7 +60,6 @@ class ESPOneWire { | |||||||
|  |  | ||||||
|   ISRInternalGPIOPin pin_; |   ISRInternalGPIOPin pin_; | ||||||
|   uint8_t last_discrepancy_{0}; |   uint8_t last_discrepancy_{0}; | ||||||
|   uint8_t last_family_discrepancy_{0}; |  | ||||||
|   bool last_device_flag_{false}; |   bool last_device_flag_{false}; | ||||||
|   uint64_t rom_number_{0}; |   uint64_t rom_number_{0}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,13 +1,18 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome.components import time | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import pins, automation | from esphome import pins, automation | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_HOUR, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_MINUTE, | ||||||
|     CONF_MODE, |     CONF_MODE, | ||||||
|     CONF_NUMBER, |     CONF_NUMBER, | ||||||
|     CONF_PINS, |     CONF_PINS, | ||||||
|     CONF_RUN_DURATION, |     CONF_RUN_DURATION, | ||||||
|  |     CONF_SECOND, | ||||||
|     CONF_SLEEP_DURATION, |     CONF_SLEEP_DURATION, | ||||||
|  |     CONF_TIME_ID, | ||||||
|     CONF_WAKEUP_PIN, |     CONF_WAKEUP_PIN, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -15,6 +20,7 @@ from esphome.components.esp32 import get_esp32_variant | |||||||
| from esphome.components.esp32.const import ( | from esphome.components.esp32.const import ( | ||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|     VARIANT_ESP32C3, |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| WAKEUP_PINS = { | WAKEUP_PINS = { | ||||||
| @@ -39,6 +45,30 @@ WAKEUP_PINS = { | |||||||
|         39, |         39, | ||||||
|     ], |     ], | ||||||
|     VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], |     VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], | ||||||
|  |     VARIANT_ESP32S2: [ | ||||||
|  |         0, | ||||||
|  |         1, | ||||||
|  |         2, | ||||||
|  |         3, | ||||||
|  |         4, | ||||||
|  |         5, | ||||||
|  |         6, | ||||||
|  |         7, | ||||||
|  |         8, | ||||||
|  |         9, | ||||||
|  |         10, | ||||||
|  |         11, | ||||||
|  |         12, | ||||||
|  |         13, | ||||||
|  |         14, | ||||||
|  |         15, | ||||||
|  |         16, | ||||||
|  |         17, | ||||||
|  |         18, | ||||||
|  |         19, | ||||||
|  |         20, | ||||||
|  |         21, | ||||||
|  |     ], | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -87,6 +117,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup" | |||||||
| CONF_DEFAULT = "default" | CONF_DEFAULT = "default" | ||||||
| CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" | CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" | ||||||
| CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" | CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" | ||||||
|  | CONF_UNTIL = "until" | ||||||
|  |  | ||||||
| WAKEUP_CAUSES_SCHEMA = cv.Schema( | WAKEUP_CAUSES_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
| @@ -177,13 +208,19 @@ async def to_code(config): | |||||||
|     cg.add_define("USE_DEEP_SLEEP") |     cg.add_define("USE_DEEP_SLEEP") | ||||||
|  |  | ||||||
|  |  | ||||||
| DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( | DEEP_SLEEP_ENTER_SCHEMA = cv.All( | ||||||
|     { |     automation.maybe_simple_id( | ||||||
|         cv.GenerateID(): cv.use_id(DeepSleepComponent), |         { | ||||||
|         cv.Optional(CONF_SLEEP_DURATION): cv.templatable( |             cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||||
|             cv.positive_time_period_milliseconds |             cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable( | ||||||
|         ), |                 cv.positive_time_period_milliseconds | ||||||
|     } |             ), | ||||||
|  |             # Only on ESP32 due to how long the RTC on ESP8266 can stay asleep | ||||||
|  |             cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day), | ||||||
|  |             cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -203,6 +240,14 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args): | |||||||
|     if CONF_SLEEP_DURATION in config: |     if CONF_SLEEP_DURATION in config: | ||||||
|         template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) |         template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) | ||||||
|         cg.add(var.set_sleep_duration(template_)) |         cg.add(var.set_sleep_duration(template_)) | ||||||
|  |  | ||||||
|  |     if CONF_UNTIL in config: | ||||||
|  |         until = config[CONF_UNTIL] | ||||||
|  |         cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND])) | ||||||
|  |  | ||||||
|  |         time_ = await cg.get_variable(config[CONF_TIME_ID]) | ||||||
|  |         cg.add(var.set_time(time_)) | ||||||
|  |  | ||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #include "deep_sleep_component.h" | #include "deep_sleep_component.h" | ||||||
| #include "esphome/core/log.h" | #include <cinttypes> | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| #include <Esp.h> | #include <Esp.h> | ||||||
| @@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   ESP_LOGI(TAG, "Beginning Deep Sleep"); |   ESP_LOGI(TAG, "Beginning Deep Sleep"); | ||||||
|  |   if (this->sleep_duration_.has_value()) | ||||||
|  |     ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_); | ||||||
|  |  | ||||||
|   App.run_safe_shutdown_hooks(); |   App.run_safe_shutdown_hooks(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,10 @@ | |||||||
| #include <esp_sleep.h> | #include <esp_sleep.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_TIME | ||||||
|  | #include "esphome/components/time/real_time_clock.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace deep_sleep { | namespace deep_sleep { | ||||||
|  |  | ||||||
| @@ -116,15 +120,71 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> { | |||||||
|   EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} |   EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} | ||||||
|   TEMPLATABLE_VALUE(uint32_t, sleep_duration); |   TEMPLATABLE_VALUE(uint32_t, sleep_duration); | ||||||
|  |  | ||||||
|  | #ifdef USE_TIME | ||||||
|  |   void set_until(uint8_t hour, uint8_t minute, uint8_t second) { | ||||||
|  |     this->hour_ = hour; | ||||||
|  |     this->minute_ = minute; | ||||||
|  |     this->second_ = second; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_time(time::RealTimeClock *time) { this->time_ = time; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     if (this->sleep_duration_.has_value()) { |     if (this->sleep_duration_.has_value()) { | ||||||
|       this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); |       this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...)); | ||||||
|     } |     } | ||||||
|  | #ifdef USE_TIME | ||||||
|  |  | ||||||
|  |     if (this->hour_.has_value()) { | ||||||
|  |       auto time = this->time_->now(); | ||||||
|  |       const uint32_t timestamp_now = time.timestamp; | ||||||
|  |  | ||||||
|  |       bool after_time = false; | ||||||
|  |       if (time.hour > this->hour_) { | ||||||
|  |         after_time = true; | ||||||
|  |       } else { | ||||||
|  |         if (time.hour == this->hour_) { | ||||||
|  |           if (time.minute > this->minute_) { | ||||||
|  |             after_time = true; | ||||||
|  |           } else { | ||||||
|  |             if (time.minute == this->minute_) { | ||||||
|  |               if (time.second > this->second_) { | ||||||
|  |                 after_time = true; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       time.hour = *this->hour_; | ||||||
|  |       time.minute = *this->minute_; | ||||||
|  |       time.second = *this->second_; | ||||||
|  |       time.recalc_timestamp_utc(); | ||||||
|  |  | ||||||
|  |       time_t timestamp = time.timestamp;  // timestamp in local time zone | ||||||
|  |  | ||||||
|  |       if (after_time) | ||||||
|  |         timestamp += 60 * 60 * 24; | ||||||
|  |  | ||||||
|  |       int32_t offset = time::ESPTime::timezone_offset(); | ||||||
|  |       timestamp -= offset;  // Change timestamp to utc | ||||||
|  |       const uint32_t ms_left = (timestamp - timestamp_now) * 1000; | ||||||
|  |       this->deep_sleep_->set_sleep_duration(ms_left); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|     this->deep_sleep_->begin_sleep(true); |     this->deep_sleep_->begin_sleep(true); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   DeepSleepComponent *deep_sleep_; |   DeepSleepComponent *deep_sleep_; | ||||||
|  | #ifdef USE_TIME | ||||||
|  |   optional<uint8_t> hour_; | ||||||
|  |   optional<uint8_t> minute_; | ||||||
|  |   optional<uint8_t> second_; | ||||||
|  |  | ||||||
|  |   time::RealTimeClock *time_; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> { | template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> { | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ using namespace esphome::cover; | |||||||
| CoverTraits EndstopCover::get_traits() { | CoverTraits EndstopCover::get_traits() { | ||||||
|   auto traits = CoverTraits(); |   auto traits = CoverTraits(); | ||||||
|   traits.set_supports_position(true); |   traits.set_supports_position(true); | ||||||
|  |   traits.set_supports_toggle(true); | ||||||
|   traits.set_is_assumed_state(false); |   traits.set_is_assumed_state(false); | ||||||
|   return traits; |   return traits; | ||||||
| } | } | ||||||
| @@ -20,6 +21,20 @@ void EndstopCover::control(const CoverCall &call) { | |||||||
|     this->start_direction_(COVER_OPERATION_IDLE); |     this->start_direction_(COVER_OPERATION_IDLE); | ||||||
|     this->publish_state(); |     this->publish_state(); | ||||||
|   } |   } | ||||||
|  |   if (call.get_toggle().has_value()) { | ||||||
|  |     if (this->current_operation != COVER_OPERATION_IDLE) { | ||||||
|  |       this->start_direction_(COVER_OPERATION_IDLE); | ||||||
|  |       this->publish_state(); | ||||||
|  |     } else { | ||||||
|  |       if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) { | ||||||
|  |         this->target_position_ = COVER_OPEN; | ||||||
|  |         this->start_direction_(COVER_OPERATION_OPENING); | ||||||
|  |       } else { | ||||||
|  |         this->target_position_ = COVER_CLOSED; | ||||||
|  |         this->start_direction_(COVER_OPERATION_CLOSING); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   if (call.get_position().has_value()) { |   if (call.get_position().has_value()) { | ||||||
|     auto pos = *call.get_position(); |     auto pos = *call.get_position(); | ||||||
|     if (pos == this->position) { |     if (pos == this->position) { | ||||||
| @@ -125,9 +140,11 @@ void EndstopCover::start_direction_(CoverOperation dir) { | |||||||
|       trig = this->stop_trigger_; |       trig = this->stop_trigger_; | ||||||
|       break; |       break; | ||||||
|     case COVER_OPERATION_OPENING: |     case COVER_OPERATION_OPENING: | ||||||
|  |       this->last_operation_ = dir; | ||||||
|       trig = this->open_trigger_; |       trig = this->open_trigger_; | ||||||
|       break; |       break; | ||||||
|     case COVER_OPERATION_CLOSING: |     case COVER_OPERATION_CLOSING: | ||||||
|  |       this->last_operation_ = dir; | ||||||
|       trig = this->close_trigger_; |       trig = this->close_trigger_; | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component { | |||||||
|   uint32_t start_dir_time_{0}; |   uint32_t start_dir_time_{0}; | ||||||
|   uint32_t last_publish_time_{0}; |   uint32_t last_publish_time_{0}; | ||||||
|   float target_position_{0}; |   float target_position_{0}; | ||||||
|  |   cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace endstop | }  // namespace endstop | ||||||
|   | |||||||
| @@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ | |||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|  |   for (auto *client : global_esp32_ble_tracker->clients_) { | ||||||
|  |     client->gap_event_handler(event, param); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | ||||||
|   | |||||||
| @@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener { | |||||||
|  public: |  public: | ||||||
|   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                    esp_ble_gattc_cb_param_t *param) = 0; |                                    esp_ble_gattc_cb_param_t *param) = 0; | ||||||
|  |   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||||
|   virtual void connect() = 0; |   virtual void connect() = 0; | ||||||
|   void set_state(ClientState st) { this->state_ = st; } |   void set_state(ClientState st) { this->state_ = st; } | ||||||
|   ClientState state() const { return state_; } |   ClientState state() const { return state_; } | ||||||
|   | |||||||
| @@ -1,12 +1,29 @@ | |||||||
| import functools | import functools | ||||||
|  | from pathlib import Path | ||||||
|  | import hashlib | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | import requests | ||||||
|  |  | ||||||
| from esphome import core | from esphome import core | ||||||
| from esphome.components import display | from esphome.components import display | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE | from esphome.const import ( | ||||||
|  |     CONF_FAMILY, | ||||||
|  |     CONF_FILE, | ||||||
|  |     CONF_GLYPHS, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_RAW_DATA_ID, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     CONF_SIZE, | ||||||
|  |     CONF_PATH, | ||||||
|  |     CONF_WEIGHT, | ||||||
|  | ) | ||||||
| from esphome.core import CORE, HexInt | from esphome.core import CORE, HexInt | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DOMAIN = "font" | ||||||
| DEPENDENCIES = ["display"] | DEPENDENCIES = ["display"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| @@ -71,6 +88,128 @@ def validate_truetype_file(value): | |||||||
|     return cv.file_(value) |     return cv.file_(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _compute_gfonts_local_path(value) -> Path: | ||||||
|  |     name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1" | ||||||
|  |     base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN | ||||||
|  |     h = hashlib.new("sha256") | ||||||
|  |     h.update(name.encode()) | ||||||
|  |     return base_dir / h.hexdigest()[:8] / "font.ttf" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TYPE_LOCAL = "local" | ||||||
|  | TYPE_GFONTS = "gfonts" | ||||||
|  | LOCAL_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_PATH): validate_truetype_file, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  | CONF_ITALIC = "italic" | ||||||
|  | FONT_WEIGHTS = { | ||||||
|  |     "thin": 100, | ||||||
|  |     "extra-light": 200, | ||||||
|  |     "light": 300, | ||||||
|  |     "regular": 400, | ||||||
|  |     "medium": 500, | ||||||
|  |     "semi-bold": 600, | ||||||
|  |     "bold": 700, | ||||||
|  |     "extra-bold": 800, | ||||||
|  |     "black": 900, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_weight_name(value): | ||||||
|  |     return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def download_gfonts(value): | ||||||
|  |     wght = value[CONF_WEIGHT] | ||||||
|  |     if value[CONF_ITALIC]: | ||||||
|  |         wght = f"1,{wght}" | ||||||
|  |     name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}" | ||||||
|  |     url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}" | ||||||
|  |  | ||||||
|  |     path = _compute_gfonts_local_path(value) | ||||||
|  |     if path.is_file(): | ||||||
|  |         return value | ||||||
|  |     try: | ||||||
|  |         req = requests.get(url) | ||||||
|  |         req.raise_for_status() | ||||||
|  |     except requests.exceptions.RequestException as e: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"Could not download font for {name}, please check the fonts exists " | ||||||
|  |             f"at google fonts ({e})" | ||||||
|  |         ) | ||||||
|  |     match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text) | ||||||
|  |     if match is None: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"Could not extract ttf file from gfonts response for {name}, " | ||||||
|  |             f"please report this." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     ttf_url = match.group(1) | ||||||
|  |     try: | ||||||
|  |         req = requests.get(ttf_url) | ||||||
|  |         req.raise_for_status() | ||||||
|  |     except requests.exceptions.RequestException as e: | ||||||
|  |         raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}") | ||||||
|  |  | ||||||
|  |     path.parent.mkdir(exist_ok=True, parents=True) | ||||||
|  |     path.write_bytes(req.content) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | GFONTS_SCHEMA = cv.All( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_FAMILY): cv.string_strict, | ||||||
|  |         cv.Optional(CONF_WEIGHT, default="regular"): cv.Any( | ||||||
|  |             cv.int_, validate_weight_name | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ITALIC, default=False): cv.boolean, | ||||||
|  |     }, | ||||||
|  |     download_gfonts, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_file_shorthand(value): | ||||||
|  |     value = cv.string_strict(value) | ||||||
|  |     if value.startswith("gfonts://"): | ||||||
|  |         match = re.match(r"^gfonts://([^@]+)(@.+)?$", value) | ||||||
|  |         if match is None: | ||||||
|  |             raise cv.Invalid("Could not parse gfonts shorthand syntax, please check it") | ||||||
|  |         family = match.group(1) | ||||||
|  |         weight = match.group(2) | ||||||
|  |         data = { | ||||||
|  |             CONF_TYPE: TYPE_GFONTS, | ||||||
|  |             CONF_FAMILY: family, | ||||||
|  |         } | ||||||
|  |         if weight is not None: | ||||||
|  |             data[CONF_WEIGHT] = weight[1:] | ||||||
|  |         return FILE_SCHEMA(data) | ||||||
|  |     return FILE_SCHEMA( | ||||||
|  |         { | ||||||
|  |             CONF_TYPE: TYPE_LOCAL, | ||||||
|  |             CONF_PATH: value, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TYPED_FILE_SCHEMA = cv.typed_schema( | ||||||
|  |     { | ||||||
|  |         TYPE_LOCAL: LOCAL_SCHEMA, | ||||||
|  |         TYPE_GFONTS: GFONTS_SCHEMA, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _file_schema(value): | ||||||
|  |     if isinstance(value, str): | ||||||
|  |         return validate_file_shorthand(value) | ||||||
|  |     return TYPED_FILE_SCHEMA(value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FILE_SCHEMA = cv.Schema(_file_schema) | ||||||
|  |  | ||||||
|  |  | ||||||
| DEFAULT_GLYPHS = ( | DEFAULT_GLYPHS = ( | ||||||
|     ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' |     ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' | ||||||
| ) | ) | ||||||
| @@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id" | |||||||
| FONT_SCHEMA = cv.Schema( | FONT_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_ID): cv.declare_id(Font), |         cv.Required(CONF_ID): cv.declare_id(Font), | ||||||
|         cv.Required(CONF_FILE): validate_truetype_file, |         cv.Required(CONF_FILE): FILE_SCHEMA, | ||||||
|         cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, |         cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, | ||||||
|         cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), |         cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1), | ||||||
|         cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), |         cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
| @@ -93,9 +232,13 @@ CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA) | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     from PIL import ImageFont |     from PIL import ImageFont | ||||||
|  |  | ||||||
|     path = CORE.relative_config_path(config[CONF_FILE]) |     conf = config[CONF_FILE] | ||||||
|  |     if conf[CONF_TYPE] == TYPE_LOCAL: | ||||||
|  |         path = CORE.relative_config_path(conf[CONF_PATH]) | ||||||
|  |     elif conf[CONF_TYPE] == TYPE_GFONTS: | ||||||
|  |         path = _compute_gfonts_local_path(conf) | ||||||
|     try: |     try: | ||||||
|         font = ImageFont.truetype(path, config[CONF_SIZE]) |         font = ImageFont.truetype(str(path), config[CONF_SIZE]) | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         raise core.EsphomeError(f"Could not load truetype file {path}: {e}") |         raise core.EsphomeError(f"Could not load truetype file {path}: {e}") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,9 +7,11 @@ namespace growatt_solar { | |||||||
| static const char *const TAG = "growatt_solar"; | static const char *const TAG = "growatt_solar"; | ||||||
|  |  | ||||||
| static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; | static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; | ||||||
| static const uint8_t MODBUS_REGISTER_COUNT = 33; | static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95};  // indexed with enum GrowattProtocolVersion | ||||||
|  |  | ||||||
| void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } | void GrowattSolar::update() { | ||||||
|  |   this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]); | ||||||
|  | } | ||||||
|  |  | ||||||
| void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) { | void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) { | ||||||
|   auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { |   auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { | ||||||
| @@ -27,37 +29,76 @@ void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) { | |||||||
|       sensor->publish_state(value); |       sensor->publish_state(value); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->inverter_status_, 0, 1); |   switch (this->protocol_version_) { | ||||||
|  |     case RTU: { | ||||||
|  |       publish_1_reg_sensor_state(this->inverter_status_, 0, 1); | ||||||
|  |  | ||||||
|   publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT); |       publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT); | ||||||
|   publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT); | ||||||
|   publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT); |       publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|   publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT); |       publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case RTU2: { | ||||||
|  |       publish_1_reg_sensor_state(this->inverter_status_, 0, 1); | ||||||
|  |  | ||||||
|  |       publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_2_reg_sensor_state(this->grid_active_power_sensor_, 35, 36, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->grid_frequency_sensor_, 37, TWO_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 38, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 39, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 40, 41, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 42, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 43, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 44, 45, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 46, ONE_DEC_UNIT); | ||||||
|  |       publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 47, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 48, 49, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_2_reg_sensor_state(this->today_production_, 53, 54, ONE_DEC_UNIT); | ||||||
|  |       publish_2_reg_sensor_state(this->total_energy_production_, 55, 56, ONE_DEC_UNIT); | ||||||
|  |  | ||||||
|  |       publish_1_reg_sensor_state(this->inverter_module_temp_, 93, ONE_DEC_UNIT); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void GrowattSolar::dump_config() { | void GrowattSolar::dump_config() { | ||||||
|   | |||||||
| @@ -10,12 +10,19 @@ namespace growatt_solar { | |||||||
| static const float TWO_DEC_UNIT = 0.01; | static const float TWO_DEC_UNIT = 0.01; | ||||||
| static const float ONE_DEC_UNIT = 0.1; | static const float ONE_DEC_UNIT = 0.1; | ||||||
|  |  | ||||||
|  | enum GrowattProtocolVersion { | ||||||
|  |   RTU = 0, | ||||||
|  |   RTU2, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { | class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { | ||||||
|  public: |  public: | ||||||
|   void update() override; |   void update() override; | ||||||
|   void on_modbus_data(const std::vector<uint8_t> &data) override; |   void on_modbus_data(const std::vector<uint8_t> &data) override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void set_protocol_version(GrowattProtocolVersion protocol_version) { this->protocol_version_ = protocol_version; } | ||||||
|  |  | ||||||
|   void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; } |   void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; } | ||||||
|  |  | ||||||
|   void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; } |   void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; } | ||||||
| @@ -67,6 +74,7 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { | |||||||
|   sensor::Sensor *today_production_{nullptr}; |   sensor::Sensor *today_production_{nullptr}; | ||||||
|   sensor::Sensor *total_energy_production_{nullptr}; |   sensor::Sensor *total_energy_production_{nullptr}; | ||||||
|   sensor::Sensor *inverter_module_temp_{nullptr}; |   sensor::Sensor *inverter_module_temp_{nullptr}; | ||||||
|  |   GrowattProtocolVersion protocol_version_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace growatt_solar | }  // namespace growatt_solar | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA" | |||||||
| CONF_INVERTER_STATUS = "inverter_status" | CONF_INVERTER_STATUS = "inverter_status" | ||||||
| CONF_PV_ACTIVE_POWER = "pv_active_power" | CONF_PV_ACTIVE_POWER = "pv_active_power" | ||||||
| CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" | CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" | ||||||
|  | CONF_PROTOCOL_VERSION = "protocol_version" | ||||||
|  |  | ||||||
| AUTO_LOAD = ["modbus"] | AUTO_LOAD = ["modbus"] | ||||||
| CODEOWNERS = ["@leeuwte"] | CODEOWNERS = ["@leeuwte"] | ||||||
| @@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema( | |||||||
|     {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} |     {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | GrowattProtocolVersion = growatt_solar_ns.enum("GrowattProtocolVersion") | ||||||
|  | PROTOCOL_VERSIONS = { | ||||||
|  |     "RTU": GrowattProtocolVersion.RTU, | ||||||
|  |     "RTU2": GrowattProtocolVersion.RTU2, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(GrowattSolar), |             cv.GenerateID(): cv.declare_id(GrowattSolar), | ||||||
|  |             cv.Optional(CONF_PROTOCOL_VERSION, default="RTU"): cv.enum( | ||||||
|  |                 PROTOCOL_VERSIONS, upper=True | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, |             cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, | ||||||
|             cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, |             cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, | ||||||
|             cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, |             cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, | ||||||
| @@ -152,6 +162,8 @@ async def to_code(config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await modbus.register_modbus_device(var, config) |     await modbus.register_modbus_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION])) | ||||||
|  |  | ||||||
|     if CONF_INVERTER_STATUS in config: |     if CONF_INVERTER_STATUS in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) |         sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) | ||||||
|         cg.add(var.set_inverter_status_sensor(sens)) |         cg.add(var.set_inverter_status_sensor(sens)) | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ PROTOCOLS = { | |||||||
|     "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, |     "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, | ||||||
|     "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, |     "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, | ||||||
|     "daikin": Protocol.PROTOCOL_DAIKIN, |     "daikin": Protocol.PROTOCOL_DAIKIN, | ||||||
|  |     "electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL, | ||||||
|     "fuego": Protocol.PROTOCOL_FUEGO, |     "fuego": Protocol.PROTOCOL_FUEGO, | ||||||
|     "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, |     "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, | ||||||
|     "gree": Protocol.PROTOCOL_GREE, |     "gree": Protocol.PROTOCOL_GREE, | ||||||
| @@ -112,6 +113,4 @@ def to_code(config): | |||||||
|     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) |     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) | ||||||
|     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) |     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||||
|  |  | ||||||
|     # PIO isn't updating releases, so referencing the release tag directly. See: |     cg.add_library("tonia/HeatpumpIR", "1.0.20") | ||||||
|     # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd |  | ||||||
|     cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18") |  | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP | |||||||
|     {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }},                 // NOLINT |     {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }},                 // NOLINT | ||||||
|     {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }},              // NOLINT |     {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }},              // NOLINT | ||||||
|     {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }},                              // NOLINT |     {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }},                              // NOLINT | ||||||
|  |     {PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }},                // NOLINT | ||||||
|     {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }},                                // NOLINT |     {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }},                                // NOLINT | ||||||
|     {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }},                       // NOLINT |     {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }},                       // NOLINT | ||||||
|     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT |     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ enum Protocol { | |||||||
|   PROTOCOL_DAIKIN_ARC417, |   PROTOCOL_DAIKIN_ARC417, | ||||||
|   PROTOCOL_DAIKIN_ARC480, |   PROTOCOL_DAIKIN_ARC480, | ||||||
|   PROTOCOL_DAIKIN, |   PROTOCOL_DAIKIN, | ||||||
|  |   PROTOCOL_ELECTROLUXYAL, | ||||||
|   PROTOCOL_FUEGO, |   PROTOCOL_FUEGO, | ||||||
|   PROTOCOL_FUJITSU_AWYZ, |   PROTOCOL_FUJITSU_AWYZ, | ||||||
|   PROTOCOL_GREE, |   PROTOCOL_GREE, | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ namespace hm3301 { | |||||||
|  |  | ||||||
| class AbstractAQICalculator { | class AbstractAQICalculator { | ||||||
|  public: |  public: | ||||||
|   virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; |   virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ namespace hm3301 { | |||||||
|  |  | ||||||
| class AQICalculator : public AbstractAQICalculator { | class AQICalculator : public AbstractAQICalculator { | ||||||
|  public: |  public: | ||||||
|   uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { |   uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { | ||||||
|     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); |     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); | ||||||
|     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); |     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ namespace hm3301 { | |||||||
|  |  | ||||||
| class CAQICalculator : public AbstractAQICalculator { | class CAQICalculator : public AbstractAQICalculator { | ||||||
|  public: |  public: | ||||||
|   uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { |   uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { | ||||||
|     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); |     int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_); | ||||||
|     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); |     int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ void HM3301Component::update() { | |||||||
|     pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); |     pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   int8_t aqi_value = -1; |   int16_t aqi_value = -1; | ||||||
|   if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { |   if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) { | ||||||
|     AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); |     AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_); | ||||||
|     aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value); |     aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value); | ||||||
|   | |||||||
| @@ -1,4 +1,20 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL | ||||||
|  |  | ||||||
| CODEOWNERS = ["@OttoWinter"] | CODEOWNERS = ["@OttoWinter"] | ||||||
| homeassistant_ns = cg.esphome_ns.namespace("homeassistant") | homeassistant_ns = cg.esphome_ns.namespace("homeassistant") | ||||||
|  |  | ||||||
|  | HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||||
|  |         cv.Optional(CONF_ATTRIBUTE): cv.string, | ||||||
|  |         cv.Optional(CONF_INTERNAL, default=True): cv.boolean, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def setup_home_assistant_entity(var, config): | ||||||
|  |     cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) | ||||||
|  |     if CONF_ATTRIBUTE in config: | ||||||
|  |         cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) | ||||||
|   | |||||||
| @@ -1,30 +1,24 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID |  | ||||||
| from .. import homeassistant_ns | from .. import ( | ||||||
|  |     HOME_ASSISTANT_IMPORT_SCHEMA, | ||||||
|  |     homeassistant_ns, | ||||||
|  |     setup_home_assistant_entity, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["api"] | DEPENDENCIES = ["api"] | ||||||
|  |  | ||||||
| HomeassistantBinarySensor = homeassistant_ns.class_( | HomeassistantBinarySensor = homeassistant_ns.class_( | ||||||
|     "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component |     "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend( | ||||||
|     binary_sensor.binary_sensor_schema(HomeassistantBinarySensor) |     HOME_ASSISTANT_IMPORT_SCHEMA | ||||||
|     .extend( |  | ||||||
|         { |  | ||||||
|             cv.Required(CONF_ENTITY_ID): cv.entity_id, |  | ||||||
|             cv.Optional(CONF_ATTRIBUTE): cv.string, |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = await binary_sensor.new_binary_sensor(config) |     var = await binary_sensor.new_binary_sensor(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     setup_home_assistant_entity(var, config) | ||||||
|     cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) |  | ||||||
|     if CONF_ATTRIBUTE in config: |  | ||||||
|         cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) |  | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import ( |  | ||||||
|     CONF_ATTRIBUTE, | from .. import ( | ||||||
|     CONF_ENTITY_ID, |     HOME_ASSISTANT_IMPORT_SCHEMA, | ||||||
|     CONF_ID, |     homeassistant_ns, | ||||||
|  |     setup_home_assistant_entity, | ||||||
| ) | ) | ||||||
| from .. import homeassistant_ns |  | ||||||
|  |  | ||||||
| DEPENDENCIES = ["api"] | DEPENDENCIES = ["api"] | ||||||
|  |  | ||||||
| @@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_( | |||||||
|     "HomeassistantSensor", sensor.Sensor, cg.Component |     "HomeassistantSensor", sensor.Sensor, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend( | CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend( | ||||||
|     { |     HOME_ASSISTANT_IMPORT_SCHEMA | ||||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, |  | ||||||
|         cv.Optional(CONF_ATTRIBUTE): cv.string, |  | ||||||
|     } |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = await sensor.new_sensor(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await sensor.register_sensor(var, config) |     setup_home_assistant_entity(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) |  | ||||||
|     if CONF_ATTRIBUTE in config: |  | ||||||
|         cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) |  | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.components import text_sensor | from esphome.components import text_sensor | ||||||
| from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID |  | ||||||
|  |  | ||||||
| from .. import homeassistant_ns | from .. import ( | ||||||
|  |     HOME_ASSISTANT_IMPORT_SCHEMA, | ||||||
|  |     homeassistant_ns, | ||||||
|  |     setup_home_assistant_entity, | ||||||
|  | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["api"] | DEPENDENCIES = ["api"] | ||||||
|  |  | ||||||
| @@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_( | |||||||
|     "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component |     "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( | CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend( | ||||||
|     { |     HOME_ASSISTANT_IMPORT_SCHEMA | ||||||
|         cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), |  | ||||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, |  | ||||||
|         cv.Optional(CONF_ATTRIBUTE): cv.string, |  | ||||||
|     } |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = await text_sensor.new_text_sensor(config) |     var = await text_sensor.new_text_sensor(config) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     setup_home_assistant_entity(var, config) | ||||||
|     cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) |  | ||||||
|     if CONF_ATTRIBUTE in config: |  | ||||||
|         cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) |  | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								esphome/components/hydreon_rgxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								esphome/components/hydreon_rgxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import uart | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@functionpointer"] | ||||||
|  | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
|  | hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx") | ||||||
|  | RGModel = hydreon_rgxx_ns.enum("RGModel") | ||||||
|  | HydreonRGxxComponent = hydreon_rgxx_ns.class_( | ||||||
|  |     "HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice | ||||||
|  | ) | ||||||
							
								
								
									
										36
									
								
								esphome/components/hydreon_rgxx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/hydreon_rgxx/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_COLD, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import hydreon_rgxx_ns, HydreonRGxxComponent | ||||||
|  |  | ||||||
|  | CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" | ||||||
|  | CONF_TOO_COLD = "too_cold" | ||||||
|  |  | ||||||
|  | HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( | ||||||
|  |     "HydreonRGxxBinaryComponent", cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(HydreonRGxxBinarySensor), | ||||||
|  |         cv.GenerateID(CONF_HYDREON_RGXX_ID): cv.use_id(HydreonRGxxComponent), | ||||||
|  |         cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( | ||||||
|  |             device_class=DEVICE_CLASS_COLD | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) | ||||||
|  |     bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) | ||||||
|  |     await cg.register_component(bin_component, config) | ||||||
|  |     if CONF_TOO_COLD in config: | ||||||
|  |         tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD]) | ||||||
|  |         cg.add(main_sensor.set_too_cold_sensor(tc)) | ||||||
							
								
								
									
										211
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | |||||||
|  | #include "hydreon_rgxx.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hydreon_rgxx { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "hydreon_rgxx.sensor"; | ||||||
|  | static const int MAX_DATA_LENGTH_BYTES = 80; | ||||||
|  | static const uint8_t ASCII_LF = 0x0A; | ||||||
|  | #define HYDREON_RGXX_COMMA , | ||||||
|  | static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; | ||||||
|  |  | ||||||
|  | void HydreonRGxxComponent::dump_config() { | ||||||
|  |   this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); | ||||||
|  |   ESP_LOGCONFIG(TAG, "hydreon_rgxx:"); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  |   int i = 0; | ||||||
|  | #define HYDREON_RGXX_LOG_SENSOR(s) \ | ||||||
|  |   if (this->sensors_[i++] != nullptr) { \ | ||||||
|  |     LOG_SENSOR("  ", #s, this->sensors_[i - 1]); \ | ||||||
|  |   } | ||||||
|  |   HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HydreonRGxxComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx..."); | ||||||
|  |   while (this->available() != 0) { | ||||||
|  |     this->read(); | ||||||
|  |   } | ||||||
|  |   this->schedule_reboot_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HydreonRGxxComponent::sensor_missing_() { | ||||||
|  |   if (this->sensors_received_ == -1) { | ||||||
|  |     // no request sent yet, don't check | ||||||
|  |     return false; | ||||||
|  |   } else { | ||||||
|  |     if (this->sensors_received_ == 0) { | ||||||
|  |       ESP_LOGW(TAG, "No data at all"); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     for (int i = 0; i < NUM_SENSORS; i++) { | ||||||
|  |       if (this->sensors_[i] == nullptr) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       if ((this->sensors_received_ >> i & 1) == 0) { | ||||||
|  |         ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HydreonRGxxComponent::update() { | ||||||
|  |   if (this->boot_count_ > 0) { | ||||||
|  |     if (this->sensor_missing_()) { | ||||||
|  |       this->no_response_count_++; | ||||||
|  |       ESP_LOGE(TAG, "data missing %d times", this->no_response_count_); | ||||||
|  |       if (this->no_response_count_ > 15) { | ||||||
|  |         ESP_LOGE(TAG, "asking sensor to reboot"); | ||||||
|  |         for (auto &sensor : this->sensors_) { | ||||||
|  |           if (sensor != nullptr) { | ||||||
|  |             sensor->publish_state(NAN); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         this->schedule_reboot_(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       this->no_response_count_ = 0; | ||||||
|  |     } | ||||||
|  |     this->write_str("R\n"); | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |     if (this->too_cold_sensor_ != nullptr) { | ||||||
|  |       this->too_cold_sensor_->publish_state(this->too_cold_); | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     this->too_cold_ = false; | ||||||
|  |     this->sensors_received_ = 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HydreonRGxxComponent::loop() { | ||||||
|  |   uint8_t data; | ||||||
|  |   while (this->available() > 0) { | ||||||
|  |     if (this->read_byte(&data)) { | ||||||
|  |       buffer_ += (char) data; | ||||||
|  |       if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) { | ||||||
|  |         // complete line received | ||||||
|  |         this->process_line_(); | ||||||
|  |         this->buffer_.clear(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Communication with the sensor is asynchronous. | ||||||
|  |  * We send requests and let esphome continue doing its thing. | ||||||
|  |  * Once we have received a complete line, we process it. | ||||||
|  |  * | ||||||
|  |  * Catching communication failures is done in two layers: | ||||||
|  |  * | ||||||
|  |  * 1. We check if all requested data has been received | ||||||
|  |  *    before we send out the next request. If data keeps | ||||||
|  |  *    missing, we escalate. | ||||||
|  |  * 2. Request the sensor to reboot. We retry based on | ||||||
|  |  *    a timeout. If the sensor does not respond after | ||||||
|  |  *    several boot attempts, we give up. | ||||||
|  |  */ | ||||||
|  | void HydreonRGxxComponent::schedule_reboot_() { | ||||||
|  |   this->boot_count_ = 0; | ||||||
|  |   this->set_interval("reboot", 5000, [this]() { | ||||||
|  |     if (this->boot_count_ < 0) { | ||||||
|  |       ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_); | ||||||
|  |     } | ||||||
|  |     this->boot_count_--; | ||||||
|  |     this->write_str("K\n"); | ||||||
|  |     if (this->boot_count_ < -5) { | ||||||
|  |       ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up"); | ||||||
|  |       for (auto &sensor : this->sensors_) { | ||||||
|  |         if (sensor != nullptr) { | ||||||
|  |           sensor->publish_state(NAN); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       this->mark_failed(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) { | ||||||
|  |   return this->buffer_starts_with_(prefix.c_str()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; } | ||||||
|  |  | ||||||
|  | void HydreonRGxxComponent::process_line_() { | ||||||
|  |   ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |  | ||||||
|  |   if (buffer_[0] == ';') { | ||||||
|  |     ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_starts_with_("PwrDays")) { | ||||||
|  |     if (this->boot_count_ <= 0) { | ||||||
|  |       this->boot_count_ = 1; | ||||||
|  |     } else { | ||||||
|  |       this->boot_count_++; | ||||||
|  |     } | ||||||
|  |     this->cancel_interval("reboot"); | ||||||
|  |     this->no_response_count_ = 0; | ||||||
|  |     ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |     this->write_str("P\nH\nM\n");  // set sensor to polling mode, high res mode, metric mode | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->buffer_starts_with_("SW")) { | ||||||
|  |     std::string::size_type majend = this->buffer_.find('.'); | ||||||
|  |     std::string::size_type endversion = this->buffer_.find(' ', 3); | ||||||
|  |     if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) { | ||||||
|  |       ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |     } | ||||||
|  |     int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10); | ||||||
|  |     int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10); | ||||||
|  |  | ||||||
|  |     if (major > 10 || minor >= 1000 || minor < 0 || major < 0) { | ||||||
|  |       ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); | ||||||
|  |     } | ||||||
|  |     this->sw_version_ = major * 1000 + minor; | ||||||
|  |     ESP_LOGI(TAG, "detected sw version %i", this->sw_version_); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   bool is_data_line = false; | ||||||
|  |   for (int i = 0; i < NUM_SENSORS; i++) { | ||||||
|  |     if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) { | ||||||
|  |       is_data_line = true; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (is_data_line) { | ||||||
|  |     std::string::size_type tc = this->buffer_.find("TooCold"); | ||||||
|  |     this->too_cold_ |= tc != std::string::npos; | ||||||
|  |     if (this->too_cold_) { | ||||||
|  |       ESP_LOGD(TAG, "Received TooCold"); | ||||||
|  |     } | ||||||
|  |     for (int i = 0; i < NUM_SENSORS; i++) { | ||||||
|  |       if (this->sensors_[i] == nullptr) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]); | ||||||
|  |       if (n == std::string::npos) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10); | ||||||
|  |       this->sensors_[i]->publish_state(data); | ||||||
|  |       ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); | ||||||
|  |       this->sensors_received_ |= (1 << i); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float HydreonRGxxComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace hydreon_rgxx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										76
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/hydreon_rgxx/hydreon_rgxx.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif | ||||||
|  | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace hydreon_rgxx { | ||||||
|  |  | ||||||
|  | enum RGModel { | ||||||
|  |   RG9 = 1, | ||||||
|  |   RG15 = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #ifdef HYDREON_RGXX_NUM_SENSORS | ||||||
|  | static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS; | ||||||
|  | #else | ||||||
|  | static const uint8_t NUM_SENSORS = 1; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #ifndef HYDREON_RGXX_PROTOCOL_LIST | ||||||
|  | #define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { | ||||||
|  |  public: | ||||||
|  |   void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } | ||||||
|  | #endif | ||||||
|  |   void set_model(RGModel model) { model_ = model; } | ||||||
|  |  | ||||||
|  |   /// Schedule data readings. | ||||||
|  |   void update() override; | ||||||
|  |   /// Read data once available | ||||||
|  |   void loop() override; | ||||||
|  |   /// Setup the sensor and test for a connection. | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void process_line_(); | ||||||
|  |   void schedule_reboot_(); | ||||||
|  |   bool buffer_starts_with_(const std::string &prefix); | ||||||
|  |   bool buffer_starts_with_(const char *prefix); | ||||||
|  |   bool sensor_missing_(); | ||||||
|  |  | ||||||
|  |   sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |   binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   int16_t boot_count_ = 0; | ||||||
|  |   int16_t no_response_count_ = 0; | ||||||
|  |   std::string buffer_; | ||||||
|  |   RGModel model_ = RG9; | ||||||
|  |   int sw_version_ = 0; | ||||||
|  |   bool too_cold_ = false; | ||||||
|  |  | ||||||
|  |   // bit field showing which sensors we have received data for | ||||||
|  |   int sensors_received_ = -1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class HydreonRGxxBinaryComponent : public Component { | ||||||
|  |  public: | ||||||
|  |   HydreonRGxxBinaryComponent(HydreonRGxxComponent *parent) {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace hydreon_rgxx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										119
									
								
								esphome/components/hydreon_rgxx/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								esphome/components/hydreon_rgxx/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import uart, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_MODEL, | ||||||
|  |     CONF_MOISTURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from . import RGModel, HydreonRGxxComponent | ||||||
|  |  | ||||||
|  | UNIT_INTENSITY = "intensity" | ||||||
|  | UNIT_MILLIMETERS = "mm" | ||||||
|  | UNIT_MILLIMETERS_PER_HOUR = "mm/h" | ||||||
|  |  | ||||||
|  | CONF_ACC = "acc" | ||||||
|  | CONF_EVENT_ACC = "event_acc" | ||||||
|  | CONF_TOTAL_ACC = "total_acc" | ||||||
|  | CONF_R_INT = "r_int" | ||||||
|  |  | ||||||
|  | RG_MODELS = { | ||||||
|  |     "RG_9": RGModel.RG9, | ||||||
|  |     "RG_15": RGModel.RG15, | ||||||
|  |     # https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf | ||||||
|  |     # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf | ||||||
|  |     # https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf | ||||||
|  | } | ||||||
|  | SUPPORTED_SENSORS = { | ||||||
|  |     CONF_ACC: ["RG_15"], | ||||||
|  |     CONF_EVENT_ACC: ["RG_15"], | ||||||
|  |     CONF_TOTAL_ACC: ["RG_15"], | ||||||
|  |     CONF_R_INT: ["RG_15"], | ||||||
|  |     CONF_MOISTURE: ["RG_9"], | ||||||
|  | } | ||||||
|  | PROTOCOL_NAMES = { | ||||||
|  |     CONF_MOISTURE: "R", | ||||||
|  |     CONF_ACC: "Acc", | ||||||
|  |     CONF_R_INT: "Rint", | ||||||
|  |     CONF_EVENT_ACC: "EventAcc", | ||||||
|  |     CONF_TOTAL_ACC: "TotalAcc", | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate(config): | ||||||
|  |     for conf, models in SUPPORTED_SENSORS.items(): | ||||||
|  |         if conf in config: | ||||||
|  |             if config[CONF_MODEL] not in models: | ||||||
|  |                 raise cv.Invalid( | ||||||
|  |                     f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}" | ||||||
|  |                 ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(HydreonRGxxComponent), | ||||||
|  |             cv.Required(CONF_MODEL): cv.enum( | ||||||
|  |                 RG_MODELS, | ||||||
|  |                 upper=True, | ||||||
|  |                 space="_", | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_ACC): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MILLIMETERS, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MILLIMETERS, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MILLIMETERS, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_R_INT): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_MOISTURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_INTENSITY, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(uart.UART_DEVICE_SCHEMA), | ||||||
|  |     _validate, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await uart.register_uart_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add_define( | ||||||
|  |         "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", | ||||||
|  |         cg.RawExpression( | ||||||
|  |             " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()]) | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |     cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) | ||||||
|  |  | ||||||
|  |     for i, conf in enumerate(PROTOCOL_NAMES): | ||||||
|  |         if conf in config: | ||||||
|  |             sens = await sensor.new_sensor(config[conf]) | ||||||
|  |             cg.add(var.set_sensor(sens, i)) | ||||||
| @@ -46,21 +46,21 @@ class I2CDevice { | |||||||
|   I2CRegister reg(uint8_t a_register) { return {this, a_register}; } |   I2CRegister reg(uint8_t a_register) { return {this, a_register}; } | ||||||
|  |  | ||||||
|   ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } |   ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } | ||||||
|   ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) { |   ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) { | ||||||
|     ErrorCode err = this->write(&a_register, 1); |     ErrorCode err = this->write(&a_register, 1, stop); | ||||||
|     if (err != ERROR_OK) |     if (err != ERROR_OK) | ||||||
|       return err; |       return err; | ||||||
|     return this->read(data, len); |     return this->read(data, len); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); } |   ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); } | ||||||
|   ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) { |   ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true) { | ||||||
|     WriteBuffer buffers[2]; |     WriteBuffer buffers[2]; | ||||||
|     buffers[0].data = &a_register; |     buffers[0].data = &a_register; | ||||||
|     buffers[0].len = 1; |     buffers[0].len = 1; | ||||||
|     buffers[1].data = data; |     buffers[1].data = data; | ||||||
|     buffers[1].len = len; |     buffers[1].len = len; | ||||||
|     return bus_->writev(address_, buffers, 2); |     return bus_->writev(address_, buffers, 2, stop); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Compat APIs |   // Compat APIs | ||||||
| @@ -93,7 +93,9 @@ class I2CDevice { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; } |   bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) { | ||||||
|  |     return read_register(a_register, data, 1, stop) == ERROR_OK; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   optional<uint8_t> read_byte(uint8_t a_register) { |   optional<uint8_t> read_byte(uint8_t a_register) { | ||||||
|     uint8_t data; |     uint8_t data; | ||||||
| @@ -104,8 +106,8 @@ class I2CDevice { | |||||||
|  |  | ||||||
|   bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); } |   bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); } | ||||||
|  |  | ||||||
|   bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { |   bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) { | ||||||
|     return write_register(a_register, data, len) == ERROR_OK; |     return write_register(a_register, data, len, stop) == ERROR_OK; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) { |   bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) { | ||||||
| @@ -118,7 +120,9 @@ class I2CDevice { | |||||||
|  |  | ||||||
|   bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); |   bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); | ||||||
|  |  | ||||||
|   bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); } |   bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) { | ||||||
|  |     return write_bytes(a_register, &data, 1, stop); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } |   bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ enum ErrorCode { | |||||||
|   ERROR_NOT_INITIALIZED = 4, |   ERROR_NOT_INITIALIZED = 4, | ||||||
|   ERROR_TOO_LARGE = 5, |   ERROR_TOO_LARGE = 5, | ||||||
|   ERROR_UNKNOWN = 6, |   ERROR_UNKNOWN = 6, | ||||||
|  |   ERROR_CRC = 7, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ReadBuffer { | struct ReadBuffer { | ||||||
| @@ -36,12 +37,18 @@ class I2CBus { | |||||||
|   } |   } | ||||||
|   virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; |   virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0; | ||||||
|   virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { |   virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) { | ||||||
|  |     return write(address, buffer, len, true); | ||||||
|  |   } | ||||||
|  |   virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) { | ||||||
|     WriteBuffer buf; |     WriteBuffer buf; | ||||||
|     buf.data = buffer; |     buf.data = buffer; | ||||||
|     buf.len = len; |     buf.len = len; | ||||||
|     return writev(address, &buf, 1); |     return writev(address, &buf, 1, stop); | ||||||
|   } |   } | ||||||
|   virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0; |   virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { | ||||||
|  |     return writev(address, buffers, cnt, true); | ||||||
|  |   } | ||||||
|  |   virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void i2c_scan_() { |   void i2c_scan_() { | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) | |||||||
|  |  | ||||||
|   return ERROR_OK; |   return ERROR_OK; | ||||||
| } | } | ||||||
| ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { | ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { | ||||||
|   // logging is only enabled with vv level, if warnings are shown the caller |   // logging is only enabled with vv level, if warnings are shown the caller | ||||||
|   // should log them |   // should log them | ||||||
|   if (!initialized_) { |   if (!initialized_) { | ||||||
| @@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn | |||||||
|       return ERROR_UNKNOWN; |       return ERROR_UNKNOWN; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   uint8_t status = wire_->endTransmission(true); |   uint8_t status = wire_->endTransmission(stop); | ||||||
|   if (status == 0) { |   if (status == 0) { | ||||||
|     return ERROR_OK; |     return ERROR_OK; | ||||||
|   } else if (status == 1) { |   } else if (status == 1) { | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component { | |||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; |   ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; | ||||||
|   ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; |   ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override; | ||||||
|   float get_setup_priority() const override { return setup_priority::BUS; } |   float get_setup_priority() const override { return setup_priority::BUS; } | ||||||
|  |  | ||||||
|   void set_scan(bool scan) { scan_ = scan; } |   void set_scan(bool scan) { scan_ = scan; } | ||||||
|   | |||||||
| @@ -142,7 +142,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { | |||||||
|  |  | ||||||
|   return ERROR_OK; |   return ERROR_OK; | ||||||
| } | } | ||||||
| ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { | ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { | ||||||
|   // logging is only enabled with vv level, if warnings are shown the caller |   // logging is only enabled with vv level, if warnings are shown the caller | ||||||
|   // should log them |   // should log them | ||||||
|   if (!initialized_) { |   if (!initialized_) { | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component { | |||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; |   ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override; | ||||||
|   ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override; |   ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override; | ||||||
|   float get_setup_priority() const override { return setup_priority::BUS; } |   float get_setup_priority() const override { return setup_priority::BUS; } | ||||||
|  |  | ||||||
|   void set_scan(bool scan) { scan_ = scan; } |   void set_scan(bool scan) { scan_ = scan; } | ||||||
|   | |||||||
| @@ -16,16 +16,24 @@ static const char *const TAG = "json"; | |||||||
| static std::vector<char> global_json_build_buffer;  // NOLINT | static std::vector<char> global_json_build_buffer;  // NOLINT | ||||||
|  |  | ||||||
| std::string build_json(const json_build_t &f) { | std::string build_json(const json_build_t &f) { | ||||||
|   // Here we are allocating as much heap memory as available minus 2kb to be safe |   // Here we are allocating up to 5kb of memory, | ||||||
|  |   // with the heap size minus 2kb to be safe if less than 5kb | ||||||
|   // as we can not have a true dynamic sized document. |   // as we can not have a true dynamic sized document. | ||||||
|   // The excess memory is freed below with `shrinkToFit()` |   // The excess memory is freed below with `shrinkToFit()` | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|   const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048;  // NOLINT(readability-static-accessed-through-instance) |   const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance) | ||||||
| #elif defined(USE_ESP32) | #elif defined(USE_ESP32) | ||||||
|   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; |   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   DynamicJsonDocument json_document(free_heap); |   const size_t request_size = std::min(free_heap - 2048, (size_t) 5120); | ||||||
|  |  | ||||||
|  |   DynamicJsonDocument json_document(request_size); | ||||||
|  |   if (json_document.memoryPool().buffer() == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes", | ||||||
|  |              request_size, free_heap); | ||||||
|  |     return "{}"; | ||||||
|  |   } | ||||||
|   JsonObject root = json_document.to<JsonObject>(); |   JsonObject root = json_document.to<JsonObject>(); | ||||||
|   f(root); |   f(root); | ||||||
|   json_document.shrinkToFit(); |   json_document.shrinkToFit(); | ||||||
| @@ -36,27 +44,45 @@ std::string build_json(const json_build_t &f) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void parse_json(const std::string &data, const json_parse_t &f) { | void parse_json(const std::string &data, const json_parse_t &f) { | ||||||
|   // Here we are allocating as much heap memory as available minus 2kb to be safe |   // Here we are allocating 1.5 times the data size, | ||||||
|  |   // with the heap size minus 2kb to be safe if less than that | ||||||
|   // as we can not have a true dynamic sized document. |   // as we can not have a true dynamic sized document. | ||||||
|   // The excess memory is freed below with `shrinkToFit()` |   // The excess memory is freed below with `shrinkToFit()` | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|   const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048;  // NOLINT(readability-static-accessed-through-instance) |   const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance) | ||||||
| #elif defined(USE_ESP32) | #elif defined(USE_ESP32) | ||||||
|   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048; |   const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL); | ||||||
| #endif | #endif | ||||||
|  |   bool pass = false; | ||||||
|  |   size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5)); | ||||||
|  |   do { | ||||||
|  |     DynamicJsonDocument json_document(request_size); | ||||||
|  |     if (json_document.memoryPool().buffer() == nullptr) { | ||||||
|  |       ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size, | ||||||
|  |                free_heap); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     DeserializationError err = deserializeJson(json_document, data); | ||||||
|  |     json_document.shrinkToFit(); | ||||||
|  |  | ||||||
|   DynamicJsonDocument json_document(free_heap); |     JsonObject root = json_document.as<JsonObject>(); | ||||||
|   DeserializationError err = deserializeJson(json_document, data); |  | ||||||
|   json_document.shrinkToFit(); |  | ||||||
|  |  | ||||||
|   JsonObject root = json_document.as<JsonObject>(); |     if (err == DeserializationError::Ok) { | ||||||
|  |       pass = true; | ||||||
|   if (err) { |       f(root); | ||||||
|     ESP_LOGW(TAG, "Parsing JSON failed."); |     } else if (err == DeserializationError::NoMemory) { | ||||||
|     return; |       if (request_size * 2 >= free_heap) { | ||||||
|   } |         ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); | ||||||
|  |         return; | ||||||
|   f(root); |       } | ||||||
|  |       ESP_LOGV(TAG, "Increasing memory allocation."); | ||||||
|  |       request_size *= 2; | ||||||
|  |       continue; | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGE(TAG, "JSON parse error: %s", err.c_str()); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } while (!pass); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace json | }  // namespace json | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import display | from esphome.components import display | ||||||
| from esphome.const import CONF_DIMENSIONS | from esphome.const import CONF_DIMENSIONS, CONF_POSITION, CONF_DATA | ||||||
|  |  | ||||||
|  | CONF_USER_CHARACTERS = "user_characters" | ||||||
|  |  | ||||||
| lcd_base_ns = cg.esphome_ns.namespace("lcd_base") | lcd_base_ns = cg.esphome_ns.namespace("lcd_base") | ||||||
| LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent) | LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent) | ||||||
| @@ -16,9 +18,35 @@ def validate_lcd_dimensions(value): | |||||||
|     return value |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_user_characters(value): | ||||||
|  |     positions = set() | ||||||
|  |     for conf in value: | ||||||
|  |         if conf[CONF_POSITION] in positions: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"Duplicate user defined character at position {conf[CONF_POSITION]}" | ||||||
|  |             ) | ||||||
|  |         positions.add(conf[CONF_POSITION]) | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
| LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( | LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend( | ||||||
|     { |     { | ||||||
|         cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions, |         cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions, | ||||||
|  |         cv.Optional(CONF_USER_CHARACTERS): cv.All( | ||||||
|  |             cv.ensure_list( | ||||||
|  |                 cv.Schema( | ||||||
|  |                     { | ||||||
|  |                         cv.Required(CONF_POSITION): cv.int_range(min=0, max=7), | ||||||
|  |                         cv.Required(CONF_DATA): cv.All( | ||||||
|  |                             cv.ensure_list(cv.int_range(min=0, max=31)), | ||||||
|  |                             cv.Length(min=8, max=8), | ||||||
|  |                         ), | ||||||
|  |                     } | ||||||
|  |                 ), | ||||||
|  |             ), | ||||||
|  |             cv.Length(max=8), | ||||||
|  |             validate_user_characters, | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(cv.polling_component_schema("1s")) | ).extend(cv.polling_component_schema("1s")) | ||||||
|  |  | ||||||
| @@ -27,3 +55,6 @@ async def setup_lcd_display(var, config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await display.register_display(var, config) |     await display.register_display(var, config) | ||||||
|     cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])) |     cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1])) | ||||||
|  |     if CONF_USER_CHARACTERS in config: | ||||||
|  |         for usr in config[CONF_USER_CHARACTERS]: | ||||||
|  |             cg.add(var.set_user_defined_char(usr[CONF_POSITION], usr[CONF_DATA])) | ||||||
|   | |||||||
| @@ -65,6 +65,13 @@ void LCDDisplay::setup() { | |||||||
|     this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); |     this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // store user defined characters | ||||||
|  |   for (auto &user_defined_char : this->user_defined_chars_) { | ||||||
|  |     this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (user_defined_char.first << 3)); | ||||||
|  |     for (auto data : user_defined_char.second) | ||||||
|  |       this->send(data, true); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); |   this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function); | ||||||
|   uint8_t display_control = LCD_DISPLAY_DISPLAY_ON; |   uint8_t display_control = LCD_DISPLAY_DISPLAY_ON; | ||||||
|   this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control); |   this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control); | ||||||
| @@ -160,6 +167,13 @@ void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time: | |||||||
| } | } | ||||||
| void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); } | void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); } | ||||||
| #endif | #endif | ||||||
|  | void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) { | ||||||
|  |   location &= 0x7;  // we only have 8 locations 0-7 | ||||||
|  |   this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3)); | ||||||
|  |   for (int i = 0; i < 8; i++) { | ||||||
|  |     this->send(charmap[i], true); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace lcd_base | }  // namespace lcd_base | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ | |||||||
| #include "esphome/components/time/real_time_clock.h" | #include "esphome/components/time/real_time_clock.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include <map> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace lcd_base { | namespace lcd_base { | ||||||
|  |  | ||||||
| @@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent { | |||||||
|     this->rows_ = rows; |     this->rows_ = rows; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   void set_user_defined_char(uint8_t pos, const std::vector<uint8_t> &data) { this->user_defined_chars_[pos] = data; } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void update() override; |   void update() override; | ||||||
| @@ -47,6 +51,9 @@ class LCDDisplay : public PollingComponent { | |||||||
|   void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); |   void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0))); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   /// Load custom char to given location | ||||||
|  |   void loadchar(uint8_t location, uint8_t charmap[]); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   virtual bool is_four_bit_mode() = 0; |   virtual bool is_four_bit_mode() = 0; | ||||||
|   virtual void write_n_bits(uint8_t value, uint8_t n) = 0; |   virtual void write_n_bits(uint8_t value, uint8_t n) = 0; | ||||||
| @@ -58,6 +65,7 @@ class LCDDisplay : public PollingComponent { | |||||||
|   uint8_t columns_; |   uint8_t columns_; | ||||||
|   uint8_t rows_; |   uint8_t rows_; | ||||||
|   uint8_t *buffer_{nullptr}; |   uint8_t *buffer_{nullptr}; | ||||||
|  |   std::map<uint8_t, std::vector<uint8_t> > user_defined_chars_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace lcd_base | }  // namespace lcd_base | ||||||
|   | |||||||
| @@ -203,15 +203,6 @@ async def to_code(config): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def maybe_simple_message(schema): |  | ||||||
|     def validator(value): |  | ||||||
|         if isinstance(value, dict): |  | ||||||
|             return cv.Schema(schema)(value) |  | ||||||
|         return cv.Schema(schema)({CONF_FORMAT: value}) |  | ||||||
|  |  | ||||||
|     return validator |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_printf(value): | def validate_printf(value): | ||||||
|     # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python |     # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python | ||||||
|     cfmt = r""" |     cfmt = r""" | ||||||
| @@ -234,7 +225,7 @@ def validate_printf(value): | |||||||
|  |  | ||||||
| CONF_LOGGER_LOG = "logger.log" | CONF_LOGGER_LOG = "logger.log" | ||||||
| LOGGER_LOG_ACTION_SCHEMA = cv.All( | LOGGER_LOG_ACTION_SCHEMA = cv.All( | ||||||
|     maybe_simple_message( |     cv.maybe_simple_value( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_FORMAT): cv.string, |             cv.Required(CONF_FORMAT): cv.string, | ||||||
|             cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), |             cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_), | ||||||
| @@ -242,9 +233,10 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All( | |||||||
|                 *LOG_LEVEL_TO_ESP_LOG, upper=True |                 *LOG_LEVEL_TO_ESP_LOG, upper=True | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_TAG, default="main"): cv.string, |             cv.Optional(CONF_TAG, default="main"): cv.string, | ||||||
|         } |         }, | ||||||
|     ), |         validate_printf, | ||||||
|     validate_printf, |         key=CONF_FORMAT, | ||||||
|  |     ) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ void MCP3204::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| float MCP3204::read_data(uint8_t pin) { | float MCP3204::read_data(uint8_t pin) { | ||||||
|   uint8_t adc_primary_config = 0b00000110 & 0b00000111; |   uint8_t adc_primary_config = 0b00000110 | (pin >> 2); | ||||||
|   uint8_t adc_secondary_config = pin << 6; |   uint8_t adc_secondary_config = pin << 6; | ||||||
|   this->enable(); |   this->enable(); | ||||||
|   this->transfer_byte(adc_primary_config); |   this->transfer_byte(adc_primary_config); | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | |||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(MCP3204Sensor), |         cv.GenerateID(): cv.declare_id(MCP3204Sensor), | ||||||
|         cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), |         cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), | ||||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=3), |         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), | ||||||
|     } |     } | ||||||
| ).extend(cv.polling_component_schema("60s")) | ).extend(cv.polling_component_schema("60s")) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -71,9 +71,9 @@ SENSOR_VALUE_TYPE = { | |||||||
|     "S_DWORD": SensorValueType.S_DWORD, |     "S_DWORD": SensorValueType.S_DWORD, | ||||||
|     "S_DWORD_R": SensorValueType.S_DWORD_R, |     "S_DWORD_R": SensorValueType.S_DWORD_R, | ||||||
|     "U_QWORD": SensorValueType.U_QWORD, |     "U_QWORD": SensorValueType.U_QWORD, | ||||||
|     "U_QWORDU_R": SensorValueType.U_QWORD_R, |     "U_QWORD_R": SensorValueType.U_QWORD_R, | ||||||
|     "S_QWORD": SensorValueType.S_QWORD, |     "S_QWORD": SensorValueType.S_QWORD, | ||||||
|     "U_QWORD_R": SensorValueType.S_QWORD_R, |     "S_QWORD_R": SensorValueType.S_QWORD_R, | ||||||
|     "FP32": SensorValueType.FP32, |     "FP32": SensorValueType.FP32, | ||||||
|     "FP32_R": SensorValueType.FP32_R, |     "FP32_R": SensorValueType.FP32_R, | ||||||
| } | } | ||||||
| @@ -87,9 +87,9 @@ TYPE_REGISTER_MAP = { | |||||||
|     "S_DWORD": 2, |     "S_DWORD": 2, | ||||||
|     "S_DWORD_R": 2, |     "S_DWORD_R": 2, | ||||||
|     "U_QWORD": 4, |     "U_QWORD": 4, | ||||||
|     "U_QWORDU_R": 4, |  | ||||||
|     "S_QWORD": 4, |  | ||||||
|     "U_QWORD_R": 4, |     "U_QWORD_R": 4, | ||||||
|  |     "S_QWORD": 4, | ||||||
|  |     "S_QWORD_R": 4, | ||||||
|     "FP32": 2, |     "FP32": 2, | ||||||
|     "FP32_R": 2, |     "FP32_R": 2, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -455,6 +455,28 @@ ModbusCommandItem ModbusCommandItem::create_custom_command( | |||||||
|   return cmd; |   return cmd; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ModbusCommandItem ModbusCommandItem::create_custom_command( | ||||||
|  |     ModbusController *modbusdevice, const std::vector<uint16_t> &values, | ||||||
|  |     std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> | ||||||
|  |         &&handler) { | ||||||
|  |   ModbusCommandItem cmd = {}; | ||||||
|  |   cmd.modbusdevice = modbusdevice; | ||||||
|  |   cmd.function_code = ModbusFunctionCode::CUSTOM; | ||||||
|  |   if (handler == nullptr) { | ||||||
|  |     cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { | ||||||
|  |       ESP_LOGI(TAG, "Custom Command sent"); | ||||||
|  |     }; | ||||||
|  |   } else { | ||||||
|  |     cmd.on_data_func = handler; | ||||||
|  |   } | ||||||
|  |   for (auto v : values) { | ||||||
|  |     cmd.payload.push_back((v >> 8) & 0xFF); | ||||||
|  |     cmd.payload.push_back(v & 0xFF); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return cmd; | ||||||
|  | } | ||||||
|  |  | ||||||
| bool ModbusCommandItem::send() { | bool ModbusCommandItem::send() { | ||||||
|   if (this->function_code != ModbusFunctionCode::CUSTOM) { |   if (this->function_code != ModbusFunctionCode::CUSTOM) { | ||||||
|     modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(), |     modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(), | ||||||
|   | |||||||
| @@ -2,12 +2,12 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
| #include "esphome/core/automation.h" |  | ||||||
| #include "esphome/components/modbus/modbus.h" | #include "esphome/components/modbus/modbus.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
| #include <list> | #include <list> | ||||||
| #include <set> |  | ||||||
| #include <queue> | #include <queue> | ||||||
|  | #include <set> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -374,8 +374,8 @@ class ModbusCommandItem { | |||||||
|                                                        const std::vector<bool> &values); |                                                        const std::vector<bool> &values); | ||||||
|   /** Create custom modbus command |   /** Create custom modbus command | ||||||
|    * @param modbusdevice pointer to the device to execute the command |    * @param modbusdevice pointer to the device to execute the command | ||||||
|    * @param values byte vector of data to be sent to the device. The compplete payload must be provided with the |    * @param values byte vector of data to be sent to the device. The complete payload must be provided with the | ||||||
|    * exception of the crc codess |    * exception of the crc codes | ||||||
|    * @param handler function called when the response is received. Default is just logging a response |    * @param handler function called when the response is received. Default is just logging a response | ||||||
|    * @return ModbusCommandItem with the prepared command |    * @return ModbusCommandItem with the prepared command | ||||||
|    */ |    */ | ||||||
| @@ -383,6 +383,18 @@ class ModbusCommandItem { | |||||||
|       ModbusController *modbusdevice, const std::vector<uint8_t> &values, |       ModbusController *modbusdevice, const std::vector<uint8_t> &values, | ||||||
|       std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> |       std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> | ||||||
|           &&handler = nullptr); |           &&handler = nullptr); | ||||||
|  |  | ||||||
|  |   /** Create custom modbus command | ||||||
|  |    * @param modbusdevice pointer to the device to execute the command | ||||||
|  |    * @param values word vector of data to be sent to the device. The complete payload must be provided with the | ||||||
|  |    * exception of the crc codes | ||||||
|  |    * @param handler function called when the response is received. Default is just logging a response | ||||||
|  |    * @return ModbusCommandItem with the prepared command | ||||||
|  |    */ | ||||||
|  |   static ModbusCommandItem create_custom_command( | ||||||
|  |       ModbusController *modbusdevice, const std::vector<uint16_t> &values, | ||||||
|  |       std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> | ||||||
|  |           &&handler = nullptr); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** Modbus controller class. | /** Modbus controller class. | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void ModbusNumber::control(float value) { | void ModbusNumber::control(float value) { | ||||||
|  |   ModbusCommandItem write_cmd; | ||||||
|   std::vector<uint16_t> data; |   std::vector<uint16_t> data; | ||||||
|   float write_value = value; |   float write_value = value; | ||||||
|   // Is there are lambda configured? |   // Is there are lambda configured? | ||||||
| @@ -45,33 +46,39 @@ void ModbusNumber::control(float value) { | |||||||
|     write_value = multiply_by_ * write_value; |     write_value = multiply_by_ * write_value; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // lambda didn't set payload |   if (!data.empty()) { | ||||||
|   if (data.empty()) { |     ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str()); | ||||||
|     data = float_to_payload(write_value, this->sensor_value_type); |     write_cmd = ModbusCommandItem::create_custom_command( | ||||||
|   } |         this->parent_, data, | ||||||
|  |         [this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { | ||||||
|   ESP_LOGD(TAG, |           this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data); | ||||||
|            "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", |         }); | ||||||
|            this->get_name().c_str(), this->start_address, this->register_count, value, write_value); |  | ||||||
|  |  | ||||||
|   // Create and send the write command |  | ||||||
|   ModbusCommandItem write_cmd; |  | ||||||
|   if (this->register_count == 1 && !this->use_write_multiple_) { |  | ||||||
|     // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 |  | ||||||
|     write_cmd = |  | ||||||
|         ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); |  | ||||||
|   } else { |   } else { | ||||||
|     write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, |     data = float_to_payload(write_value, this->sensor_value_type); | ||||||
|                                                                  this->register_count, data); |  | ||||||
|  |     ESP_LOGD(TAG, | ||||||
|  |              "Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)", | ||||||
|  |              this->get_name().c_str(), this->start_address, this->register_count, value, write_value); | ||||||
|  |  | ||||||
|  |     // Create and send the write command | ||||||
|  |     if (this->register_count == 1 && !this->use_write_multiple_) { | ||||||
|  |       // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 | ||||||
|  |       write_cmd = | ||||||
|  |           ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); | ||||||
|  |     } else { | ||||||
|  |       write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, | ||||||
|  |                                                                    this->register_count, data); | ||||||
|  |     } | ||||||
|  |     // publish new value | ||||||
|  |     write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, | ||||||
|  |                                                       const std::vector<uint8_t> &data) { | ||||||
|  |       // gets called when the write command is ack'd from the device | ||||||
|  |       parent_->on_write_register_response(write_cmd.register_type, start_address, data); | ||||||
|  |       this->publish_state(value); | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
|   // publish new value |  | ||||||
|   write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address, |  | ||||||
|                                                     const std::vector<uint8_t> &data) { |  | ||||||
|     // gets called when the write command is ack'd from the device |  | ||||||
|     parent_->on_write_register_response(write_cmd.register_type, start_address, data); |  | ||||||
|     this->publish_state(value); |  | ||||||
|   }; |  | ||||||
|   parent_->queue_command(write_cmd); |   parent_->queue_command(write_cmd); | ||||||
|  |   this->publish_state(value); | ||||||
| } | } | ||||||
| void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } | void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import select | from esphome.components import select | ||||||
| from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC | from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC | ||||||
| from esphome.jsonschema import jschema_composite |  | ||||||
|  |  | ||||||
| from .. import ( | from .. import ( | ||||||
|     SENSOR_VALUE_TYPE, |     SENSOR_VALUE_TYPE, | ||||||
| @@ -30,7 +29,6 @@ ModbusSelect = modbus_controller_ns.class_( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @jschema_composite |  | ||||||
| def ensure_option_map(): | def ensure_option_map(): | ||||||
|     def validator(value): |     def validator(value): | ||||||
|         cv.check_not_templatable(value) |         cv.check_not_templatable(value) | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ from esphome.const import ( | |||||||
|     CONF_AVAILABILITY, |     CONF_AVAILABILITY, | ||||||
|     CONF_BIRTH_MESSAGE, |     CONF_BIRTH_MESSAGE, | ||||||
|     CONF_BROKER, |     CONF_BROKER, | ||||||
|  |     CONF_CERTIFICATE_AUTHORITY, | ||||||
|     CONF_CLIENT_ID, |     CONF_CLIENT_ID, | ||||||
|     CONF_COMMAND_TOPIC, |     CONF_COMMAND_TOPIC, | ||||||
|     CONF_COMMAND_RETAIN, |     CONF_COMMAND_RETAIN, | ||||||
| @@ -42,9 +43,14 @@ from esphome.const import ( | |||||||
|     CONF_WILL_MESSAGE, |     CONF_WILL_MESSAGE, | ||||||
| ) | ) | ||||||
| from esphome.core import coroutine_with_priority, CORE | from esphome.core import coroutine_with_priority, CORE | ||||||
|  | from esphome.components.esp32 import add_idf_sdkconfig_option | ||||||
|  |  | ||||||
| DEPENDENCIES = ["network"] | DEPENDENCIES = ["network"] | ||||||
| AUTO_LOAD = ["json", "async_tcp"] |  | ||||||
|  | AUTO_LOAD = ["json"] | ||||||
|  |  | ||||||
|  | CONF_IDF_SEND_ASYNC = "idf_send_async" | ||||||
|  | CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_message_just_topic(value): | def validate_message_just_topic(value): | ||||||
| @@ -163,6 +169,15 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_USERNAME, default=""): cv.string, |             cv.Optional(CONF_USERNAME, default=""): cv.string, | ||||||
|             cv.Optional(CONF_PASSWORD, default=""): cv.string, |             cv.Optional(CONF_PASSWORD, default=""): cv.string, | ||||||
|             cv.Optional(CONF_CLIENT_ID): cv.string, |             cv.Optional(CONF_CLIENT_ID): cv.string, | ||||||
|  |             cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All( | ||||||
|  |                 cv.boolean, cv.only_with_esp_idf | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All( | ||||||
|  |                 cv.string, cv.only_with_esp_idf | ||||||
|  |             ), | ||||||
|  |             cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All( | ||||||
|  |                 cv.boolean, cv.only_with_esp_idf | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_DISCOVERY, default=True): cv.Any( |             cv.Optional(CONF_DISCOVERY, default=True): cv.Any( | ||||||
|                 cv.boolean, cv.one_of("CLEAN", upper=True) |                 cv.boolean, cv.one_of("CLEAN", upper=True) | ||||||
|             ), |             ), | ||||||
| @@ -217,7 +232,6 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     validate_config, |     validate_config, | ||||||
|     cv.only_with_arduino, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -238,9 +252,11 @@ def exp_mqtt_message(config): | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |     # Add required libraries for arduino | ||||||
|  |     if CORE.using_arduino: | ||||||
|  |         # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json | ||||||
|  |         cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") | ||||||
|  |  | ||||||
|     # https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json |  | ||||||
|     cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6") |  | ||||||
|     cg.add_define("USE_MQTT") |     cg.add_define("USE_MQTT") | ||||||
|     cg.add_global(mqtt_ns.using) |     cg.add_global(mqtt_ns.using) | ||||||
|  |  | ||||||
| @@ -321,6 +337,19 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) |     cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) | ||||||
|  |  | ||||||
|  |     # esp-idf only | ||||||
|  |     if CONF_CERTIFICATE_AUTHORITY in config: | ||||||
|  |         cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY])) | ||||||
|  |         cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK])) | ||||||
|  |  | ||||||
|  |         # prevent error -0x428e | ||||||
|  |         # See https://github.com/espressif/esp-idf/issues/139 | ||||||
|  |         add_idf_sdkconfig_option("CONFIG_MBEDTLS_HARDWARE_MPI", False) | ||||||
|  |  | ||||||
|  |     if CONF_IDF_SEND_ASYNC in config and config[CONF_IDF_SEND_ASYNC]: | ||||||
|  |         cg.add_define("USE_MQTT_IDF_ENQUEUE") | ||||||
|  |     # end esp-idf | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_MESSAGE, []): |     for conf in config.get(CONF_ON_MESSAGE, []): | ||||||
|         trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC]) |         trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC]) | ||||||
|         cg.add(trig.set_qos(conf[CONF_QOS])) |         cg.add(trig.set_qos(conf[CONF_QOS])) | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								esphome/components/mqtt/mqtt_backend.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/mqtt/mqtt_backend.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include <map> | ||||||
|  | #include "esphome/components/network/ip_address.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mqtt { | ||||||
|  |  | ||||||
|  | enum class MQTTClientDisconnectReason : int8_t { | ||||||
|  |   TCP_DISCONNECTED = 0, | ||||||
|  |   MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1, | ||||||
|  |   MQTT_IDENTIFIER_REJECTED = 2, | ||||||
|  |   MQTT_SERVER_UNAVAILABLE = 3, | ||||||
|  |   MQTT_MALFORMED_CREDENTIALS = 4, | ||||||
|  |   MQTT_NOT_AUTHORIZED = 5, | ||||||
|  |   ESP8266_NOT_ENOUGH_SPACE = 6, | ||||||
|  |   TLS_BAD_FINGERPRINT = 7 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// internal struct for MQTT messages. | ||||||
|  | struct MQTTMessage { | ||||||
|  |   std::string topic; | ||||||
|  |   std::string payload; | ||||||
|  |   uint8_t qos;  ///< QoS. Only for last will testaments. | ||||||
|  |   bool retain; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MQTTBackend { | ||||||
|  |  public: | ||||||
|  |   using on_connect_callback_t = void(bool session_present); | ||||||
|  |   using on_disconnect_callback_t = void(MQTTClientDisconnectReason reason); | ||||||
|  |   using on_subscribe_callback_t = void(uint16_t packet_id, uint8_t qos); | ||||||
|  |   using on_unsubscribe_callback_t = void(uint16_t packet_id); | ||||||
|  |   using on_message_callback_t = void(const char *topic, const char *payload, size_t len, size_t index, size_t total); | ||||||
|  |   using on_publish_user_callback_t = void(uint16_t packet_id); | ||||||
|  |  | ||||||
|  |   virtual void set_keep_alive(uint16_t keep_alive) = 0; | ||||||
|  |   virtual void set_client_id(const char *client_id) = 0; | ||||||
|  |   virtual void set_clean_session(bool clean_session) = 0; | ||||||
|  |   virtual void set_credentials(const char *username, const char *password) = 0; | ||||||
|  |   virtual void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) = 0; | ||||||
|  |   virtual void set_server(network::IPAddress ip, uint16_t port) = 0; | ||||||
|  |   virtual void set_server(const char *host, uint16_t port) = 0; | ||||||
|  |   virtual void set_on_connect(std::function<on_connect_callback_t> &&callback) = 0; | ||||||
|  |   virtual void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) = 0; | ||||||
|  |   virtual void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) = 0; | ||||||
|  |   virtual void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) = 0; | ||||||
|  |   virtual void set_on_message(std::function<on_message_callback_t> &&callback) = 0; | ||||||
|  |   virtual void set_on_publish(std::function<on_publish_user_callback_t> &&callback) = 0; | ||||||
|  |   virtual bool connected() const = 0; | ||||||
|  |   virtual void connect() = 0; | ||||||
|  |   virtual void disconnect() = 0; | ||||||
|  |   virtual bool subscribe(const char *topic, uint8_t qos) = 0; | ||||||
|  |   virtual bool unsubscribe(const char *topic) = 0; | ||||||
|  |   virtual bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) = 0; | ||||||
|  |  | ||||||
|  |   virtual bool publish(const MQTTMessage &message) { | ||||||
|  |     return publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos, | ||||||
|  |                    message.retain); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // called from MQTTClient::loop() | ||||||
|  |   virtual void loop() {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace mqtt | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										74
									
								
								esphome/components/mqtt/mqtt_backend_arduino.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/mqtt/mqtt_backend_arduino.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ARDUINO | ||||||
|  |  | ||||||
|  | #include "mqtt_backend.h" | ||||||
|  | #include <AsyncMqttClient.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mqtt { | ||||||
|  |  | ||||||
|  | class MQTTBackendArduino final : public MQTTBackend { | ||||||
|  |  public: | ||||||
|  |   void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); } | ||||||
|  |   void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); } | ||||||
|  |   void set_clean_session(bool clean_session) final { mqtt_client_.setCleanSession(clean_session); } | ||||||
|  |   void set_credentials(const char *username, const char *password) final { | ||||||
|  |     mqtt_client_.setCredentials(username, password); | ||||||
|  |   } | ||||||
|  |   void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { | ||||||
|  |     mqtt_client_.setWill(topic, qos, retain, payload); | ||||||
|  |   } | ||||||
|  |   void set_server(network::IPAddress ip, uint16_t port) final { | ||||||
|  |     mqtt_client_.setServer(IPAddress(static_cast<uint32_t>(ip)), port); | ||||||
|  |   } | ||||||
|  |   void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } | ||||||
|  | #if ASYNC_TCP_SSL_ENABLED | ||||||
|  |   void set_secure(bool secure) { mqtt_client.setSecure(secure); } | ||||||
|  |   void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   void set_on_connect(std::function<on_connect_callback_t> &&callback) final { | ||||||
|  |     this->mqtt_client_.onConnect(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final { | ||||||
|  |     auto async_callback = [callback](AsyncMqttClientDisconnectReason reason) { | ||||||
|  |       // int based enum so casting isn't a problem | ||||||
|  |       callback(static_cast<MQTTClientDisconnectReason>(reason)); | ||||||
|  |     }; | ||||||
|  |     this->mqtt_client_.onDisconnect(std::move(async_callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final { | ||||||
|  |     this->mqtt_client_.onSubscribe(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final { | ||||||
|  |     this->mqtt_client_.onUnsubscribe(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_message(std::function<on_message_callback_t> &&callback) final { | ||||||
|  |     auto async_callback = [callback](const char *topic, const char *payload, | ||||||
|  |                                      AsyncMqttClientMessageProperties async_properties, size_t len, size_t index, | ||||||
|  |                                      size_t total) { callback(topic, payload, len, index, total); }; | ||||||
|  |     mqtt_client_.onMessage(std::move(async_callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final { | ||||||
|  |     this->mqtt_client_.onPublish(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool connected() const final { return mqtt_client_.connected(); } | ||||||
|  |   void connect() final { mqtt_client_.connect(); } | ||||||
|  |   void disconnect() final { mqtt_client_.disconnect(true); } | ||||||
|  |   bool subscribe(const char *topic, uint8_t qos) final { return mqtt_client_.subscribe(topic, qos) != 0; } | ||||||
|  |   bool unsubscribe(const char *topic) final { return mqtt_client_.unsubscribe(topic) != 0; } | ||||||
|  |   bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { | ||||||
|  |     return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0; | ||||||
|  |   } | ||||||
|  |   using MQTTBackend::publish; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   AsyncMqttClient mqtt_client_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace mqtt | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // defined(USE_ARDUINO) | ||||||
							
								
								
									
										149
									
								
								esphome/components/mqtt/mqtt_backend_idf.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								esphome/components/mqtt/mqtt_backend_idf.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | |||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include "mqtt_backend_idf.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mqtt { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "mqtt.idf"; | ||||||
|  |  | ||||||
|  | bool MQTTBackendIDF::initialize_() { | ||||||
|  |   mqtt_cfg_.user_context = (void *) this; | ||||||
|  |   mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE; | ||||||
|  |  | ||||||
|  |   mqtt_cfg_.host = this->host_.c_str(); | ||||||
|  |   mqtt_cfg_.port = this->port_; | ||||||
|  |   mqtt_cfg_.keepalive = this->keep_alive_; | ||||||
|  |   mqtt_cfg_.disable_clean_session = !this->clean_session_; | ||||||
|  |  | ||||||
|  |   if (!this->username_.empty()) { | ||||||
|  |     mqtt_cfg_.username = this->username_.c_str(); | ||||||
|  |     if (!this->password_.empty()) { | ||||||
|  |       mqtt_cfg_.password = this->password_.c_str(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->lwt_topic_.empty()) { | ||||||
|  |     mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str(); | ||||||
|  |     this->mqtt_cfg_.lwt_qos = this->lwt_qos_; | ||||||
|  |     this->mqtt_cfg_.lwt_retain = this->lwt_retain_; | ||||||
|  |  | ||||||
|  |     if (!this->lwt_message_.empty()) { | ||||||
|  |       mqtt_cfg_.lwt_msg = this->lwt_message_.c_str(); | ||||||
|  |       mqtt_cfg_.lwt_msg_len = this->lwt_message_.size(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->client_id_.empty()) { | ||||||
|  |     mqtt_cfg_.client_id = this->client_id_.c_str(); | ||||||
|  |   } | ||||||
|  |   if (ca_certificate_.has_value()) { | ||||||
|  |     mqtt_cfg_.cert_pem = ca_certificate_.value().c_str(); | ||||||
|  |     mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_; | ||||||
|  |     mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL; | ||||||
|  |   } else { | ||||||
|  |     mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; | ||||||
|  |   } | ||||||
|  |   auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_); | ||||||
|  |   if (mqtt_client) { | ||||||
|  |     handler_.reset(mqtt_client); | ||||||
|  |     is_initalized_ = true; | ||||||
|  |     esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, this); | ||||||
|  |     return true; | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Failed to initialize IDF-MQTT"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MQTTBackendIDF::loop() { | ||||||
|  |   // process new events | ||||||
|  |   // handle only 1 message per loop iteration | ||||||
|  |   if (!mqtt_events_.empty()) { | ||||||
|  |     auto &event = mqtt_events_.front(); | ||||||
|  |     mqtt_event_handler_(event); | ||||||
|  |     mqtt_events_.pop(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MQTTBackendIDF::mqtt_event_handler_(const esp_mqtt_event_t &event) { | ||||||
|  |   ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id); | ||||||
|  |   switch (event.event_id) { | ||||||
|  |     case MQTT_EVENT_BEFORE_CONNECT: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_BEFORE_CONNECT"); | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case MQTT_EVENT_CONNECTED: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED"); | ||||||
|  |       // TODO session present check | ||||||
|  |       this->is_connected_ = true; | ||||||
|  |       this->on_connect_.call(!mqtt_cfg_.disable_clean_session); | ||||||
|  |       break; | ||||||
|  |     case MQTT_EVENT_DISCONNECTED: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED"); | ||||||
|  |       // TODO is there a way to get the disconnect reason? | ||||||
|  |       this->is_connected_ = false; | ||||||
|  |       this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED); | ||||||
|  |       break; | ||||||
|  |  | ||||||
|  |     case MQTT_EVENT_SUBSCRIBED: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event.msg_id); | ||||||
|  |       // hardcode QoS to 0. QoS is not used in this context but required to mirror the AsyncMqtt interface | ||||||
|  |       this->on_subscribe_.call((int) event.msg_id, 0); | ||||||
|  |       break; | ||||||
|  |     case MQTT_EVENT_UNSUBSCRIBED: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event.msg_id); | ||||||
|  |       this->on_unsubscribe_.call((int) event.msg_id); | ||||||
|  |       break; | ||||||
|  |     case MQTT_EVENT_PUBLISHED: | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event.msg_id); | ||||||
|  |       this->on_publish_.call((int) event.msg_id); | ||||||
|  |       break; | ||||||
|  |     case MQTT_EVENT_DATA: { | ||||||
|  |       static std::string topic; | ||||||
|  |       if (event.topic) { | ||||||
|  |         // not 0 terminated - create a string from it | ||||||
|  |         topic = std::string(event.topic, event.topic_len); | ||||||
|  |       } | ||||||
|  |       ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str()); | ||||||
|  |       auto data_len = event.data_len; | ||||||
|  |       if (data_len == 0) | ||||||
|  |         data_len = strlen(event.data); | ||||||
|  |       this->on_message_.call(event.topic ? const_cast<char *>(topic.c_str()) : nullptr, event.data, data_len, | ||||||
|  |                              event.current_data_offset, event.total_data_len); | ||||||
|  |     } break; | ||||||
|  |     case MQTT_EVENT_ERROR: | ||||||
|  |       ESP_LOGE(TAG, "MQTT_EVENT_ERROR"); | ||||||
|  |       if (event.error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) { | ||||||
|  |         ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle->esp_tls_last_esp_err); | ||||||
|  |         ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle->esp_tls_stack_err); | ||||||
|  |         ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle->esp_transport_sock_errno, | ||||||
|  |                  strerror(event.error_handle->esp_transport_sock_errno)); | ||||||
|  |       } else if (event.error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) { | ||||||
|  |         ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle->connect_return_code); | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGE(TAG, "Unknown error type: 0x%x", event.error_handle->error_type); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       ESP_LOGV(TAG, "Other event id:%d", event.event_id); | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// static - Dispatch event to instance method | ||||||
|  | void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { | ||||||
|  |   MQTTBackendIDF *instance = static_cast<MQTTBackendIDF *>(handler_args); | ||||||
|  |   // queue event to decouple processing | ||||||
|  |   if (instance) { | ||||||
|  |     auto event = *static_cast<esp_mqtt_event_t *>(event_data); | ||||||
|  |     instance->mqtt_events_.push(event); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace mqtt | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif  // USE_ESP_IDF | ||||||
							
								
								
									
										143
									
								
								esphome/components/mqtt/mqtt_backend_idf.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								esphome/components/mqtt/mqtt_backend_idf.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include <queue> | ||||||
|  | #include <mqtt_client.h> | ||||||
|  | #include "esphome/components/network/ip_address.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "mqtt_backend.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace mqtt { | ||||||
|  |  | ||||||
|  | class MQTTBackendIDF final : public MQTTBackend { | ||||||
|  |  public: | ||||||
|  |   static const size_t MQTT_BUFFER_SIZE = 4096; | ||||||
|  |  | ||||||
|  |   void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; } | ||||||
|  |   void set_client_id(const char *client_id) final { this->client_id_ = client_id; } | ||||||
|  |   void set_clean_session(bool clean_session) final { this->clean_session_ = clean_session; } | ||||||
|  |  | ||||||
|  |   void set_credentials(const char *username, const char *password) final { | ||||||
|  |     if (username) | ||||||
|  |       this->username_ = username; | ||||||
|  |     if (password) | ||||||
|  |       this->password_ = password; | ||||||
|  |   } | ||||||
|  |   void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { | ||||||
|  |     if (topic) | ||||||
|  |       this->lwt_topic_ = topic; | ||||||
|  |     this->lwt_qos_ = qos; | ||||||
|  |     if (payload) | ||||||
|  |       this->lwt_message_ = payload; | ||||||
|  |     this->lwt_retain_ = retain; | ||||||
|  |   } | ||||||
|  |   void set_server(network::IPAddress ip, uint16_t port) final { | ||||||
|  |     this->host_ = ip.str(); | ||||||
|  |     this->port_ = port; | ||||||
|  |   } | ||||||
|  |   void set_server(const char *host, uint16_t port) final { | ||||||
|  |     this->host_ = host; | ||||||
|  |     this->port_ = port; | ||||||
|  |   } | ||||||
|  |   void set_on_connect(std::function<on_connect_callback_t> &&callback) final { | ||||||
|  |     this->on_connect_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final { | ||||||
|  |     this->on_disconnect_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final { | ||||||
|  |     this->on_subscribe_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final { | ||||||
|  |     this->on_unsubscribe_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_message(std::function<on_message_callback_t> &&callback) final { | ||||||
|  |     this->on_message_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final { | ||||||
|  |     this->on_publish_.add(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   bool connected() const final { return this->is_connected_; } | ||||||
|  |  | ||||||
|  |   void connect() final { | ||||||
|  |     if (!is_initalized_) { | ||||||
|  |       if (initialize_()) { | ||||||
|  |         esp_mqtt_client_start(handler_.get()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   void disconnect() final { | ||||||
|  |     if (is_initalized_) | ||||||
|  |       esp_mqtt_client_disconnect(handler_.get()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool subscribe(const char *topic, uint8_t qos) final { | ||||||
|  |     return esp_mqtt_client_subscribe(handler_.get(), topic, qos) != -1; | ||||||
|  |   } | ||||||
|  |   bool unsubscribe(const char *topic) final { return esp_mqtt_client_unsubscribe(handler_.get(), topic) != -1; } | ||||||
|  |  | ||||||
|  |   bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final { | ||||||
|  | #if defined(USE_MQTT_IDF_ENQUEUE) | ||||||
|  |     // use the non-blocking version | ||||||
|  |     // it can delay sending a couple of seconds but won't block | ||||||
|  |     return esp_mqtt_client_enqueue(handler_.get(), topic, payload, length, qos, retain, true) != -1; | ||||||
|  | #else | ||||||
|  |     // might block for several seconds, either due to network timeout (10s) | ||||||
|  |     // or if publishing payloads longer than internal buffer (due to message fragmentation) | ||||||
|  |     return esp_mqtt_client_publish(handler_.get(), topic, payload, length, qos, retain) != -1; | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  |   using MQTTBackend::publish; | ||||||
|  |  | ||||||
|  |   void loop() final; | ||||||
|  |  | ||||||
|  |   void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; } | ||||||
|  |   void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool initialize_(); | ||||||
|  |   void mqtt_event_handler_(const esp_mqtt_event_t &event); | ||||||
|  |   static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data); | ||||||
|  |  | ||||||
|  |   struct MqttClientDeleter { | ||||||
|  |     void operator()(esp_mqtt_client *client_handler) { esp_mqtt_client_destroy(client_handler); } | ||||||
|  |   }; | ||||||
|  |   using ClientHandler_ = std::unique_ptr<esp_mqtt_client, MqttClientDeleter>; | ||||||
|  |   ClientHandler_ handler_; | ||||||
|  |  | ||||||
|  |   bool is_connected_{false}; | ||||||
|  |   bool is_initalized_{false}; | ||||||
|  |  | ||||||
|  |   esp_mqtt_client_config_t mqtt_cfg_{}; | ||||||
|  |  | ||||||
|  |   std::string host_; | ||||||
|  |   uint16_t port_; | ||||||
|  |   std::string username_; | ||||||
|  |   std::string password_; | ||||||
|  |   std::string lwt_topic_; | ||||||
|  |   std::string lwt_message_; | ||||||
|  |   uint8_t lwt_qos_; | ||||||
|  |   bool lwt_retain_; | ||||||
|  |   std::string client_id_; | ||||||
|  |   uint16_t keep_alive_; | ||||||
|  |   bool clean_session_; | ||||||
|  |   optional<std::string> ca_certificate_; | ||||||
|  |   bool skip_cert_cn_check_{false}; | ||||||
|  |  | ||||||
|  |   // callbacks | ||||||
|  |   CallbackManager<on_connect_callback_t> on_connect_; | ||||||
|  |   CallbackManager<on_disconnect_callback_t> on_disconnect_; | ||||||
|  |   CallbackManager<on_subscribe_callback_t> on_subscribe_; | ||||||
|  |   CallbackManager<on_unsubscribe_callback_t> on_unsubscribe_; | ||||||
|  |   CallbackManager<on_message_callback_t> on_message_; | ||||||
|  |   CallbackManager<on_publish_user_callback_t> on_publish_; | ||||||
|  |   std::queue<esp_mqtt_event_t> mqtt_events_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace mqtt | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -27,21 +27,21 @@ MQTTClientComponent::MQTTClientComponent() { | |||||||
| // Connection | // Connection | ||||||
| void MQTTClientComponent::setup() { | void MQTTClientComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up MQTT..."); |   ESP_LOGCONFIG(TAG, "Setting up MQTT..."); | ||||||
|   this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties, |   this->mqtt_backend_.set_on_message( | ||||||
|                                       size_t len, size_t index, size_t total) { |       [this](const char *topic, const char *payload, size_t len, size_t index, size_t total) { | ||||||
|     if (index == 0) |         if (index == 0) | ||||||
|       this->payload_buffer_.reserve(total); |           this->payload_buffer_.reserve(total); | ||||||
|  |  | ||||||
|     // append new payload, may contain incomplete MQTT message |         // append new payload, may contain incomplete MQTT message | ||||||
|     this->payload_buffer_.append(payload, len); |         this->payload_buffer_.append(payload, len); | ||||||
|  |  | ||||||
|     // MQTT fully received |         // MQTT fully received | ||||||
|     if (len + index == total) { |         if (len + index == total) { | ||||||
|       this->on_message(topic, this->payload_buffer_); |           this->on_message(topic, this->payload_buffer_); | ||||||
|       this->payload_buffer_.clear(); |           this->payload_buffer_.clear(); | ||||||
|     } |         } | ||||||
|   }); |       }); | ||||||
|   this->mqtt_client_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { |   this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) { | ||||||
|     this->state_ = MQTT_CLIENT_DISCONNECTED; |     this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||||
|     this->disconnect_reason_ = reason; |     this->disconnect_reason_ = reason; | ||||||
|   }); |   }); | ||||||
| @@ -49,8 +49,10 @@ void MQTTClientComponent::setup() { | |||||||
|   if (this->is_log_message_enabled() && logger::global_logger != nullptr) { |   if (this->is_log_message_enabled() && logger::global_logger != nullptr) { | ||||||
|     logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { |     logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { | ||||||
|       if (level <= this->log_level_ && this->is_connected()) { |       if (level <= this->log_level_ && this->is_connected()) { | ||||||
|         this->publish(this->log_message_.topic, message, strlen(message), this->log_message_.qos, |         this->publish({.topic = this->log_message_.topic, | ||||||
|                       this->log_message_.retain); |                        .payload = message, | ||||||
|  |                        .qos = this->log_message_.qos, | ||||||
|  |                        .retain = this->log_message_.retain}); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @@ -173,9 +175,9 @@ void MQTTClientComponent::start_connect_() { | |||||||
|  |  | ||||||
|   ESP_LOGI(TAG, "Connecting to MQTT..."); |   ESP_LOGI(TAG, "Connecting to MQTT..."); | ||||||
|   // Force disconnect first |   // Force disconnect first | ||||||
|   this->mqtt_client_.disconnect(true); |   this->mqtt_backend_.disconnect(); | ||||||
|  |  | ||||||
|   this->mqtt_client_.setClientId(this->credentials_.client_id.c_str()); |   this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str()); | ||||||
|   const char *username = nullptr; |   const char *username = nullptr; | ||||||
|   if (!this->credentials_.username.empty()) |   if (!this->credentials_.username.empty()) | ||||||
|     username = this->credentials_.username.c_str(); |     username = this->credentials_.username.c_str(); | ||||||
| @@ -183,24 +185,24 @@ void MQTTClientComponent::start_connect_() { | |||||||
|   if (!this->credentials_.password.empty()) |   if (!this->credentials_.password.empty()) | ||||||
|     password = this->credentials_.password.c_str(); |     password = this->credentials_.password.c_str(); | ||||||
|  |  | ||||||
|   this->mqtt_client_.setCredentials(username, password); |   this->mqtt_backend_.set_credentials(username, password); | ||||||
|  |  | ||||||
|   this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port); |   this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port); | ||||||
|   if (!this->last_will_.topic.empty()) { |   if (!this->last_will_.topic.empty()) { | ||||||
|     this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, |     this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain, | ||||||
|                                this->last_will_.payload.c_str(), this->last_will_.payload.length()); |                                  this->last_will_.payload.c_str()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->mqtt_client_.connect(); |   this->mqtt_backend_.connect(); | ||||||
|   this->state_ = MQTT_CLIENT_CONNECTING; |   this->state_ = MQTT_CLIENT_CONNECTING; | ||||||
|   this->connect_begin_ = millis(); |   this->connect_begin_ = millis(); | ||||||
| } | } | ||||||
| bool MQTTClientComponent::is_connected() { | bool MQTTClientComponent::is_connected() { | ||||||
|   return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_client_.connected(); |   return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_backend_.connected(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::check_connected() { | void MQTTClientComponent::check_connected() { | ||||||
|   if (!this->mqtt_client_.connected()) { |   if (!this->mqtt_backend_.connected()) { | ||||||
|     if (millis() - this->connect_begin_ > 60000) { |     if (millis() - this->connect_begin_ > 60000) { | ||||||
|       this->state_ = MQTT_CLIENT_DISCONNECTED; |       this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||||
|       this->start_dnslookup_(); |       this->start_dnslookup_(); | ||||||
| @@ -222,31 +224,34 @@ void MQTTClientComponent::check_connected() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::loop() { | void MQTTClientComponent::loop() { | ||||||
|  |   // Call the backend loop first | ||||||
|  |   mqtt_backend_.loop(); | ||||||
|  |  | ||||||
|   if (this->disconnect_reason_.has_value()) { |   if (this->disconnect_reason_.has_value()) { | ||||||
|     const LogString *reason_s; |     const LogString *reason_s; | ||||||
|     switch (*this->disconnect_reason_) { |     switch (*this->disconnect_reason_) { | ||||||
|       case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED: |       case MQTTClientDisconnectReason::TCP_DISCONNECTED: | ||||||
|         reason_s = LOG_STR("TCP disconnected"); |         reason_s = LOG_STR("TCP disconnected"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: |       case MQTTClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: | ||||||
|         reason_s = LOG_STR("Unacceptable Protocol Version"); |         reason_s = LOG_STR("Unacceptable Protocol Version"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: |       case MQTTClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: | ||||||
|         reason_s = LOG_STR("Identifier Rejected"); |         reason_s = LOG_STR("Identifier Rejected"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: |       case MQTTClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: | ||||||
|         reason_s = LOG_STR("Server Unavailable"); |         reason_s = LOG_STR("Server Unavailable"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: |       case MQTTClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: | ||||||
|         reason_s = LOG_STR("Malformed Credentials"); |         reason_s = LOG_STR("Malformed Credentials"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED: |       case MQTTClientDisconnectReason::MQTT_NOT_AUTHORIZED: | ||||||
|         reason_s = LOG_STR("Not Authorized"); |         reason_s = LOG_STR("Not Authorized"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: |       case MQTTClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: | ||||||
|         reason_s = LOG_STR("Not Enough Space"); |         reason_s = LOG_STR("Not Enough Space"); | ||||||
|         break; |         break; | ||||||
|       case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT: |       case MQTTClientDisconnectReason::TLS_BAD_FINGERPRINT: | ||||||
|         reason_s = LOG_STR("TLS Bad Fingerprint"); |         reason_s = LOG_STR("TLS Bad Fingerprint"); | ||||||
|         break; |         break; | ||||||
|       default: |       default: | ||||||
| @@ -275,7 +280,7 @@ void MQTTClientComponent::loop() { | |||||||
|       this->check_connected(); |       this->check_connected(); | ||||||
|       break; |       break; | ||||||
|     case MQTT_CLIENT_CONNECTED: |     case MQTT_CLIENT_CONNECTED: | ||||||
|       if (!this->mqtt_client_.connected()) { |       if (!this->mqtt_backend_.connected()) { | ||||||
|         this->state_ = MQTT_CLIENT_DISCONNECTED; |         this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||||
|         ESP_LOGW(TAG, "Lost MQTT Client connection!"); |         ESP_LOGW(TAG, "Lost MQTT Client connection!"); | ||||||
|         this->start_dnslookup_(); |         this->start_dnslookup_(); | ||||||
| @@ -302,10 +307,10 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) { | |||||||
|   if (!this->is_connected()) |   if (!this->is_connected()) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   uint16_t ret = this->mqtt_client_.subscribe(topic, qos); |   bool ret = this->mqtt_backend_.subscribe(topic, qos); | ||||||
|   yield(); |   yield(); | ||||||
|  |  | ||||||
|   if (ret != 0) { |   if (ret) { | ||||||
|     ESP_LOGV(TAG, "subscribe(topic='%s')", topic); |     ESP_LOGV(TAG, "subscribe(topic='%s')", topic); | ||||||
|   } else { |   } else { | ||||||
|     delay(5); |     delay(5); | ||||||
| @@ -360,9 +365,9 @@ void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_js | |||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::unsubscribe(const std::string &topic) { | void MQTTClientComponent::unsubscribe(const std::string &topic) { | ||||||
|   uint16_t ret = this->mqtt_client_.unsubscribe(topic.c_str()); |   bool ret = this->mqtt_backend_.unsubscribe(topic.c_str()); | ||||||
|   yield(); |   yield(); | ||||||
|   if (ret != 0) { |   if (ret) { | ||||||
|     ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str()); |     ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str()); | ||||||
|   } else { |   } else { | ||||||
|     delay(5); |     delay(5); | ||||||
| @@ -387,34 +392,35 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p | |||||||
|  |  | ||||||
| bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos, | bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos, | ||||||
|                                   bool retain) { |                                   bool retain) { | ||||||
|  |   return publish({.topic = topic, .payload = payload, .qos = qos, .retain = retain}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MQTTClientComponent::publish(const MQTTMessage &message) { | ||||||
|   if (!this->is_connected()) { |   if (!this->is_connected()) { | ||||||
|     // critical components will re-transmit their messages |     // critical components will re-transmit their messages | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   bool logging_topic = topic == this->log_message_.topic; |   bool logging_topic = this->log_message_.topic == message.topic; | ||||||
|   uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); |   bool ret = this->mqtt_backend_.publish(message); | ||||||
|   delay(0); |   delay(0); | ||||||
|   if (ret == 0 && !logging_topic && this->is_connected()) { |   if (!ret && !logging_topic && this->is_connected()) { | ||||||
|     delay(0); |     delay(0); | ||||||
|     ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); |     ret = this->mqtt_backend_.publish(message); | ||||||
|     delay(0); |     delay(0); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!logging_topic) { |   if (!logging_topic) { | ||||||
|     if (ret != 0) { |     if (ret) { | ||||||
|       ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain); |       ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(), | ||||||
|  |                message.retain); | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(), |       ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(), | ||||||
|                payload_length);  // NOLINT |                message.payload.length()); | ||||||
|       this->status_momentary_warning("publish", 1000); |       this->status_momentary_warning("publish", 1000); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   return ret != 0; |   return ret != 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MQTTClientComponent::publish(const MQTTMessage &message) { |  | ||||||
|   return this->publish(message.topic, message.payload, message.qos, message.retain); |  | ||||||
| } |  | ||||||
| bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, | bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, | ||||||
|                                        bool retain) { |                                        bool retain) { | ||||||
|   std::string message = json::build_json(f); |   std::string message = json::build_json(f); | ||||||
| @@ -499,10 +505,10 @@ bool MQTTClientComponent::is_log_message_enabled() const { return !this->log_mes | |||||||
| void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } | void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } | ||||||
| void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); } | void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); } | ||||||
| void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; } | void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; } | ||||||
| void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_client_.setKeepAlive(keep_alive_s); } | void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_backend_.set_keep_alive(keep_alive_s); } | ||||||
| void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); } | void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); } | ||||||
| const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } | const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; } | ||||||
| void MQTTClientComponent::set_topic_prefix(std::string topic_prefix) { this->topic_prefix_ = std::move(topic_prefix); } | void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; } | ||||||
| const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } | const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; } | ||||||
| void MQTTClientComponent::disable_birth_message() { | void MQTTClientComponent::disable_birth_message() { | ||||||
|   this->birth_message_.topic = ""; |   this->birth_message_.topic = ""; | ||||||
| @@ -549,7 +555,8 @@ void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscovery | |||||||
| void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; } | void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; } | ||||||
|  |  | ||||||
| void MQTTClientComponent::disable_discovery() { | void MQTTClientComponent::disable_discovery() { | ||||||
|   this->discovery_info_ = MQTTDiscoveryInfo{.prefix = "", .retain = false}; |   this->discovery_info_ = MQTTDiscoveryInfo{ | ||||||
|  |       .prefix = "", .retain = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR}; | ||||||
| } | } | ||||||
| void MQTTClientComponent::on_shutdown() { | void MQTTClientComponent::on_shutdown() { | ||||||
|   if (!this->shutdown_message_.topic.empty()) { |   if (!this->shutdown_message_.topic.empty()) { | ||||||
| @@ -557,13 +564,13 @@ void MQTTClientComponent::on_shutdown() { | |||||||
|     this->publish(this->shutdown_message_); |     this->publish(this->shutdown_message_); | ||||||
|     yield(); |     yield(); | ||||||
|   } |   } | ||||||
|   this->mqtt_client_.disconnect(true); |   this->mqtt_backend_.disconnect(); | ||||||
| } | } | ||||||
|  |  | ||||||
| #if ASYNC_TCP_SSL_ENABLED | #if ASYNC_TCP_SSL_ENABLED | ||||||
| void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) { | void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) { | ||||||
|   this->mqtt_client_.setSecure(true); |   this->mqtt_backend_.setSecure(true); | ||||||
|   this->mqtt_client_.addServerFingerprint(fingerprint.data()); |   this->mqtt_backend_.addServerFingerprint(fingerprint.data()); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,7 +9,11 @@ | |||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/components/json/json_util.h" | #include "esphome/components/json/json_util.h" | ||||||
| #include "esphome/components/network/ip_address.h" | #include "esphome/components/network/ip_address.h" | ||||||
| #include <AsyncMqttClient.h> | #if defined(USE_ESP_IDF) | ||||||
|  | #include "mqtt_backend_idf.h" | ||||||
|  | #elif defined(USE_ARDUINO) | ||||||
|  | #include "mqtt_backend_arduino.h" | ||||||
|  | #endif | ||||||
| #include "lwip/ip_addr.h" | #include "lwip/ip_addr.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -22,14 +26,6 @@ namespace mqtt { | |||||||
| using mqtt_callback_t = std::function<void(const std::string &, const std::string &)>; | using mqtt_callback_t = std::function<void(const std::string &, const std::string &)>; | ||||||
| using mqtt_json_callback_t = std::function<void(const std::string &, JsonObject)>; | using mqtt_json_callback_t = std::function<void(const std::string &, JsonObject)>; | ||||||
|  |  | ||||||
| /// internal struct for MQTT messages. |  | ||||||
| struct MQTTMessage { |  | ||||||
|   std::string topic; |  | ||||||
|   std::string payload; |  | ||||||
|   uint8_t qos;  ///< QoS. Only for last will testaments. |  | ||||||
|   bool retain; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /// internal struct for MQTT subscriptions. | /// internal struct for MQTT subscriptions. | ||||||
| struct MQTTSubscription { | struct MQTTSubscription { | ||||||
|   std::string topic; |   std::string topic; | ||||||
| @@ -139,7 +135,10 @@ class MQTTClientComponent : public Component { | |||||||
|    */ |    */ | ||||||
|   void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint); |   void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint); | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |   void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); } | ||||||
|  |   void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); } | ||||||
|  | #endif | ||||||
|   const Availability &get_availability(); |   const Availability &get_availability(); | ||||||
|  |  | ||||||
|   /** Set the topic prefix that will be prepended to all topics together with "/". This will, in most cases, |   /** Set the topic prefix that will be prepended to all topics together with "/". This will, in most cases, | ||||||
| @@ -150,7 +149,7 @@ class MQTTClientComponent : public Component { | |||||||
|    * |    * | ||||||
|    * @param topic_prefix The topic prefix. The last "/" is appended automatically. |    * @param topic_prefix The topic prefix. The last "/" is appended automatically. | ||||||
|    */ |    */ | ||||||
|   void set_topic_prefix(std::string topic_prefix); |   void set_topic_prefix(const std::string &topic_prefix); | ||||||
|   /// Get the topic prefix of this device, using default if necessary |   /// Get the topic prefix of this device, using default if necessary | ||||||
|   const std::string &get_topic_prefix() const; |   const std::string &get_topic_prefix() const; | ||||||
|  |  | ||||||
| @@ -277,6 +276,7 @@ class MQTTClientComponent : public Component { | |||||||
|       .prefix = "homeassistant", |       .prefix = "homeassistant", | ||||||
|       .retain = true, |       .retain = true, | ||||||
|       .clean = false, |       .clean = false, | ||||||
|  |       .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, | ||||||
|   }; |   }; | ||||||
|   std::string topic_prefix_{}; |   std::string topic_prefix_{}; | ||||||
|   MQTTMessage log_message_; |   MQTTMessage log_message_; | ||||||
| @@ -284,7 +284,12 @@ class MQTTClientComponent : public Component { | |||||||
|   int log_level_{ESPHOME_LOG_LEVEL}; |   int log_level_{ESPHOME_LOG_LEVEL}; | ||||||
|  |  | ||||||
|   std::vector<MQTTSubscription> subscriptions_; |   std::vector<MQTTSubscription> subscriptions_; | ||||||
|   AsyncMqttClient mqtt_client_; | #if defined(USE_ESP_IDF) | ||||||
|  |   MQTTBackendIDF mqtt_backend_; | ||||||
|  | #elif defined(USE_ARDUINO) | ||||||
|  |   MQTTBackendArduino mqtt_backend_; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; |   MQTTClientState state_{MQTT_CLIENT_DISCONNECTED}; | ||||||
|   network::IPAddress ip_; |   network::IPAddress ip_; | ||||||
|   bool dns_resolved_{false}; |   bool dns_resolved_{false}; | ||||||
| @@ -293,7 +298,7 @@ class MQTTClientComponent : public Component { | |||||||
|   uint32_t reboot_timeout_{300000}; |   uint32_t reboot_timeout_{300000}; | ||||||
|   uint32_t connect_begin_; |   uint32_t connect_begin_; | ||||||
|   uint32_t last_connected_{0}; |   uint32_t last_connected_{0}; | ||||||
|   optional<AsyncMqttClientDisconnectReason> disconnect_reason_{}; |   optional<MQTTClientDisconnectReason> disconnect_reason_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| @@ -5,3 +6,7 @@ CODEOWNERS = ["@jesserockz"] | |||||||
| nfc_ns = cg.esphome_ns.namespace("nfc") | nfc_ns = cg.esphome_ns.namespace("nfc") | ||||||
|  |  | ||||||
| NfcTag = nfc_ns.class_("NfcTag") | NfcTag = nfc_ns.class_("NfcTag") | ||||||
|  |  | ||||||
|  | NfcOnTagTrigger = nfc_ns.class_( | ||||||
|  |     "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) | ||||||
|  | ) | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								esphome/components/nfc/automation.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/nfc/automation.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | #include "automation.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nfc { | ||||||
|  |  | ||||||
|  | void NfcOnTagTrigger::process(const std::unique_ptr<NfcTag> &tag) { this->trigger(format_uid(tag->get_uid()), *tag); } | ||||||
|  |  | ||||||
|  | }  // namespace nfc | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										17
									
								
								esphome/components/nfc/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								esphome/components/nfc/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <string> | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
|  | #include "nfc.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nfc { | ||||||
|  |  | ||||||
|  | class NfcOnTagTrigger : public Trigger<std::string, NfcTag> { | ||||||
|  |  public: | ||||||
|  |   void process(const std::unique_ptr<NfcTag> &tag); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace nfc | ||||||
|  | }  // namespace esphome | ||||||
| @@ -63,8 +63,8 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e | |||||||
|         cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( |         cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), | ||||||
|                 cv.Optional(CONF_ABOVE): cv.float_, |                 cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), | ||||||
|                 cv.Optional(CONF_BELOW): cv.float_, |                 cv.Optional(CONF_BELOW): cv.templatable(cv.float_), | ||||||
|             }, |             }, | ||||||
|             cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), |             cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW), | ||||||
|         ), |         ), | ||||||
|   | |||||||
| @@ -473,6 +473,8 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ | |||||||
|       App.reboot(); |       App.reboot(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised. | ||||||
|  |     delay(300);  // NOLINT | ||||||
|     App.setup(); |     App.setup(); | ||||||
|  |  | ||||||
|     ESP_LOGI(TAG, "Waiting for OTA attempt."); |     ESP_LOGI(TAG, "Waiting for OTA attempt."); | ||||||
|   | |||||||
| @@ -14,9 +14,6 @@ CONF_ON_FINISHED_WRITE = "on_finished_write" | |||||||
| pn532_ns = cg.esphome_ns.namespace("pn532") | pn532_ns = cg.esphome_ns.namespace("pn532") | ||||||
| PN532 = pn532_ns.class_("PN532", cg.PollingComponent) | PN532 = pn532_ns.class_("PN532", cg.PollingComponent) | ||||||
|  |  | ||||||
| PN532OnTagTrigger = pn532_ns.class_( |  | ||||||
|     "PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag) |  | ||||||
| ) |  | ||||||
| PN532OnFinishedWriteTrigger = pn532_ns.class_( | PN532OnFinishedWriteTrigger = pn532_ns.class_( | ||||||
|     "PN532OnFinishedWriteTrigger", automation.Trigger.template() |     "PN532OnFinishedWriteTrigger", automation.Trigger.template() | ||||||
| ) | ) | ||||||
| @@ -30,7 +27,7 @@ PN532_SCHEMA = cv.Schema( | |||||||
|         cv.GenerateID(): cv.declare_id(PN532), |         cv.GenerateID(): cv.declare_id(PN532), | ||||||
|         cv.Optional(CONF_ON_TAG): automation.validate_automation( |         cv.Optional(CONF_ON_TAG): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( |         cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation( | ||||||
| @@ -42,7 +39,7 @@ PN532_SCHEMA = cv.Schema( | |||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( |         cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger), |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger), | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -144,9 +144,9 @@ void PN532::loop() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (nfcid.size() == this->current_uid_.size()) { |   if (nfcid.size() == this->current_uid_.size()) { | ||||||
|     bool same_uid = false; |     bool same_uid = true; | ||||||
|     for (size_t i = 0; i < nfcid.size(); i++) |     for (size_t i = 0; i < nfcid.size(); i++) | ||||||
|       same_uid |= nfcid[i] == this->current_uid_[i]; |       same_uid &= nfcid[i] == this->current_uid_[i]; | ||||||
|     if (same_uid) |     if (same_uid) | ||||||
|       return; |       return; | ||||||
|   } |   } | ||||||
| @@ -376,9 +376,6 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) { | |||||||
|   this->found_ = true; |   this->found_ = true; | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| void PN532OnTagTrigger::process(const std::unique_ptr<nfc::NfcTag> &tag) { |  | ||||||
|   this->trigger(nfc::format_uid(tag->get_uid()), *tag); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| }  // namespace pn532 | }  // namespace pn532 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
| #include "esphome/components/nfc/nfc_tag.h" | #include "esphome/components/nfc/nfc_tag.h" | ||||||
| #include "esphome/components/nfc/nfc.h" | #include "esphome/components/nfc/nfc.h" | ||||||
|  | #include "esphome/components/nfc/automation.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace pn532 { | namespace pn532 { | ||||||
| @@ -16,7 +17,6 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; | |||||||
| static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | ||||||
|  |  | ||||||
| class PN532BinarySensor; | class PN532BinarySensor; | ||||||
| class PN532OnTagTrigger; |  | ||||||
|  |  | ||||||
| class PN532 : public PollingComponent { | class PN532 : public PollingComponent { | ||||||
|  public: |  public: | ||||||
| @@ -30,8 +30,8 @@ class PN532 : public PollingComponent { | |||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } |   void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } | ||||||
|   void register_ontag_trigger(PN532OnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } |   void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } | ||||||
|   void register_ontagremoved_trigger(PN532OnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } |   void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } | ||||||
|  |  | ||||||
|   void add_on_finished_write_callback(std::function<void()> callback) { |   void add_on_finished_write_callback(std::function<void()> callback) { | ||||||
|     this->on_finished_write_callback_.add(std::move(callback)); |     this->on_finished_write_callback_.add(std::move(callback)); | ||||||
| @@ -79,8 +79,8 @@ class PN532 : public PollingComponent { | |||||||
|  |  | ||||||
|   bool requested_read_{false}; |   bool requested_read_{false}; | ||||||
|   std::vector<PN532BinarySensor *> binary_sensors_; |   std::vector<PN532BinarySensor *> binary_sensors_; | ||||||
|   std::vector<PN532OnTagTrigger *> triggers_ontag_; |   std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_; | ||||||
|   std::vector<PN532OnTagTrigger *> triggers_ontagremoved_; |   std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_; | ||||||
|   std::vector<uint8_t> current_uid_; |   std::vector<uint8_t> current_uid_; | ||||||
|   nfc::NdefMessage *next_task_message_to_write_; |   nfc::NdefMessage *next_task_message_to_write_; | ||||||
|   enum NfcTask { |   enum NfcTask { | ||||||
| @@ -115,11 +115,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { | |||||||
|   bool found_{false}; |   bool found_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> { |  | ||||||
|  public: |  | ||||||
|   void process(const std::unique_ptr<nfc::NfcTag> &tag); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class PN532OnFinishedWriteTrigger : public Trigger<> { | class PN532OnFinishedWriteTrigger : public Trigger<> { | ||||||
|  public: |  public: | ||||||
|   explicit PN532OnFinishedWriteTrigger(PN532 *parent) { |   explicit PN532OnFinishedWriteTrigger(PN532 *parent) { | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/qmp6988/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/qmp6988/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@andrewpc"] | ||||||
							
								
								
									
										397
									
								
								esphome/components/qmp6988/qmp6988.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								esphome/components/qmp6988/qmp6988.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,397 @@ | |||||||
|  | #include "qmp6988.h" | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace qmp6988 { | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CHIP_ID = 0x5C; | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CHIP_ID_REG = 0xD1;     /* Chip ID confirmation Register */ | ||||||
|  | static const uint8_t QMP6988_RESET_REG = 0xE0;       /* Device reset register */ | ||||||
|  | static const uint8_t QMP6988_DEVICE_STAT_REG = 0xF3; /* Device state register */ | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG = 0xF4;    /* Measurement Condition Control Register */ | ||||||
|  | /* data */ | ||||||
|  | static const uint8_t QMP6988_PRESSURE_MSB_REG = 0xF7;    /* Pressure MSB Register */ | ||||||
|  | static const uint8_t QMP6988_TEMPERATURE_MSB_REG = 0xFA; /* Temperature MSB Reg */ | ||||||
|  |  | ||||||
|  | /* compensation calculation */ | ||||||
|  | static const uint8_t QMP6988_CALIBRATION_DATA_START = 0xA0; /* QMP6988 compensation coefficients */ | ||||||
|  | static const uint8_t QMP6988_CALIBRATION_DATA_LENGTH = 25; | ||||||
|  |  | ||||||
|  | static const uint8_t SHIFT_RIGHT_4_POSITION = 4; | ||||||
|  | static const uint8_t SHIFT_LEFT_2_POSITION = 2; | ||||||
|  | static const uint8_t SHIFT_LEFT_4_POSITION = 4; | ||||||
|  | static const uint8_t SHIFT_LEFT_5_POSITION = 5; | ||||||
|  | static const uint8_t SHIFT_LEFT_8_POSITION = 8; | ||||||
|  | static const uint8_t SHIFT_LEFT_12_POSITION = 12; | ||||||
|  | static const uint8_t SHIFT_LEFT_16_POSITION = 16; | ||||||
|  |  | ||||||
|  | /* power mode */ | ||||||
|  | static const uint8_t QMP6988_SLEEP_MODE = 0x00; | ||||||
|  | static const uint8_t QMP6988_FORCED_MODE = 0x01; | ||||||
|  | static const uint8_t QMP6988_NORMAL_MODE = 0x03; | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_MODE_POS = 0; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_MODE_MSK = 0x03; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_MODE_LEN = 2; | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_POS = 5; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_MSK = 0xE0; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_LEN = 3; | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_POS = 2; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_MSK = 0x1C; | ||||||
|  | static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_LEN = 3; | ||||||
|  |  | ||||||
|  | static const uint8_t QMP6988_CONFIG_REG = 0xF1; /*IIR filter co-efficient setting Register*/ | ||||||
|  | static const uint8_t QMP6988_CONFIG_REG_FILTER_POS = 0; | ||||||
|  | static const uint8_t QMP6988_CONFIG_REG_FILTER_MSK = 0x07; | ||||||
|  | static const uint8_t QMP6988_CONFIG_REG_FILTER_LEN = 3; | ||||||
|  |  | ||||||
|  | static const uint32_t SUBTRACTOR = 8388608; | ||||||
|  |  | ||||||
|  | static const char *const TAG = "qmp6988"; | ||||||
|  |  | ||||||
|  | static const char *oversampling_to_str(QMP6988Oversampling oversampling) { | ||||||
|  |   switch (oversampling) { | ||||||
|  |     case QMP6988_OVERSAMPLING_SKIPPED: | ||||||
|  |       return "None"; | ||||||
|  |     case QMP6988_OVERSAMPLING_1X: | ||||||
|  |       return "1x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_2X: | ||||||
|  |       return "2x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_4X: | ||||||
|  |       return "4x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_8X: | ||||||
|  |       return "8x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_16X: | ||||||
|  |       return "16x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_32X: | ||||||
|  |       return "32x"; | ||||||
|  |     case QMP6988_OVERSAMPLING_64X: | ||||||
|  |       return "64x"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const char *iir_filter_to_str(QMP6988IIRFilter filter) { | ||||||
|  |   switch (filter) { | ||||||
|  |     case QMP6988_IIR_FILTER_OFF: | ||||||
|  |       return "OFF"; | ||||||
|  |     case QMP6988_IIR_FILTER_2X: | ||||||
|  |       return "2x"; | ||||||
|  |     case QMP6988_IIR_FILTER_4X: | ||||||
|  |       return "4x"; | ||||||
|  |     case QMP6988_IIR_FILTER_8X: | ||||||
|  |       return "8x"; | ||||||
|  |     case QMP6988_IIR_FILTER_16X: | ||||||
|  |       return "16x"; | ||||||
|  |     case QMP6988_IIR_FILTER_32X: | ||||||
|  |       return "32x"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool QMP6988Component::device_check_() { | ||||||
|  |   uint8_t ret = 0; | ||||||
|  |  | ||||||
|  |   ret = this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1); | ||||||
|  |   if (ret != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "%s: read chip ID (0xD1) failed", __func__); | ||||||
|  |   } | ||||||
|  |   ESP_LOGD(TAG, "qmp6988 read chip id = 0x%x", qmp6988_data_.chip_id); | ||||||
|  |  | ||||||
|  |   return qmp6988_data_.chip_id == QMP6988_CHIP_ID; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool QMP6988Component::get_calibration_data_() { | ||||||
|  |   uint8_t status = 0; | ||||||
|  |   // BITFIELDS temp_COE; | ||||||
|  |   uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0}; | ||||||
|  |   int len; | ||||||
|  |  | ||||||
|  |   for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) { | ||||||
|  |     status = this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1); | ||||||
|  |     if (status != i2c::ERROR_OK) { | ||||||
|  |       ESP_LOGE(TAG, "qmp6988 read calibration data (0xA0) error!"); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_a0 = | ||||||
|  |       (QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) | | ||||||
|  |                        (a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) | (a_data_uint8_tr[24] & 0x0f)) | ||||||
|  |                       << 12); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_a0 = qmp6988_data_.qmp6988_cali.COE_a0 >> 12; | ||||||
|  |  | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_a1 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[21]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_a2 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[23]); | ||||||
|  |  | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_b00 = | ||||||
|  |       (QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) | (a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) | | ||||||
|  |                        ((a_data_uint8_tr[24] & 0xf0) >> SHIFT_RIGHT_4_POSITION)) | ||||||
|  |                       << 12); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_b00 = qmp6988_data_.qmp6988_cali.COE_b00 >> 12; | ||||||
|  |  | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_bt1 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[3]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_bt2 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[5]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_bp1 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[7]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_b11 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[9]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_bp2 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[11]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_b12 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[13]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_b21 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[15]); | ||||||
|  |   qmp6988_data_.qmp6988_cali.COE_bp3 = | ||||||
|  |       (QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[17]); | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); | ||||||
|  |   ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0, | ||||||
|  |            qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00); | ||||||
|  |   ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1, | ||||||
|  |            qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11); | ||||||
|  |   ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2, | ||||||
|  |            qmp6988_data_.qmp6988_cali.COE_b12, qmp6988_data_.qmp6988_cali.COE_b21, qmp6988_data_.qmp6988_cali.COE_bp3); | ||||||
|  |   ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n"); | ||||||
|  |  | ||||||
|  |   qmp6988_data_.ik.a0 = qmp6988_data_.qmp6988_cali.COE_a0;    // 20Q4 | ||||||
|  |   qmp6988_data_.ik.b00 = qmp6988_data_.qmp6988_cali.COE_b00;  // 20Q4 | ||||||
|  |  | ||||||
|  |   qmp6988_data_.ik.a1 = 3608L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L;  // 31Q23 | ||||||
|  |   qmp6988_data_.ik.a2 = 16889L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L;   // 30Q47 | ||||||
|  |  | ||||||
|  |   qmp6988_data_.ik.bt1 = 2982L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L;    // 28Q15 | ||||||
|  |   qmp6988_data_.ik.bt2 = 329854L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L;  // 34Q38 | ||||||
|  |   qmp6988_data_.ik.bp1 = 19923L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L;  // 31Q20 | ||||||
|  |   qmp6988_data_.ik.b11 = 2406L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L;    // 28Q34 | ||||||
|  |   qmp6988_data_.ik.bp2 = 3079L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L;    // 29Q43 | ||||||
|  |   qmp6988_data_.ik.b12 = 6846L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L;     // 29Q53 | ||||||
|  |   qmp6988_data_.ik.b21 = 13836L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L;    // 29Q60 | ||||||
|  |   qmp6988_data_.ik.bp3 = 2915L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L;    // 28Q65 | ||||||
|  |   ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); | ||||||
|  |   ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2, | ||||||
|  |            qmp6988_data_.ik.b00); | ||||||
|  |   ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2, | ||||||
|  |            qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11); | ||||||
|  |   ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12, | ||||||
|  |            qmp6988_data_.ik.b21, qmp6988_data_.ik.bp3); | ||||||
|  |   ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n"); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt) { | ||||||
|  |   QMP6988_S16_t ret; | ||||||
|  |   QMP6988_S64_t wk1, wk2; | ||||||
|  |  | ||||||
|  |   // wk1: 60Q4 // bit size | ||||||
|  |   wk1 = ((QMP6988_S64_t) ik->a1 * (QMP6988_S64_t) dt);        // 31Q23+24-1=54 (54Q23) | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14;  // 30Q47+24-1=53 (39Q33) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10;                     // 39Q33+24-1=62 (52Q23) | ||||||
|  |   wk2 = ((wk1 + wk2) / 32767) >> 19;                          // 54,52->55Q23 (20Q04) | ||||||
|  |   ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4);                 // 21Q4 -> 17Q0 | ||||||
|  |   return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QMP6988_S32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx) { | ||||||
|  |   QMP6988_S32_t ret; | ||||||
|  |   QMP6988_S64_t wk1, wk2, wk3; | ||||||
|  |  | ||||||
|  |   // wk1 = 48Q16 // bit size | ||||||
|  |   wk1 = ((QMP6988_S64_t) ik->bt1 * (QMP6988_S64_t) tx);        // 28Q15+16-1=43 (43Q15) | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->bp1 * (QMP6988_S64_t) dp) >> 5;   // 31Q20+24-1=54 (49Q15) | ||||||
|  |   wk1 += wk2;                                                  // 43,49->50Q15 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->bt2 * (QMP6988_S64_t) tx) >> 1;   // 34Q38+16-1=49 (48Q37) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) tx) >> 8;                       // 48Q37+16-1=63 (55Q29) | ||||||
|  |   wk3 = wk2;                                                   // 55Q29 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->b11 * (QMP6988_S64_t) tx) >> 4;   // 28Q34+16-1=43 (39Q30) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1;                       // 39Q30+24-1=62 (61Q29) | ||||||
|  |   wk3 += wk2;                                                  // 55,61->62Q29 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->bp2 * (QMP6988_S64_t) dp) >> 13;  // 29Q43+24-1=52 (39Q30) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1;                       // 39Q30+24-1=62 (61Q29) | ||||||
|  |   wk3 += wk2;                                                  // 62,61->63Q29 | ||||||
|  |   wk1 += wk3 >> 14;                                            // Q29 >> 14 -> Q15 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->b12 * (QMP6988_S64_t) tx);        // 29Q53+16-1=45 (45Q53) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) tx) >> 22;                      // 45Q53+16-1=61 (39Q31) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1;                       // 39Q31+24-1=62 (61Q30) | ||||||
|  |   wk3 = wk2;                                                   // 61Q30 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->b21 * (QMP6988_S64_t) tx) >> 6;   // 29Q60+16-1=45 (39Q54) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23;                      // 39Q54+24-1=62 (39Q31) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1;                       // 39Q31+24-1=62 (61Q20) | ||||||
|  |   wk3 += wk2;                                                  // 61,61->62Q30 | ||||||
|  |   wk2 = ((QMP6988_S64_t) ik->bp3 * (QMP6988_S64_t) dp) >> 12;  // 28Q65+24-1=51 (39Q53) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23;                      // 39Q53+24-1=62 (39Q30) | ||||||
|  |   wk2 = (wk2 * (QMP6988_S64_t) dp);                            // 39Q30+24-1=62 (62Q30) | ||||||
|  |   wk3 += wk2;                                                  // 62,62->63Q30 | ||||||
|  |   wk1 += wk3 >> 15;                                            // Q30 >> 15 = Q15 | ||||||
|  |   wk1 /= 32767L; | ||||||
|  |   wk1 >>= 11;      // Q15 >> 7 = Q4 | ||||||
|  |   wk1 += ik->b00;  // Q4 + 20Q4 | ||||||
|  |   // wk1 >>= 4; // 28Q4 -> 24Q0 | ||||||
|  |   ret = (QMP6988_S32_t) wk1; | ||||||
|  |   return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::software_reset_() { | ||||||
|  |   uint8_t ret = 0; | ||||||
|  |  | ||||||
|  |   ret = this->write_byte(QMP6988_RESET_REG, 0xe6); | ||||||
|  |   if (ret != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Software Reset (0xe6) failed"); | ||||||
|  |   } | ||||||
|  |   delay(10); | ||||||
|  |  | ||||||
|  |   this->write_byte(QMP6988_RESET_REG, 0x00); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::set_power_mode_(uint8_t power_mode) { | ||||||
|  |   uint8_t data; | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Setting Power mode to: %d", power_mode); | ||||||
|  |  | ||||||
|  |   qmp6988_data_.power_mode = power_mode; | ||||||
|  |   this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); | ||||||
|  |   data = data & 0xfc; | ||||||
|  |   if (power_mode == QMP6988_SLEEP_MODE) { | ||||||
|  |     data |= 0x00; | ||||||
|  |   } else if (power_mode == QMP6988_FORCED_MODE) { | ||||||
|  |     data |= 0x01; | ||||||
|  |   } else if (power_mode == QMP6988_NORMAL_MODE) { | ||||||
|  |     data |= 0x03; | ||||||
|  |   } | ||||||
|  |   this->write_byte(QMP6988_CTRLMEAS_REG, data); | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Set Power mode 0xf4=0x%x \r\n", data); | ||||||
|  |  | ||||||
|  |   delay(10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::write_filter_(unsigned char filter) { | ||||||
|  |   uint8_t data; | ||||||
|  |  | ||||||
|  |   data = (filter & 0x03); | ||||||
|  |   this->write_byte(QMP6988_CONFIG_REG, data); | ||||||
|  |   delay(10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p) { | ||||||
|  |   uint8_t data; | ||||||
|  |  | ||||||
|  |   this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); | ||||||
|  |   data &= 0xe3; | ||||||
|  |   data |= (oversampling_p << 2); | ||||||
|  |   this->write_byte(QMP6988_CTRLMEAS_REG, data); | ||||||
|  |   delay(10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::write_oversampling_temperature_(unsigned char oversampling_t) { | ||||||
|  |   uint8_t data; | ||||||
|  |  | ||||||
|  |   this->read_register(QMP6988_CTRLMEAS_REG, &data, 1); | ||||||
|  |   data &= 0x1f; | ||||||
|  |   data |= (oversampling_t << 5); | ||||||
|  |   this->write_byte(QMP6988_CTRLMEAS_REG, data); | ||||||
|  |   delay(10); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::set_temperature_oversampling(QMP6988Oversampling oversampling_t) { | ||||||
|  |   this->temperature_oversampling_ = oversampling_t; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::set_pressure_oversampling(QMP6988Oversampling oversampling_p) { | ||||||
|  |   this->pressure_oversampling_ = oversampling_p; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; } | ||||||
|  |  | ||||||
|  | void QMP6988Component::calculate_altitude_(float pressure, float temp) { | ||||||
|  |   float altitude; | ||||||
|  |   altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065; | ||||||
|  |   this->qmp6988_data_.altitude = altitude; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::calculate_pressure_() { | ||||||
|  |   uint8_t err = 0; | ||||||
|  |   QMP6988_U32_t p_read, t_read; | ||||||
|  |   QMP6988_S32_t p_raw, t_raw; | ||||||
|  |   uint8_t a_data_uint8_tr[6] = {0}; | ||||||
|  |   QMP6988_S32_t t_int, p_int; | ||||||
|  |   this->qmp6988_data_.temperature = 0; | ||||||
|  |   this->qmp6988_data_.pressure = 0; | ||||||
|  |  | ||||||
|  |   err = this->read_register(QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6); | ||||||
|  |   if (err != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Error reading raw pressure/temp values"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   p_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) | | ||||||
|  |                            (((QMP6988_U16_t)(a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2])); | ||||||
|  |   p_raw = (QMP6988_S32_t)(p_read - SUBTRACTOR); | ||||||
|  |  | ||||||
|  |   t_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) | | ||||||
|  |                            (((QMP6988_U16_t)(a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5])); | ||||||
|  |   t_raw = (QMP6988_S32_t)(t_read - SUBTRACTOR); | ||||||
|  |  | ||||||
|  |   t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw); | ||||||
|  |   p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int); | ||||||
|  |  | ||||||
|  |   this->qmp6988_data_.temperature = (float) t_int / 256.0f; | ||||||
|  |   this->qmp6988_data_.pressure = (float) p_int / 16.0f; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up QMP6988"); | ||||||
|  |  | ||||||
|  |   bool ret; | ||||||
|  |   ret = this->device_check_(); | ||||||
|  |   if (!ret) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "Setup failed - device not found"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->software_reset_(); | ||||||
|  |   this->get_calibration_data_(); | ||||||
|  |   this->set_power_mode_(QMP6988_NORMAL_MODE); | ||||||
|  |   this->write_filter_(iir_filter_); | ||||||
|  |   this->write_oversampling_pressure_(this->pressure_oversampling_); | ||||||
|  |   this->write_oversampling_temperature_(this->temperature_oversampling_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void QMP6988Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "QMP6988:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with QMP6988 failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_)); | ||||||
|  |   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "    Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_)); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  IIR Filter: %s", iir_filter_to_str(this->iir_filter_)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float QMP6988Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void QMP6988Component::update() { | ||||||
|  |   this->calculate_pressure_(); | ||||||
|  |   float pressurehectopascals = this->qmp6988_data_.pressure / 100; | ||||||
|  |   float temperature = this->qmp6988_data_.temperature; | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Temperature=%.2f°C, Pressure=%.2fhPa", temperature, pressurehectopascals); | ||||||
|  |   if (this->temperature_sensor_ != nullptr) | ||||||
|  |     this->temperature_sensor_->publish_state(temperature); | ||||||
|  |   if (this->pressure_sensor_ != nullptr) | ||||||
|  |     this->pressure_sensor_->publish_state(pressurehectopascals); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace qmp6988 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										116
									
								
								esphome/components/qmp6988/qmp6988.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								esphome/components/qmp6988/qmp6988.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace qmp6988 { | ||||||
|  |  | ||||||
|  | #define QMP6988_U16_t unsigned short | ||||||
|  | #define QMP6988_S16_t short | ||||||
|  | #define QMP6988_U32_t unsigned int | ||||||
|  | #define QMP6988_S32_t int | ||||||
|  | #define QMP6988_U64_t unsigned long long | ||||||
|  | #define QMP6988_S64_t long long | ||||||
|  |  | ||||||
|  | /* oversampling */ | ||||||
|  | enum QMP6988Oversampling { | ||||||
|  |   QMP6988_OVERSAMPLING_SKIPPED = 0x00, | ||||||
|  |   QMP6988_OVERSAMPLING_1X = 0x01, | ||||||
|  |   QMP6988_OVERSAMPLING_2X = 0x02, | ||||||
|  |   QMP6988_OVERSAMPLING_4X = 0x03, | ||||||
|  |   QMP6988_OVERSAMPLING_8X = 0x04, | ||||||
|  |   QMP6988_OVERSAMPLING_16X = 0x05, | ||||||
|  |   QMP6988_OVERSAMPLING_32X = 0x06, | ||||||
|  |   QMP6988_OVERSAMPLING_64X = 0x07, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /* filter */ | ||||||
|  | enum QMP6988IIRFilter { | ||||||
|  |   QMP6988_IIR_FILTER_OFF = 0x00, | ||||||
|  |   QMP6988_IIR_FILTER_2X = 0x01, | ||||||
|  |   QMP6988_IIR_FILTER_4X = 0x02, | ||||||
|  |   QMP6988_IIR_FILTER_8X = 0x03, | ||||||
|  |   QMP6988_IIR_FILTER_16X = 0x04, | ||||||
|  |   QMP6988_IIR_FILTER_32X = 0x05, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using qmp6988_cali_data_t = struct Qmp6988CaliData { | ||||||
|  |   QMP6988_S32_t COE_a0; | ||||||
|  |   QMP6988_S16_t COE_a1; | ||||||
|  |   QMP6988_S16_t COE_a2; | ||||||
|  |   QMP6988_S32_t COE_b00; | ||||||
|  |   QMP6988_S16_t COE_bt1; | ||||||
|  |   QMP6988_S16_t COE_bt2; | ||||||
|  |   QMP6988_S16_t COE_bp1; | ||||||
|  |   QMP6988_S16_t COE_b11; | ||||||
|  |   QMP6988_S16_t COE_bp2; | ||||||
|  |   QMP6988_S16_t COE_b12; | ||||||
|  |   QMP6988_S16_t COE_b21; | ||||||
|  |   QMP6988_S16_t COE_bp3; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using qmp6988_fk_data_t = struct Qmp6988FkData { | ||||||
|  |   float a0, b00; | ||||||
|  |   float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using qmp6988_ik_data_t = struct Qmp6988IkData { | ||||||
|  |   QMP6988_S32_t a0, b00; | ||||||
|  |   QMP6988_S32_t a1, a2; | ||||||
|  |   QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | using qmp6988_data_t = struct Qmp6988Data { | ||||||
|  |   uint8_t chip_id; | ||||||
|  |   uint8_t power_mode; | ||||||
|  |   float temperature; | ||||||
|  |   float pressure; | ||||||
|  |   float altitude; | ||||||
|  |   qmp6988_cali_data_t qmp6988_cali; | ||||||
|  |   qmp6988_ik_data_t ik; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class QMP6988Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|  |   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   void set_iir_filter(QMP6988IIRFilter iirfilter); | ||||||
|  |   void set_temperature_oversampling(QMP6988Oversampling oversampling_t); | ||||||
|  |   void set_pressure_oversampling(QMP6988Oversampling oversampling_p); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   qmp6988_data_t qmp6988_data_; | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *pressure_sensor_; | ||||||
|  |  | ||||||
|  |   QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X}; | ||||||
|  |   QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X}; | ||||||
|  |   QMP6988IIRFilter iir_filter_{QMP6988_IIR_FILTER_OFF}; | ||||||
|  |  | ||||||
|  |   void software_reset_(); | ||||||
|  |   bool get_calibration_data_(); | ||||||
|  |   bool device_check_(); | ||||||
|  |   void set_power_mode_(uint8_t power_mode); | ||||||
|  |   void write_oversampling_temperature_(unsigned char oversampling_t); | ||||||
|  |   void write_oversampling_pressure_(unsigned char oversampling_p); | ||||||
|  |   void write_filter_(unsigned char filter); | ||||||
|  |   void calculate_pressure_(); | ||||||
|  |   void calculate_altitude_(float pressure, float temp); | ||||||
|  |  | ||||||
|  |   QMP6988_S32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx); | ||||||
|  |   QMP6988_S16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace qmp6988 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										101
									
								
								esphome/components/qmp6988/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/qmp6988/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     CONF_IIR_FILTER, | ||||||
|  |     CONF_OVERSAMPLING, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | qmp6988_ns = cg.esphome_ns.namespace("qmp6988") | ||||||
|  | QMP6988Component = qmp6988_ns.class_( | ||||||
|  |     "QMP6988Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | QMP6988Oversampling = qmp6988_ns.enum("QMP6988Oversampling") | ||||||
|  | OVERSAMPLING_OPTIONS = { | ||||||
|  |     "NONE": QMP6988Oversampling.QMP6988_OVERSAMPLING_SKIPPED, | ||||||
|  |     "1X": QMP6988Oversampling.QMP6988_OVERSAMPLING_1X, | ||||||
|  |     "2X": QMP6988Oversampling.QMP6988_OVERSAMPLING_2X, | ||||||
|  |     "4X": QMP6988Oversampling.QMP6988_OVERSAMPLING_4X, | ||||||
|  |     "8X": QMP6988Oversampling.QMP6988_OVERSAMPLING_8X, | ||||||
|  |     "16X": QMP6988Oversampling.QMP6988_OVERSAMPLING_16X, | ||||||
|  |     "32X": QMP6988Oversampling.QMP6988_OVERSAMPLING_32X, | ||||||
|  |     "64X": QMP6988Oversampling.QMP6988_OVERSAMPLING_64X, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QMP6988IIRFilter = qmp6988_ns.enum("QMP6988IIRFilter") | ||||||
|  | IIR_FILTER_OPTIONS = { | ||||||
|  |     "OFF": QMP6988IIRFilter.QMP6988_IIR_FILTER_OFF, | ||||||
|  |     "2X": QMP6988IIRFilter.QMP6988_IIR_FILTER_2X, | ||||||
|  |     "4X": QMP6988IIRFilter.QMP6988_IIR_FILTER_4X, | ||||||
|  |     "8X": QMP6988IIRFilter.QMP6988_IIR_FILTER_8X, | ||||||
|  |     "16X": QMP6988IIRFilter.QMP6988_IIR_FILTER_16X, | ||||||
|  |     "32X": QMP6988IIRFilter.QMP6988_IIR_FILTER_32X, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(QMP6988Component), | ||||||
|  |             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ).extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum( | ||||||
|  |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             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, | ||||||
|  |             ).extend( | ||||||
|  |                 { | ||||||
|  |                     cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum( | ||||||
|  |                         OVERSAMPLING_OPTIONS, upper=True | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum( | ||||||
|  |                 IIR_FILTER_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x70)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |  | ||||||
|  |     if CONF_TEMPERATURE in config: | ||||||
|  |         conf = config[CONF_TEMPERATURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |         cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) | ||||||
|  |  | ||||||
|  |     if CONF_PRESSURE in config: | ||||||
|  |         conf = config[CONF_PRESSURE] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_pressure_sensor(sens)) | ||||||
|  |         cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) | ||||||
| @@ -1,3 +1,10 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/rc522/rc522.h" | ||||||
|  | #include "esphome/components/spi/spi.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
| /** | /** | ||||||
|  * Library based on https://github.com/miguelbalboa/rfid |  * Library based on https://github.com/miguelbalboa/rfid | ||||||
|  * and adapted to ESPHome by @glmnet |  * and adapted to ESPHome by @glmnet | ||||||
| @@ -6,14 +13,6 @@ | |||||||
|  * |  * | ||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/components/rc522/rc522.h" |  | ||||||
| #include "esphome/components/spi/spi.h" |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace rc522_spi { | namespace rc522_spi { | ||||||
|  |  | ||||||
| class RC522Spi : public rc522::RC522, | class RC522Spi : public rc522::RC522, | ||||||
|   | |||||||
| @@ -40,6 +40,24 @@ namespace remote_base { | |||||||
|  |  | ||||||
| static const char *const TAG = "remote.pronto"; | static const char *const TAG = "remote.pronto"; | ||||||
|  |  | ||||||
|  | bool ProntoData::operator==(const ProntoData &rhs) const { | ||||||
|  |   std::vector<uint16_t> data1 = encode_pronto(data); | ||||||
|  |   std::vector<uint16_t> data2 = encode_pronto(rhs.data); | ||||||
|  |  | ||||||
|  |   uint32_t total_diff = 0; | ||||||
|  |   // Don't need to check the last one, it's the large gap at the end. | ||||||
|  |   for (std::vector<uint16_t>::size_type i = 0; i < data1.size() - 1; ++i) { | ||||||
|  |     int diff = data2[i] - data1[i]; | ||||||
|  |     diff *= diff; | ||||||
|  |     if (diff > 9) | ||||||
|  |       return false; | ||||||
|  |  | ||||||
|  |     total_diff += diff; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return total_diff <= data1.size() * 3; | ||||||
|  | } | ||||||
|  |  | ||||||
| // DO NOT EXPORT from this file | // DO NOT EXPORT from this file | ||||||
| static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; | static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; | ||||||
| static const uint16_t LEARNED_TOKEN = 0x0000U; | static const uint16_t LEARNED_TOKEN = 0x0000U; | ||||||
| @@ -52,6 +70,7 @@ static const uint32_t REFERENCE_FREQUENCY = 4145146UL; | |||||||
| static const uint16_t FALLBACK_FREQUENCY = 64767U;  // To use with frequency = 0; | static const uint16_t FALLBACK_FREQUENCY = 64767U;  // To use with frequency = 0; | ||||||
| static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; | static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; | ||||||
| static const uint16_t PRONTO_DEFAULT_GAP = 45000; | static const uint16_t PRONTO_DEFAULT_GAP = 45000; | ||||||
|  | static const uint16_t MARK_EXCESS_MICROS = 20; | ||||||
|  |  | ||||||
| static uint16_t to_frequency_k_hz(uint16_t code) { | static uint16_t to_frequency_k_hz(uint16_t code) { | ||||||
|   if (code == 0) |   if (code == 0) | ||||||
| @@ -107,7 +126,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector<uin | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { | std::vector<uint16_t> encode_pronto(const std::string &str) { | ||||||
|   size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; |   size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1; | ||||||
|   std::vector<uint16_t> data; |   std::vector<uint16_t> data; | ||||||
|   const char *p = str.c_str(); |   const char *p = str.c_str(); | ||||||
| @@ -122,12 +141,90 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st | |||||||
|     data.push_back(x);  // If input is conforming, there can be no overflow! |     data.push_back(x);  // If input is conforming, there can be no overflow! | ||||||
|     p = *endptr; |     p = *endptr; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   return data; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) { | ||||||
|  |   std::vector<uint16_t> data = encode_pronto(str); | ||||||
|   send_pronto_(dst, data); |   send_pronto_(dst, data); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } | void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); } | ||||||
|  |  | ||||||
| optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) { return {}; } | uint16_t ProntoProtocol::effective_frequency_(uint16_t frequency) { | ||||||
|  |   return frequency > 0 ? frequency : FALLBACK_FREQUENCY; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint16_t ProntoProtocol::to_timebase_(uint16_t frequency) { | ||||||
|  |   return MICROSECONDS_IN_SECONDS / effective_frequency_(frequency); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint16_t ProntoProtocol::to_frequency_code_(uint16_t frequency) { | ||||||
|  |   return REFERENCE_FREQUENCY / effective_frequency_(frequency); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string ProntoProtocol::dump_digit_(uint8_t x) { | ||||||
|  |   return std::string(1, (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10)))); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string ProntoProtocol::dump_number_(uint16_t number, bool end /* = false */) { | ||||||
|  |   std::string num; | ||||||
|  |  | ||||||
|  |   for (uint8_t i = 0; i < DIGITS_IN_PRONTO_NUMBER; ++i) { | ||||||
|  |     uint8_t shifts = BITS_IN_HEXADECIMAL * (DIGITS_IN_PRONTO_NUMBER - 1 - i); | ||||||
|  |     num += dump_digit_((number >> shifts) & HEX_MASK); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!end) | ||||||
|  |     num += ' '; | ||||||
|  |  | ||||||
|  |   return num; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string ProntoProtocol::dump_duration_(uint32_t duration, uint16_t timebase, bool end /* = false */) { | ||||||
|  |   return dump_number_((duration + timebase / 2) / timebase, end); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string ProntoProtocol::compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase) { | ||||||
|  |   std::string out; | ||||||
|  |  | ||||||
|  |   for (std::vector<int32_t>::size_type i = 0; i < data->size() - 1; i++) { | ||||||
|  |     int32_t t_length = data->at(i); | ||||||
|  |     uint32_t t_duration; | ||||||
|  |     if (t_length > 0) { | ||||||
|  |       // Mark | ||||||
|  |       t_duration = t_length - MARK_EXCESS_MICROS; | ||||||
|  |     } else { | ||||||
|  |       t_duration = -t_length + MARK_EXCESS_MICROS; | ||||||
|  |     } | ||||||
|  |     out += dump_duration_(t_duration, timebase); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // append minimum gap | ||||||
|  |   out += dump_duration_(PRONTO_DEFAULT_GAP, timebase, true); | ||||||
|  |  | ||||||
|  |   return out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) { | ||||||
|  |   ProntoData out; | ||||||
|  |  | ||||||
|  |   uint16_t frequency = 38000U; | ||||||
|  |   std::vector<int32_t> *data = src.get_raw_data(); | ||||||
|  |   std::string prontodata; | ||||||
|  |  | ||||||
|  |   prontodata += dump_number_(frequency > 0 ? LEARNED_TOKEN : LEARNED_NON_MODULATED_TOKEN); | ||||||
|  |   prontodata += dump_number_(to_frequency_code_(frequency)); | ||||||
|  |   prontodata += dump_number_((data->size() + 1) / 2); | ||||||
|  |   prontodata += dump_number_(0); | ||||||
|  |   uint16_t timebase = to_timebase_(frequency); | ||||||
|  |   prontodata += compensate_and_dump_sequence_(data, timebase); | ||||||
|  |  | ||||||
|  |   out.data = prontodata; | ||||||
|  |  | ||||||
|  |   return out; | ||||||
|  | } | ||||||
|  |  | ||||||
| void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } | void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,10 +6,12 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace remote_base { | namespace remote_base { | ||||||
|  |  | ||||||
|  | std::vector<uint16_t> encode_pronto(const std::string &str); | ||||||
|  |  | ||||||
| struct ProntoData { | struct ProntoData { | ||||||
|   std::string data; |   std::string data; | ||||||
|  |  | ||||||
|   bool operator==(const ProntoData &rhs) const { return data == rhs.data; } |   bool operator==(const ProntoData &rhs) const; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ProntoProtocol : public RemoteProtocol<ProntoData> { | class ProntoProtocol : public RemoteProtocol<ProntoData> { | ||||||
| @@ -17,6 +19,14 @@ class ProntoProtocol : public RemoteProtocol<ProntoData> { | |||||||
|   void send_pronto_(RemoteTransmitData *dst, const std::vector<uint16_t> &data); |   void send_pronto_(RemoteTransmitData *dst, const std::vector<uint16_t> &data); | ||||||
|   void send_pronto_(RemoteTransmitData *dst, const std::string &str); |   void send_pronto_(RemoteTransmitData *dst, const std::string &str); | ||||||
|  |  | ||||||
|  |   uint16_t effective_frequency_(uint16_t frequency); | ||||||
|  |   uint16_t to_timebase_(uint16_t frequency); | ||||||
|  |   uint16_t to_frequency_code_(uint16_t frequency); | ||||||
|  |   std::string dump_digit_(uint8_t x); | ||||||
|  |   std::string dump_number_(uint16_t number, bool end = false); | ||||||
|  |   std::string dump_duration_(uint32_t duration, uint16_t timebase, bool end = false); | ||||||
|  |   std::string compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase); | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   void encode(RemoteTransmitData *dst, const ProntoData &data) override; |   void encode(RemoteTransmitData *dst, const ProntoData &data) override; | ||||||
|   optional<ProntoData> decode(RemoteReceiveData src) override; |   optional<ProntoData> decode(RemoteReceiveData src) override; | ||||||
|   | |||||||
| @@ -33,14 +33,8 @@ void SCD30Component::setup() { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /// Firmware version identification |   /// 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]; |   uint16_t raw_firmware_version[3]; | ||||||
|  |   if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) { | ||||||
|   if (!this->read_data_(raw_firmware_version, 3)) { |  | ||||||
|     this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED; |     this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED; | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
| @@ -49,7 +43,7 @@ void SCD30Component::setup() { | |||||||
|            uint16_t(raw_firmware_version[0] & 0xFF)); |            uint16_t(raw_firmware_version[0] & 0xFF)); | ||||||
|  |  | ||||||
|   if (this->temperature_offset_ != 0) { |   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."); |       ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); | ||||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; |       this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
| @@ -69,7 +63,7 @@ void SCD30Component::setup() { | |||||||
|   delay(30); |   delay(30); | ||||||
| #endif | #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."); |     ESP_LOGE(TAG, "Sensor SCD30 error setting update interval."); | ||||||
|     this->error_code_ = MEASUREMENT_INIT_FAILED; |     this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|     this->mark_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 |   // 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->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."); |       ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation."); | ||||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; |       this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
| @@ -92,7 +86,7 @@ void SCD30Component::setup() { | |||||||
|   delay(30); |   delay(30); | ||||||
| #endif | #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."); |     ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration."); | ||||||
|     this->error_code_ = MEASUREMENT_INIT_FAILED; |     this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
| @@ -103,7 +97,7 @@ void SCD30Component::setup() { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /// Sensor initialization |   /// 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."); |     ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements."); | ||||||
|     this->error_code_ = MEASUREMENT_INIT_FAILED; |     this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
| @@ -151,14 +145,14 @@ void SCD30Component::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void SCD30Component::update() { | void SCD30Component::update() { | ||||||
|   uint16_t raw_read_status[1]; |   uint16_t raw_read_status; | ||||||
|   if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { |   if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     ESP_LOGW(TAG, "Data not ready yet!"); |     ESP_LOGW(TAG, "Data not ready yet!"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) { |   if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) { | ||||||
|     ESP_LOGW(TAG, "Error reading measurement!"); |     ESP_LOGW(TAG, "Error reading measurement!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -166,7 +160,7 @@ void SCD30Component::update() { | |||||||
|  |  | ||||||
|   this->set_timeout(50, [this]() { |   this->set_timeout(50, [this]() { | ||||||
|     uint16_t raw_data[6]; |     uint16_t raw_data[6]; | ||||||
|     if (!this->read_data_(raw_data, 6)) { |     if (!this->read_data(raw_data, 6)) { | ||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @@ -197,77 +191,16 @@ void SCD30Component::update() { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool SCD30Component::is_data_ready_() { | 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; |     return false; | ||||||
|   } |   } | ||||||
|   delay(4); |   delay(4); | ||||||
|   uint16_t is_data_ready; |   uint16_t is_data_ready; | ||||||
|   if (!this->read_data_(&is_data_ready, 1)) { |   if (!this->read_data(&is_data_ready, 1)) { | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   return is_data_ready == 1; |   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 scd30 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -2,13 +2,13 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/i2c/i2c.h" | #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace scd30 { | namespace scd30 { | ||||||
|  |  | ||||||
| /// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors. | /// 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: |  public: | ||||||
|   void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } |   void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||||||
|   void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } |   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; } |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  protected: |  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_(); |   bool is_data_ready_(); | ||||||
|  |  | ||||||
|   enum ErrorCode { |   enum ErrorCode { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ from esphome import core | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
|  | from esphome.components import sensirion_common | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_HUMIDITY, |     CONF_HUMIDITY, | ||||||
| @@ -18,9 +19,12 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  | AUTO_LOAD = ["sensirion_common"] | ||||||
|  |  | ||||||
| scd30_ns = cg.esphome_ns.namespace("scd30") | 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_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" | ||||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||||
|   | |||||||
| @@ -25,15 +25,8 @@ void SCD4XComponent::setup() { | |||||||
|  |  | ||||||
|   // the sensor needs 1000 ms to enter the idle state |   // the sensor needs 1000 ms to enter the idle state | ||||||
|   this->set_timeout(1000, [this]() { |   this->set_timeout(1000, [this]() { | ||||||
|     // Check if measurement is ready before reading the value |     uint16_t raw_read_status; | ||||||
|     if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) { |     if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_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)) { |  | ||||||
|       ESP_LOGE(TAG, "Failed to read data ready status"); |       ESP_LOGE(TAG, "Failed to read data ready status"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
| @@ -41,9 +34,9 @@ void SCD4XComponent::setup() { | |||||||
|  |  | ||||||
|     uint32_t stop_measurement_delay = 0; |     uint32_t stop_measurement_delay = 0; | ||||||
|     // In order to query the device periodic measurement must be ceased |     // 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"); |       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"); |         ESP_LOGE(TAG, "Failed to stop measurements"); | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
|         return; |         return; | ||||||
| @@ -53,15 +46,8 @@ void SCD4XComponent::setup() { | |||||||
|       stop_measurement_delay = 500; |       stop_measurement_delay = 500; | ||||||
|     } |     } | ||||||
|     this->set_timeout(stop_measurement_delay, [this]() { |     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]; |       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"); |         ESP_LOGE(TAG, "Failed to read serial number"); | ||||||
|         this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; |         this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
| @@ -70,8 +56,8 @@ void SCD4XComponent::setup() { | |||||||
|       ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8), |       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)); |                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))) { |                                (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { | ||||||
|         ESP_LOGE(TAG, "Error setting temperature offset."); |         ESP_LOGE(TAG, "Error setting temperature offset."); | ||||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
| @@ -88,7 +74,7 @@ void SCD4XComponent::setup() { | |||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|       } else { |       } 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."); |           ESP_LOGE(TAG, "Error setting altitude compensation."); | ||||||
|           this->error_code_ = MEASUREMENT_INIT_FAILED; |           this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|           this->mark_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."); |         ESP_LOGE(TAG, "Error setting automatic self calibration."); | ||||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
| @@ -104,7 +90,7 @@ void SCD4XComponent::setup() { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Finally start sensor measurements |       // 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."); |         ESP_LOGE(TAG, "Error starting continuous measurements."); | ||||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; |         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
| @@ -164,19 +150,19 @@ void SCD4XComponent::update() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Check if data is ready |   // 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(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint16_t raw_read_status[1]; |   uint16_t raw_read_status; | ||||||
|   if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { |   if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     ESP_LOGW(TAG, "Data not ready yet!"); |     ESP_LOGW(TAG, "Data not ready yet!"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { |   if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { | ||||||
|     ESP_LOGW(TAG, "Error reading measurement!"); |     ESP_LOGW(TAG, "Error reading measurement!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -184,7 +170,7 @@ void SCD4XComponent::update() { | |||||||
|  |  | ||||||
|   // Read off sensor data |   // Read off sensor data | ||||||
|   uint16_t raw_data[3]; |   uint16_t raw_data[3]; | ||||||
|   if (!this->read_data_(raw_data, 3)) { |   if (!this->read_data(raw_data, 3)) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     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) { | 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); |     ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); | ||||||
|     return true; |     return true; | ||||||
|   } else { |   } 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 scd4x | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/i2c/i2c.h" | #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace scd4x { | namespace scd4x { | ||||||
|  |  | ||||||
| enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN }; | 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: |  public: | ||||||
|   float get_setup_priority() const override { return setup_priority::DATA; } |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } |   void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||||||
|  |  | ||||||
|  protected: |  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); |   bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); | ||||||
|  |  | ||||||
|   ERRORCODE error_code_; |   ERRORCODE error_code_; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
|  | from esphome.components import sensirion_common | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_CO2, |     CONF_CO2, | ||||||
| @@ -21,9 +21,12 @@ from esphome.const import ( | |||||||
|  |  | ||||||
| CODEOWNERS = ["@sjtrny"] | CODEOWNERS = ["@sjtrny"] | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  | AUTO_LOAD = ["sensirion_common"] | ||||||
|  |  | ||||||
| scd4x_ns = cg.esphome_ns.namespace("scd4x") | 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_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration" | ||||||
| CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | CONF_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||||
|   | |||||||
| @@ -7,55 +7,50 @@ namespace esphome { | |||||||
| namespace sdp3x { | namespace sdp3x { | ||||||
|  |  | ||||||
| static const char *const TAG = "sdp3x.sensor"; | static const char *const TAG = "sdp3x.sensor"; | ||||||
| static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; | static const uint16_t SDP3X_SOFT_RESET = 0x0006; | ||||||
| static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; | static const uint16_t SDP3X_READ_ID1 = 0x367C; | ||||||
| static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; | static const uint16_t SDP3X_READ_ID2 = 0xE102; | ||||||
| static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; | static const uint16_t SDP3X_START_DP_AVG = 0x3615; | ||||||
| static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03}; | static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603; | ||||||
| static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; | static const uint16_t SDP3X_STOP_MEAS = 0x3FF9; | ||||||
|  |  | ||||||
| void SDP3XComponent::update() { this->read_pressure_(); } | void SDP3XComponent::update() { this->read_pressure_(); } | ||||||
|  |  | ||||||
| void SDP3XComponent::setup() { | void SDP3XComponent::setup() { | ||||||
|   ESP_LOGD(TAG, "Setting up SDP3X..."); |   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 |     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 |     ESP_LOGW(TAG, "Soft Reset SDP3X failed!");  // This sometimes fails for no good reason | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->set_timeout(20, [this] { |   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!"); |       ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       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!"); |       ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     uint8_t data[18]; |     uint16_t data[6]; | ||||||
|     if (this->read(data, 18) != i2c::ERROR_OK) { |     if (this->read_data(data, 6) != i2c::ERROR_OK) { | ||||||
|       ESP_LOGE(TAG, "Read ID SDP3X failed!"); |       ESP_LOGE(TAG, "Read ID SDP3X failed!"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       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 |     // SDP8xx | ||||||
|     // ref: |     // ref: | ||||||
|     // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf |     // 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) { |     if (data[1] >> 8 == 0x02) { | ||||||
|       switch (data[3]) { |       switch (data[1] & 0xFF) { | ||||||
|         case 0x01:  // SDP800-500Pa |         case 0x01:  // SDP800-500Pa | ||||||
|           ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); |           ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); | ||||||
|           break; |           break; | ||||||
| @@ -75,15 +70,16 @@ void SDP3XComponent::setup() { | |||||||
|           ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); |           ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); | ||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
|     } else if (data[2] == 0x01) { |     } else if (data[1] >> 8 == 0x01) { | ||||||
|       if (data[3] == 0x01) { |       if ((data[1] & 0xFF) == 0x01) { | ||||||
|         ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa"); |         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"); |         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!"); |       ESP_LOGE(TAG, "Start Measurements SDP3X failed!"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
| @@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void SDP3XComponent::read_pressure_() { | void SDP3XComponent::read_pressure_() { | ||||||
|   uint8_t data[9]; |   uint16_t data[3]; | ||||||
|   if (this->read(data, 9) != i2c::ERROR_OK) { |   if (this->read_data(data, 3) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGW(TAG, "Couldn't read SDP3X data!"); |     ESP_LOGW(TAG, "Couldn't read SDP3X data!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) { |   int16_t pressure_raw = data[0]; | ||||||
|     ESP_LOGW(TAG, "Invalid SDP3X data!"); |   int16_t temperature_raw = data[1]; | ||||||
|     this->status_set_warning(); |   int16_t scale_factor_raw = data[2]; | ||||||
|     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]); |  | ||||||
|   // scale factor is in Pa - convert to hPa |   // scale factor is in Pa - convert to hPa | ||||||
|   float pressure = pressure_raw / (scale_factor_raw * 100.0f); |   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, |   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; } | 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 sdp3x | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ | |||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/i2c/i2c.h" | #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace sdp3x { | namespace sdp3x { | ||||||
|  |  | ||||||
| enum MeasurementMode { MASS_FLOW_AVG, DP_AVG }; | 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: |  public: | ||||||
|   /// Schedule temperature+pressure readings. |   /// Schedule temperature+pressure readings. | ||||||
|   void update() override; |   void update() override; | ||||||
| @@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se | |||||||
|  protected: |  protected: | ||||||
|   /// Internal method to read the pressure from the component after it has been scheduled. |   /// Internal method to read the pressure from the component after it has been scheduled. | ||||||
|   void read_pressure_(); |   void read_pressure_(); | ||||||
|  |  | ||||||
|   bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); |  | ||||||
|   MeasurementMode measurement_mode_; |   MeasurementMode measurement_mode_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import i2c, sensor | from esphome.components import i2c, sensor | ||||||
|  | from esphome.components import sensirion_common | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     DEVICE_CLASS_PRESSURE, |     DEVICE_CLASS_PRESSURE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
| @@ -8,10 +9,13 @@ from esphome.const import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  | AUTO_LOAD = ["sensirion_common"] | ||||||
| CODEOWNERS = ["@Azimath"] | CODEOWNERS = ["@Azimath"] | ||||||
|  |  | ||||||
| sdp3x_ns = cg.esphome_ns.namespace("sdp3x") | 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") | MeasurementMode = sdp3x_ns.enum("MeasurementMode") | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								esphome/components/sensirion_common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								esphome/components/sensirion_common/__init__.py
									
									
									
									
									
										Normal 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) | ||||||
							
								
								
									
										128
									
								
								esphome/components/sensirion_common/i2c_sensirion.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								esphome/components/sensirion_common/i2c_sensirion.cpp
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										155
									
								
								esphome/components/sensirion_common/i2c_sensirion.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								esphome/components/sensirion_common/i2c_sensirion.h
									
									
									
									
									
										Normal 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 | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user