mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +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 | ||||
| repos: | ||||
|   - repo: https://github.com/ambv/black | ||||
|     rev: 22.1.0 | ||||
|     rev: 22.3.0 | ||||
|     hooks: | ||||
|     - id: black | ||||
|       args: | ||||
| @@ -26,7 +26,7 @@ repos: | ||||
|           - --branch=release | ||||
|           - --branch=beta | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v2.31.0 | ||||
|     rev: v2.31.1 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py38-plus] | ||||
|   | ||||
| @@ -82,6 +82,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal | ||||
| esphome/components/homeassistant/* @OttoWinter | ||||
| esphome/components/honeywellabp/* @RubyBailey | ||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||
| esphome/components/hydreon_rgxx/* @functionpointer | ||||
| esphome/components/i2c/* @esphome/core | ||||
| esphome/components/improv_serial/* @esphome/core | ||||
| esphome/components/ina260/* @MrEditor97 | ||||
| @@ -151,6 +152,7 @@ esphome/components/preferences/* @esphome/core | ||||
| esphome/components/psram/* @esphome/core | ||||
| esphome/components/pulse_meter/* @cstaahl @stevebaxter | ||||
| esphome/components/pvvx_mithermometer/* @pasiz | ||||
| esphome/components/qmp6988/* @andrewpc | ||||
| esphome/components/qr_code/* @wjtje | ||||
| esphome/components/radon_eye_ble/* @jeffeb3 | ||||
| esphome/components/radon_eye_rd200/* @jeffeb3 | ||||
| @@ -168,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||
| esphome/components/sdp3x/* @Azimath | ||||
| esphome/components/selec_meter/* @sourabhjaiswal | ||||
| esphome/components/select/* @esphome/core | ||||
| esphome/components/sensirion_common/* @martgras | ||||
| esphome/components/sensor/* @esphome/core | ||||
| esphome/components/sgp40/* @SenexCrenshaw | ||||
| esphome/components/sht4x/* @sjtrny | ||||
| @@ -175,6 +178,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet | ||||
| esphome/components/sim800l/* @glmnet | ||||
| esphome/components/sm2135/* @BoukeHaarsma23 | ||||
| esphome/components/socket/* @esphome/core | ||||
| esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||
| esphome/components/spi/* @esphome/core | ||||
| esphome/components/ssd1322_base/* @kbx81 | ||||
| esphome/components/ssd1322_spi/* @kbx81 | ||||
| @@ -222,4 +226,5 @@ esphome/components/whirlpool/* @glmnet | ||||
| esphome/components/xiaomi_lywsd03mmc/* @ahpohl | ||||
| esphome/components/xiaomi_mhoc303/* @drug123 | ||||
| esphome/components/xiaomi_mhoc401/* @vevsvevs | ||||
| esphome/components/xiaomi_rtcgq02lm/* @jesserockz | ||||
| esphome/components/xpt2046/* @numo68 | ||||
|   | ||||
| @@ -6,13 +6,13 @@ | ||||
| ARG BASEIMGTYPE=docker | ||||
|  | ||||
| # 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/aarch64:5.2.3 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/amd64:5.3.0 AS base-hassio-amd64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64 | ||||
| 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 | ||||
| FROM debian:bullseye-20220125-slim AS base-docker-amd64 | ||||
| FROM debian:bullseye-20220125-slim AS base-docker-arm64 | ||||
| FROM debian:bullseye-20220125-slim AS base-docker-armv7 | ||||
| FROM debian:bullseye-20220328-slim AS base-docker-amd64 | ||||
| FROM debian:bullseye-20220328-slim AS base-docker-arm64 | ||||
| FROM debian:bullseye-20220328-slim AS base-docker-armv7 | ||||
|  | ||||
| # Use TARGETARCH/TARGETVARIANT defined by docker | ||||
| # 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 | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         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-pil=8.1.2+dfsg-0.3+deb11u1 \ | ||||
|         python3-cryptography=3.3.2-1 \ | ||||
|   | ||||
| @@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args): | ||||
|     return var | ||||
|  | ||||
|  | ||||
| def validate_wait_until(value): | ||||
|     schema = cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|             cv.Optional(CONF_TIMEOUT): cv.templatable( | ||||
|                 cv.positive_time_period_milliseconds | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     if isinstance(value, dict) and CONF_CONDITION in value: | ||||
|         return schema(value) | ||||
|     return validate_wait_until({CONF_CONDITION: value}) | ||||
| _validate_wait_until = cv.maybe_simple_value( | ||||
|     { | ||||
|         cv.Required(CONF_CONDITION): validate_potentially_and_condition, | ||||
|         cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds), | ||||
|     }, | ||||
|     key=CONF_CONDITION, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @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): | ||||
|     conditions = await build_condition(config[CONF_CONDITION], template_arg, args) | ||||
|     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; | ||||
|  | ||||
| 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); | ||||
|  | ||||
| #if defined(USE_API_PLAINTEXT) | ||||
|   | ||||
| @@ -7,7 +7,6 @@ | ||||
| #include "esphome/components/socket/socket.h" | ||||
| #include "api_pb2.h" | ||||
| #include "api_pb2_service.h" | ||||
| #include "util.h" | ||||
| #include "list_entities.h" | ||||
| #include "subscribe_state.h" | ||||
| #include "user_services.h" | ||||
|   | ||||
| @@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s | ||||
| #endif | ||||
|  | ||||
| bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) | ||||
|     : ComponentIterator(server), client_(client) {} | ||||
| ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} | ||||
| bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { | ||||
|   auto resp = service->encode_list_service_response(); | ||||
|   return this->client_->send_list_entities_services_response(resp); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/component_iterator.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -11,7 +11,7 @@ class APIConnection; | ||||
|  | ||||
| class ListEntitiesIterator : public ComponentIterator { | ||||
|  public: | ||||
|   ListEntitiesIterator(APIServer *server, APIConnection *client); | ||||
|   ListEntitiesIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
| #endif | ||||
| @@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|  | ||||
| #include "api_server.h" | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "proto.h" | ||||
| #include "util.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
|   | ||||
| @@ -195,6 +195,20 @@ class ProtoWriteBuffer { | ||||
|     this->write((value >> 16) & 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) { | ||||
|     this->encode_uint32(field_id, static_cast<uint32_t>(value), force); | ||||
|   } | ||||
| @@ -229,6 +243,15 @@ class ProtoWriteBuffer { | ||||
|     } | ||||
|     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) { | ||||
|     this->encode_field_raw(field_id, 2); | ||||
|     size_t begin = this->buffer_->size(); | ||||
|   | ||||
| @@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) { | ||||
| #ifdef USE_LOCK | ||||
| bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } | ||||
| #endif | ||||
| InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) | ||||
|     : ComponentIterator(server), client_(client) {} | ||||
| InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/component_iterator.h" | ||||
| #include "esphome/core/controller.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "util.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace api { | ||||
| @@ -12,7 +12,7 @@ class APIConnection; | ||||
|  | ||||
| class InitialStateIterator : public ComponentIterator { | ||||
|  public: | ||||
|   InitialStateIterator(APIServer *server, APIConnection *client); | ||||
|   InitialStateIterator(APIConnection *client); | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override; | ||||
| #endif | ||||
| @@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator { | ||||
|  | ||||
| }  // namespace api | ||||
| }  // 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 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); | ||||
|     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]; | ||||
|   float battery_voltage = battery_millivolt / 1000.0f; | ||||
|  | ||||
|   // Temperature in 1000 * Celsius. | ||||
|   uint16_t temp_millicelcius = data[4] << 8 | data[5]; | ||||
|   float temp_celcius = temp_millicelcius / 1000.0f; | ||||
|   // Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2). | ||||
|   float temp_celsius; | ||||
|   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). | ||||
|   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); | ||||
|   } | ||||
|   if (temperature_ != nullptr) { | ||||
|     temperature_->publish_state(temp_celcius); | ||||
|     temperature_->publish_state(temp_celsius); | ||||
|   } | ||||
|   if (humidity_ != nullptr) { | ||||
|     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); | ||||
|         break; | ||||
|       } | ||||
|       this->conn_id = param->open.conn_id; | ||||
|       auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id); | ||||
|       break; | ||||
|     } | ||||
|     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) { | ||||
|         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; | ||||
|     } | ||||
|     case ESP_GATTC_CFG_MTU_EVT: { | ||||
|       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); | ||||
|         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) { | ||||
|         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_) | ||||
|         delete svc;  // NOLINT(cppcoreguidelines-owning-memory) | ||||
|       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. | ||||
| // Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/ | ||||
| float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #include <esp_gap_ble_api.h> | ||||
| #include <esp_gattc_api.h> | ||||
| #include <esp_bt_defs.h> | ||||
| #include <esp_gatt_common_api.h> | ||||
|  | ||||
| namespace esphome { | ||||
| 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, | ||||
|                            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; | ||||
|   void on_scan_end() override {} | ||||
|   void connect() override; | ||||
|   | ||||
| @@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True | ||||
| CONF_CAN_ID = "can_id" | ||||
| CONF_CAN_ID_MASK = "can_id_mask" | ||||
| CONF_USE_EXTENDED_ID = "use_extended_id" | ||||
| CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request" | ||||
| CONF_CANBUS_ID = "canbus_id" | ||||
| CONF_BIT_RATE = "bit_rate" | ||||
| 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.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), | ||||
|             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), | ||||
|         }, | ||||
|         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)) | ||||
|  | ||||
|     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] | ||||
|     if isinstance(data, bytes): | ||||
|         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; | ||||
|  | ||||
|   uint8_t size = static_cast<uint8_t>(data.size()); | ||||
|   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 { | ||||
|     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) | ||||
|     size = CAN_MAX_DATA_LENGTH; | ||||
|   can_message.can_data_length_code = size; | ||||
|   can_message.can_id = can_id; | ||||
|   can_message.use_extended_id = use_extended_id; | ||||
|   can_message.remote_transmission_request = remote_transmission_request; | ||||
|  | ||||
|   for (int i = 0; i < size; 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; } | ||||
|   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_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; } | ||||
| @@ -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_remote_transmission_request(bool remote_transmission_request) { | ||||
|     this->remote_transmission_request_ = remote_transmission_request; | ||||
|   } | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; | ||||
|     auto use_extended_id = | ||||
|         this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; | ||||
|     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 { | ||||
|       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: | ||||
|   optional<uint32_t> can_id_{}; | ||||
|   optional<bool> use_extended_id_{}; | ||||
|   bool remote_transmission_request_{false}; | ||||
|   bool static_{false}; | ||||
|   std::function<std::vector<uint8_t>(Ts...)> data_func_{}; | ||||
|   std::vector<uint8_t> data_static_{}; | ||||
|   | ||||
| @@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t { | ||||
|   CLIMATE_SWING_HORIZONTAL = 3, | ||||
| }; | ||||
|  | ||||
| /// Enum for all modes a climate swing can be in | ||||
| /// Enum for all preset modes | ||||
| enum ClimatePreset : uint8_t { | ||||
|   /// No preset is active | ||||
|   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. | ||||
| 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); | ||||
|  | ||||
| }  // namespace climate | ||||
|   | ||||
| @@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) { | ||||
| void IRAM_ATTR ESPOneWire::reset_search() { | ||||
|   this->last_discrepancy_ = 0; | ||||
|   this->last_device_flag_ = false; | ||||
|   this->last_family_discrepancy_ = 0; | ||||
|   this->rom_number_ = 0; | ||||
| } | ||||
| uint64_t IRAM_ATTR ESPOneWire::search() { | ||||
| @@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() { | ||||
|  | ||||
|         if (!branch) { | ||||
|           last_zero = id_bit_number; | ||||
|           if (last_zero < 9) { | ||||
|             this->last_discrepancy_ = last_zero; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -60,7 +60,6 @@ class ESPOneWire { | ||||
|  | ||||
|   ISRInternalGPIOPin pin_; | ||||
|   uint8_t last_discrepancy_{0}; | ||||
|   uint8_t last_family_discrepancy_{0}; | ||||
|   bool last_device_flag_{false}; | ||||
|   uint64_t rom_number_{0}; | ||||
| }; | ||||
|   | ||||
| @@ -1,13 +1,18 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import time | ||||
| import esphome.config_validation as cv | ||||
| from esphome import pins, automation | ||||
| from esphome.const import ( | ||||
|     CONF_HOUR, | ||||
|     CONF_ID, | ||||
|     CONF_MINUTE, | ||||
|     CONF_MODE, | ||||
|     CONF_NUMBER, | ||||
|     CONF_PINS, | ||||
|     CONF_RUN_DURATION, | ||||
|     CONF_SECOND, | ||||
|     CONF_SLEEP_DURATION, | ||||
|     CONF_TIME_ID, | ||||
|     CONF_WAKEUP_PIN, | ||||
| ) | ||||
|  | ||||
| @@ -15,6 +20,7 @@ from esphome.components.esp32 import get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32S2, | ||||
| ) | ||||
|  | ||||
| WAKEUP_PINS = { | ||||
| @@ -39,6 +45,30 @@ WAKEUP_PINS = { | ||||
|         39, | ||||
|     ], | ||||
|     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_GPIO_WAKEUP_REASON = "gpio_wakeup_reason" | ||||
| CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason" | ||||
| CONF_UNTIL = "until" | ||||
|  | ||||
| WAKEUP_CAUSES_SCHEMA = cv.Schema( | ||||
|     { | ||||
| @@ -177,13 +208,19 @@ async def to_code(config): | ||||
|     cg.add_define("USE_DEEP_SLEEP") | ||||
|  | ||||
|  | ||||
| DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id( | ||||
|     { | ||||
|         cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||
|         cv.Optional(CONF_SLEEP_DURATION): cv.templatable( | ||||
|             cv.positive_time_period_milliseconds | ||||
|         ), | ||||
|     } | ||||
| DEEP_SLEEP_ENTER_SCHEMA = cv.All( | ||||
|     automation.maybe_simple_id( | ||||
|         { | ||||
|             cv.GenerateID(): cv.use_id(DeepSleepComponent), | ||||
|             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: | ||||
|         template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32) | ||||
|         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 | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "deep_sleep_component.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include <cinttypes> | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include <Esp.h> | ||||
| @@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { | ||||
| #endif | ||||
|  | ||||
|   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(); | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,10 @@ | ||||
| #include <esp_sleep.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_TIME | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace deep_sleep { | ||||
|  | ||||
| @@ -116,15 +120,71 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> { | ||||
|   EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {} | ||||
|   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 { | ||||
|     if (this->sleep_duration_.has_value()) { | ||||
|       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); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   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...> { | ||||
|   | ||||
| @@ -12,6 +12,7 @@ using namespace esphome::cover; | ||||
| CoverTraits EndstopCover::get_traits() { | ||||
|   auto traits = CoverTraits(); | ||||
|   traits.set_supports_position(true); | ||||
|   traits.set_supports_toggle(true); | ||||
|   traits.set_is_assumed_state(false); | ||||
|   return traits; | ||||
| } | ||||
| @@ -20,6 +21,20 @@ void EndstopCover::control(const CoverCall &call) { | ||||
|     this->start_direction_(COVER_OPERATION_IDLE); | ||||
|     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()) { | ||||
|     auto pos = *call.get_position(); | ||||
|     if (pos == this->position) { | ||||
| @@ -125,9 +140,11 @@ void EndstopCover::start_direction_(CoverOperation dir) { | ||||
|       trig = this->stop_trigger_; | ||||
|       break; | ||||
|     case COVER_OPERATION_OPENING: | ||||
|       this->last_operation_ = dir; | ||||
|       trig = this->open_trigger_; | ||||
|       break; | ||||
|     case COVER_OPERATION_CLOSING: | ||||
|       this->last_operation_ = dir; | ||||
|       trig = this->close_trigger_; | ||||
|       break; | ||||
|     default: | ||||
|   | ||||
| @@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component { | ||||
|   uint32_t start_dir_time_{0}; | ||||
|   uint32_t last_publish_time_{0}; | ||||
|   float target_position_{0}; | ||||
|   cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; | ||||
| }; | ||||
|  | ||||
| }  // namespace endstop | ||||
|   | ||||
| @@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ | ||||
|     default: | ||||
|       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) { | ||||
|   | ||||
| @@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener { | ||||
|  public: | ||||
|   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; | ||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||
|   virtual void connect() = 0; | ||||
|   void set_state(ClientState st) { this->state_ = st; } | ||||
|   ClientState state() const { return state_; } | ||||
|   | ||||
| @@ -1,12 +1,29 @@ | ||||
| import functools | ||||
| from pathlib import Path | ||||
| import hashlib | ||||
| import re | ||||
|  | ||||
| import requests | ||||
|  | ||||
| from esphome import core | ||||
| from esphome.components import display | ||||
| import esphome.config_validation as cv | ||||
| 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 | ||||
|  | ||||
|  | ||||
| DOMAIN = "font" | ||||
| DEPENDENCIES = ["display"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| @@ -71,6 +88,128 @@ def validate_truetype_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 = ( | ||||
|     ' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°' | ||||
| ) | ||||
| @@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id" | ||||
| FONT_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         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_SIZE, default=20): cv.int_range(min=1), | ||||
|         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): | ||||
|     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: | ||||
|         font = ImageFont.truetype(path, config[CONF_SIZE]) | ||||
|         font = ImageFont.truetype(str(path), config[CONF_SIZE]) | ||||
|     except Exception as 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 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) { | ||||
|   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); | ||||
|   }; | ||||
|  | ||||
|   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].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_[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_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_, 11, 12, ONE_DEC_UNIT); | ||||
|   publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_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->phases_[0].voltage_sensor_, 14, 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_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_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].current_sensor_, 19, 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_[1].voltage_sensor_, 18, 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_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_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, 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_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->total_energy_production_, 28, 29, 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_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() { | ||||
|   | ||||
| @@ -10,12 +10,19 @@ namespace growatt_solar { | ||||
| static const float TWO_DEC_UNIT = 0.01; | ||||
| static const float ONE_DEC_UNIT = 0.1; | ||||
|  | ||||
| enum GrowattProtocolVersion { | ||||
|   RTU = 0, | ||||
|   RTU2, | ||||
| }; | ||||
|  | ||||
| class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { | ||||
|  public: | ||||
|   void update() override; | ||||
|   void on_modbus_data(const std::vector<uint8_t> &data) 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_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 *total_energy_production_{nullptr}; | ||||
|   sensor::Sensor *inverter_module_temp_{nullptr}; | ||||
|   GrowattProtocolVersion protocol_version_; | ||||
| }; | ||||
|  | ||||
| }  // namespace growatt_solar | ||||
|   | ||||
| @@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA" | ||||
| CONF_INVERTER_STATUS = "inverter_status" | ||||
| CONF_PV_ACTIVE_POWER = "pv_active_power" | ||||
| CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" | ||||
|  | ||||
| CONF_PROTOCOL_VERSION = "protocol_version" | ||||
|  | ||||
| AUTO_LOAD = ["modbus"] | ||||
| CODEOWNERS = ["@leeuwte"] | ||||
| @@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema( | ||||
|     {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 = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             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_B): 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 modbus.register_modbus_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION])) | ||||
|  | ||||
|     if CONF_INVERTER_STATUS in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS]) | ||||
|         cg.add(var.set_inverter_status_sensor(sens)) | ||||
|   | ||||
| @@ -25,6 +25,7 @@ PROTOCOLS = { | ||||
|     "daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417, | ||||
|     "daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480, | ||||
|     "daikin": Protocol.PROTOCOL_DAIKIN, | ||||
|     "electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL, | ||||
|     "fuego": Protocol.PROTOCOL_FUEGO, | ||||
|     "fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ, | ||||
|     "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_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||
|  | ||||
|     # PIO isn't updating releases, so referencing the release tag directly. See: | ||||
|     # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd | ||||
|     cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18") | ||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.20") | ||||
|   | ||||
| @@ -20,6 +20,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP | ||||
|     {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }},                 // NOLINT | ||||
|     {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }},              // NOLINT | ||||
|     {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }},                              // NOLINT | ||||
|     {PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }},                // NOLINT | ||||
|     {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }},                                // NOLINT | ||||
|     {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }},                       // NOLINT | ||||
|     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT | ||||
|   | ||||
| @@ -20,6 +20,7 @@ enum Protocol { | ||||
|   PROTOCOL_DAIKIN_ARC417, | ||||
|   PROTOCOL_DAIKIN_ARC480, | ||||
|   PROTOCOL_DAIKIN, | ||||
|   PROTOCOL_ELECTROLUXYAL, | ||||
|   PROTOCOL_FUEGO, | ||||
|   PROTOCOL_FUJITSU_AWYZ, | ||||
|   PROTOCOL_GREE, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace hm3301 { | ||||
|  | ||||
| class AbstractAQICalculator { | ||||
|  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 | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace hm3301 { | ||||
|  | ||||
| class AQICalculator : public AbstractAQICalculator { | ||||
|  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 pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_); | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ namespace hm3301 { | ||||
|  | ||||
| class CAQICalculator : public AbstractAQICalculator { | ||||
|  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 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); | ||||
|   } | ||||
|  | ||||
|   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) { | ||||
|     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); | ||||
|   | ||||
| @@ -1,4 +1,20 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL | ||||
|  | ||||
| CODEOWNERS = ["@OttoWinter"] | ||||
| 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.config_validation as cv | ||||
| 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"] | ||||
|  | ||||
| HomeassistantBinarySensor = homeassistant_ns.class_( | ||||
|     "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     binary_sensor.binary_sensor_schema(HomeassistantBinarySensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||
|             cv.Optional(CONF_ATTRIBUTE): cv.string, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend( | ||||
|     HOME_ASSISTANT_IMPORT_SCHEMA | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await binary_sensor.new_binary_sensor(config) | ||||
|     await cg.register_component(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])) | ||||
|     setup_home_assistant_entity(var, config) | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import ( | ||||
|     CONF_ATTRIBUTE, | ||||
|     CONF_ENTITY_ID, | ||||
|     CONF_ID, | ||||
|  | ||||
| from .. import ( | ||||
|     HOME_ASSISTANT_IMPORT_SCHEMA, | ||||
|     homeassistant_ns, | ||||
|     setup_home_assistant_entity, | ||||
| ) | ||||
| from .. import homeassistant_ns | ||||
|  | ||||
| DEPENDENCIES = ["api"] | ||||
|  | ||||
| @@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_( | ||||
|     "HomeassistantSensor", sensor.Sensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend( | ||||
|     { | ||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||
|         cv.Optional(CONF_ATTRIBUTE): cv.string, | ||||
|     } | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend( | ||||
|     HOME_ASSISTANT_IMPORT_SCHEMA | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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 sensor.register_sensor(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])) | ||||
|     setup_home_assistant_entity(var, config) | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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"] | ||||
|  | ||||
| @@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_( | ||||
|     "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), | ||||
|         cv.Required(CONF_ENTITY_ID): cv.entity_id, | ||||
|         cv.Optional(CONF_ATTRIBUTE): cv.string, | ||||
|     } | ||||
| CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend( | ||||
|     HOME_ASSISTANT_IMPORT_SCHEMA | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await text_sensor.new_text_sensor(config) | ||||
|     await cg.register_component(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])) | ||||
|     setup_home_assistant_entity(var, config) | ||||
|   | ||||
							
								
								
									
										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}; } | ||||
|  | ||||
|   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 err = this->write(&a_register, 1); | ||||
|   ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) { | ||||
|     ErrorCode err = this->write(&a_register, 1, stop); | ||||
|     if (err != ERROR_OK) | ||||
|       return err; | ||||
|     return this->read(data, len); | ||||
|   } | ||||
|  | ||||
|   ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); } | ||||
|   ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t 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, bool stop = true) { | ||||
|     WriteBuffer buffers[2]; | ||||
|     buffers[0].data = &a_register; | ||||
|     buffers[0].len = 1; | ||||
|     buffers[1].data = data; | ||||
|     buffers[1].len = len; | ||||
|     return bus_->writev(address_, buffers, 2); | ||||
|     return bus_->writev(address_, buffers, 2, stop); | ||||
|   } | ||||
|  | ||||
|   // Compat APIs | ||||
| @@ -93,7 +93,9 @@ class I2CDevice { | ||||
|     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) { | ||||
|     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 write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { | ||||
|     return write_register(a_register, data, len) == ERROR_OK; | ||||
|   bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) { | ||||
|     return write_register(a_register, data, len, stop) == ERROR_OK; | ||||
|   } | ||||
|  | ||||
|   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_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); } | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ enum ErrorCode { | ||||
|   ERROR_NOT_INITIALIZED = 4, | ||||
|   ERROR_TOO_LARGE = 5, | ||||
|   ERROR_UNKNOWN = 6, | ||||
|   ERROR_CRC = 7, | ||||
| }; | ||||
|  | ||||
| struct ReadBuffer { | ||||
| @@ -36,12 +37,18 @@ class I2CBus { | ||||
|   } | ||||
|   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) { | ||||
|     return write(address, buffer, len, true); | ||||
|   } | ||||
|   virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) { | ||||
|     WriteBuffer buf; | ||||
|     buf.data = buffer; | ||||
|     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: | ||||
|   void i2c_scan_() { | ||||
|   | ||||
| @@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) | ||||
|  | ||||
|   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 | ||||
|   // should log them | ||||
|   if (!initialized_) { | ||||
| @@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn | ||||
|       return ERROR_UNKNOWN; | ||||
|     } | ||||
|   } | ||||
|   uint8_t status = wire_->endTransmission(true); | ||||
|   uint8_t status = wire_->endTransmission(stop); | ||||
|   if (status == 0) { | ||||
|     return ERROR_OK; | ||||
|   } else if (status == 1) { | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component { | ||||
|   void setup() override; | ||||
|   void dump_config() 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; } | ||||
|  | ||||
|   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; | ||||
| } | ||||
| 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 | ||||
|   // should log them | ||||
|   if (!initialized_) { | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component { | ||||
|   void setup() override; | ||||
|   void dump_config() 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; } | ||||
|  | ||||
|   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 | ||||
|  | ||||
| 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. | ||||
|   // The excess memory is freed below with `shrinkToFit()` | ||||
| #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) | ||||
|   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 | ||||
|  | ||||
|   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>(); | ||||
|   f(root); | ||||
|   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) { | ||||
|   // 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. | ||||
|   // The excess memory is freed below with `shrinkToFit()` | ||||
| #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) | ||||
|   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 | ||||
|   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); | ||||
|   DeserializationError err = deserializeJson(json_document, data); | ||||
|   json_document.shrinkToFit(); | ||||
|     JsonObject root = json_document.as<JsonObject>(); | ||||
|  | ||||
|   JsonObject root = json_document.as<JsonObject>(); | ||||
|  | ||||
|   if (err) { | ||||
|     ESP_LOGW(TAG, "Parsing JSON failed."); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   f(root); | ||||
|     if (err == DeserializationError::Ok) { | ||||
|       pass = true; | ||||
|       f(root); | ||||
|     } else if (err == DeserializationError::NoMemory) { | ||||
|       if (request_size * 2 >= free_heap) { | ||||
|         ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); | ||||
|         return; | ||||
|       } | ||||
|       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 | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| 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") | ||||
| LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent) | ||||
| @@ -16,9 +18,35 @@ def validate_lcd_dimensions(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( | ||||
|     { | ||||
|         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")) | ||||
|  | ||||
| @@ -27,3 +55,6 @@ async def setup_lcd_display(var, config): | ||||
|     await cg.register_component(var, config) | ||||
|     await display.register_display(var, config) | ||||
|     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); | ||||
|   } | ||||
|  | ||||
|   // 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); | ||||
|   uint8_t display_control = LCD_DISPLAY_DISPLAY_ON; | ||||
|   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); } | ||||
| #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 esphome | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #endif | ||||
|  | ||||
| #include <map> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace lcd_base { | ||||
|  | ||||
| @@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent { | ||||
|     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; | ||||
|   float get_setup_priority() const 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))); | ||||
| #endif | ||||
|  | ||||
|   /// Load custom char to given location | ||||
|   void loadchar(uint8_t location, uint8_t charmap[]); | ||||
|  | ||||
|  protected: | ||||
|   virtual bool is_four_bit_mode() = 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 rows_; | ||||
|   uint8_t *buffer_{nullptr}; | ||||
|   std::map<uint8_t, std::vector<uint8_t> > user_defined_chars_; | ||||
| }; | ||||
|  | ||||
| }  // 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): | ||||
|     # https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python | ||||
|     cfmt = r""" | ||||
| @@ -234,7 +225,7 @@ def validate_printf(value): | ||||
|  | ||||
| CONF_LOGGER_LOG = "logger.log" | ||||
| LOGGER_LOG_ACTION_SCHEMA = cv.All( | ||||
|     maybe_simple_message( | ||||
|     cv.maybe_simple_value( | ||||
|         { | ||||
|             cv.Required(CONF_FORMAT): cv.string, | ||||
|             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 | ||||
|             ), | ||||
|             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) { | ||||
|   uint8_t adc_primary_config = 0b00000110 & 0b00000111; | ||||
|   uint8_t adc_primary_config = 0b00000110 | (pin >> 2); | ||||
|   uint8_t adc_secondary_config = pin << 6; | ||||
|   this->enable(); | ||||
|   this->transfer_byte(adc_primary_config); | ||||
|   | ||||
| @@ -17,7 +17,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(MCP3204Sensor), | ||||
|         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")) | ||||
|  | ||||
|   | ||||
| @@ -71,9 +71,9 @@ SENSOR_VALUE_TYPE = { | ||||
|     "S_DWORD": SensorValueType.S_DWORD, | ||||
|     "S_DWORD_R": SensorValueType.S_DWORD_R, | ||||
|     "U_QWORD": SensorValueType.U_QWORD, | ||||
|     "U_QWORDU_R": SensorValueType.U_QWORD_R, | ||||
|     "U_QWORD_R": SensorValueType.U_QWORD_R, | ||||
|     "S_QWORD": SensorValueType.S_QWORD, | ||||
|     "U_QWORD_R": SensorValueType.S_QWORD_R, | ||||
|     "S_QWORD_R": SensorValueType.S_QWORD_R, | ||||
|     "FP32": SensorValueType.FP32, | ||||
|     "FP32_R": SensorValueType.FP32_R, | ||||
| } | ||||
| @@ -87,9 +87,9 @@ TYPE_REGISTER_MAP = { | ||||
|     "S_DWORD": 2, | ||||
|     "S_DWORD_R": 2, | ||||
|     "U_QWORD": 4, | ||||
|     "U_QWORDU_R": 4, | ||||
|     "S_QWORD": 4, | ||||
|     "U_QWORD_R": 4, | ||||
|     "S_QWORD": 4, | ||||
|     "S_QWORD_R": 4, | ||||
|     "FP32": 2, | ||||
|     "FP32_R": 2, | ||||
| } | ||||
|   | ||||
| @@ -455,6 +455,28 @@ ModbusCommandItem ModbusCommandItem::create_custom_command( | ||||
|   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() { | ||||
|   if (this->function_code != ModbusFunctionCode::CUSTOM) { | ||||
|     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/automation.h" | ||||
| #include "esphome/components/modbus/modbus.h" | ||||
| #include "esphome/core/automation.h" | ||||
|  | ||||
| #include <list> | ||||
| #include <set> | ||||
| #include <queue> | ||||
| #include <set> | ||||
| #include <vector> | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -374,8 +374,8 @@ class ModbusCommandItem { | ||||
|                                                        const std::vector<bool> &values); | ||||
|   /** Create custom modbus 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 | ||||
|    * exception of the crc codess | ||||
|    * @param values byte 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 | ||||
|    */ | ||||
| @@ -383,6 +383,18 @@ class ModbusCommandItem { | ||||
|       ModbusController *modbusdevice, const std::vector<uint8_t> &values, | ||||
|       std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)> | ||||
|           &&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. | ||||
|   | ||||
| @@ -26,6 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) { | ||||
| } | ||||
|  | ||||
| void ModbusNumber::control(float value) { | ||||
|   ModbusCommandItem write_cmd; | ||||
|   std::vector<uint16_t> data; | ||||
|   float write_value = value; | ||||
|   // Is there are lambda configured? | ||||
| @@ -45,33 +46,39 @@ void ModbusNumber::control(float value) { | ||||
|     write_value = multiply_by_ * write_value; | ||||
|   } | ||||
|  | ||||
|   // lambda didn't set payload | ||||
|   if (data.empty()) { | ||||
|     data = float_to_payload(write_value, this->sensor_value_type); | ||||
|   } | ||||
|  | ||||
|   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 | ||||
|   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]); | ||||
|   if (!data.empty()) { | ||||
|     ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str()); | ||||
|     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) { | ||||
|           this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data); | ||||
|         }); | ||||
|   } else { | ||||
|     write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, | ||||
|                                                                  this->register_count, data); | ||||
|     data = float_to_payload(write_value, this->sensor_value_type); | ||||
|  | ||||
|     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); | ||||
|   this->publish_state(value); | ||||
| } | ||||
| 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 | ||||
| from esphome.components import select | ||||
| from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC | ||||
| from esphome.jsonschema import jschema_composite | ||||
|  | ||||
| from .. import ( | ||||
|     SENSOR_VALUE_TYPE, | ||||
| @@ -30,7 +29,6 @@ ModbusSelect = modbus_controller_ns.class_( | ||||
| ) | ||||
|  | ||||
|  | ||||
| @jschema_composite | ||||
| def ensure_option_map(): | ||||
|     def validator(value): | ||||
|         cv.check_not_templatable(value) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from esphome.const import ( | ||||
|     CONF_AVAILABILITY, | ||||
|     CONF_BIRTH_MESSAGE, | ||||
|     CONF_BROKER, | ||||
|     CONF_CERTIFICATE_AUTHORITY, | ||||
|     CONF_CLIENT_ID, | ||||
|     CONF_COMMAND_TOPIC, | ||||
|     CONF_COMMAND_RETAIN, | ||||
| @@ -42,9 +43,14 @@ from esphome.const import ( | ||||
|     CONF_WILL_MESSAGE, | ||||
| ) | ||||
| from esphome.core import coroutine_with_priority, CORE | ||||
| from esphome.components.esp32 import add_idf_sdkconfig_option | ||||
|  | ||||
| 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): | ||||
| @@ -163,6 +169,15 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_USERNAME, default=""): cv.string, | ||||
|             cv.Optional(CONF_PASSWORD, default=""): 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.boolean, cv.one_of("CLEAN", upper=True) | ||||
|             ), | ||||
| @@ -217,7 +232,6 @@ CONFIG_SCHEMA = cv.All( | ||||
|         } | ||||
|     ), | ||||
|     validate_config, | ||||
|     cv.only_with_arduino, | ||||
| ) | ||||
|  | ||||
|  | ||||
| @@ -238,9 +252,11 @@ def exp_mqtt_message(config): | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     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_global(mqtt_ns.using) | ||||
|  | ||||
| @@ -321,6 +337,19 @@ async def to_code(config): | ||||
|  | ||||
|     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, []): | ||||
|         trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC]) | ||||
|         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 | ||||
| void MQTTClientComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up MQTT..."); | ||||
|   this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties, | ||||
|                                       size_t len, size_t index, size_t total) { | ||||
|     if (index == 0) | ||||
|       this->payload_buffer_.reserve(total); | ||||
|   this->mqtt_backend_.set_on_message( | ||||
|       [this](const char *topic, const char *payload, size_t len, size_t index, size_t total) { | ||||
|         if (index == 0) | ||||
|           this->payload_buffer_.reserve(total); | ||||
|  | ||||
|     // append new payload, may contain incomplete MQTT message | ||||
|     this->payload_buffer_.append(payload, len); | ||||
|         // append new payload, may contain incomplete MQTT message | ||||
|         this->payload_buffer_.append(payload, len); | ||||
|  | ||||
|     // MQTT fully received | ||||
|     if (len + index == total) { | ||||
|       this->on_message(topic, this->payload_buffer_); | ||||
|       this->payload_buffer_.clear(); | ||||
|     } | ||||
|   }); | ||||
|   this->mqtt_client_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) { | ||||
|         // MQTT fully received | ||||
|         if (len + index == total) { | ||||
|           this->on_message(topic, this->payload_buffer_); | ||||
|           this->payload_buffer_.clear(); | ||||
|         } | ||||
|       }); | ||||
|   this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) { | ||||
|     this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||
|     this->disconnect_reason_ = reason; | ||||
|   }); | ||||
| @@ -49,8 +49,10 @@ void MQTTClientComponent::setup() { | ||||
|   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) { | ||||
|       if (level <= this->log_level_ && this->is_connected()) { | ||||
|         this->publish(this->log_message_.topic, message, strlen(message), this->log_message_.qos, | ||||
|                       this->log_message_.retain); | ||||
|         this->publish({.topic = this->log_message_.topic, | ||||
|                        .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..."); | ||||
|   // 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; | ||||
|   if (!this->credentials_.username.empty()) | ||||
|     username = this->credentials_.username.c_str(); | ||||
| @@ -183,24 +185,24 @@ void MQTTClientComponent::start_connect_() { | ||||
|   if (!this->credentials_.password.empty()) | ||||
|     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()) { | ||||
|     this->mqtt_client_.setWill(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->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->mqtt_client_.connect(); | ||||
|   this->mqtt_backend_.connect(); | ||||
|   this->state_ = MQTT_CLIENT_CONNECTING; | ||||
|   this->connect_begin_ = millis(); | ||||
| } | ||||
| 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() { | ||||
|   if (!this->mqtt_client_.connected()) { | ||||
|   if (!this->mqtt_backend_.connected()) { | ||||
|     if (millis() - this->connect_begin_ > 60000) { | ||||
|       this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||
|       this->start_dnslookup_(); | ||||
| @@ -222,31 +224,34 @@ void MQTTClientComponent::check_connected() { | ||||
| } | ||||
|  | ||||
| void MQTTClientComponent::loop() { | ||||
|   // Call the backend loop first | ||||
|   mqtt_backend_.loop(); | ||||
|  | ||||
|   if (this->disconnect_reason_.has_value()) { | ||||
|     const LogString *reason_s; | ||||
|     switch (*this->disconnect_reason_) { | ||||
|       case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED: | ||||
|       case MQTTClientDisconnectReason::TCP_DISCONNECTED: | ||||
|         reason_s = LOG_STR("TCP disconnected"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: | ||||
|       case MQTTClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: | ||||
|         reason_s = LOG_STR("Unacceptable Protocol Version"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: | ||||
|       case MQTTClientDisconnectReason::MQTT_IDENTIFIER_REJECTED: | ||||
|         reason_s = LOG_STR("Identifier Rejected"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: | ||||
|       case MQTTClientDisconnectReason::MQTT_SERVER_UNAVAILABLE: | ||||
|         reason_s = LOG_STR("Server Unavailable"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: | ||||
|       case MQTTClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS: | ||||
|         reason_s = LOG_STR("Malformed Credentials"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED: | ||||
|       case MQTTClientDisconnectReason::MQTT_NOT_AUTHORIZED: | ||||
|         reason_s = LOG_STR("Not Authorized"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: | ||||
|       case MQTTClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE: | ||||
|         reason_s = LOG_STR("Not Enough Space"); | ||||
|         break; | ||||
|       case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT: | ||||
|       case MQTTClientDisconnectReason::TLS_BAD_FINGERPRINT: | ||||
|         reason_s = LOG_STR("TLS Bad Fingerprint"); | ||||
|         break; | ||||
|       default: | ||||
| @@ -275,7 +280,7 @@ void MQTTClientComponent::loop() { | ||||
|       this->check_connected(); | ||||
|       break; | ||||
|     case MQTT_CLIENT_CONNECTED: | ||||
|       if (!this->mqtt_client_.connected()) { | ||||
|       if (!this->mqtt_backend_.connected()) { | ||||
|         this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||
|         ESP_LOGW(TAG, "Lost MQTT Client connection!"); | ||||
|         this->start_dnslookup_(); | ||||
| @@ -302,10 +307,10 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) { | ||||
|   if (!this->is_connected()) | ||||
|     return false; | ||||
|  | ||||
|   uint16_t ret = this->mqtt_client_.subscribe(topic, qos); | ||||
|   bool ret = this->mqtt_backend_.subscribe(topic, qos); | ||||
|   yield(); | ||||
|  | ||||
|   if (ret != 0) { | ||||
|   if (ret) { | ||||
|     ESP_LOGV(TAG, "subscribe(topic='%s')", topic); | ||||
|   } else { | ||||
|     delay(5); | ||||
| @@ -360,9 +365,9 @@ void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_js | ||||
| } | ||||
|  | ||||
| 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(); | ||||
|   if (ret != 0) { | ||||
|   if (ret) { | ||||
|     ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str()); | ||||
|   } else { | ||||
|     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 retain) { | ||||
|   return publish({.topic = topic, .payload = payload, .qos = qos, .retain = retain}); | ||||
| } | ||||
|  | ||||
| bool MQTTClientComponent::publish(const MQTTMessage &message) { | ||||
|   if (!this->is_connected()) { | ||||
|     // critical components will re-transmit their messages | ||||
|     return false; | ||||
|   } | ||||
|   bool logging_topic = topic == this->log_message_.topic; | ||||
|   uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); | ||||
|   bool logging_topic = this->log_message_.topic == message.topic; | ||||
|   bool ret = this->mqtt_backend_.publish(message); | ||||
|   delay(0); | ||||
|   if (ret == 0 && !logging_topic && this->is_connected()) { | ||||
|   if (!ret && !logging_topic && this->is_connected()) { | ||||
|     delay(0); | ||||
|     ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length); | ||||
|     ret = this->mqtt_backend_.publish(message); | ||||
|     delay(0); | ||||
|   } | ||||
|  | ||||
|   if (!logging_topic) { | ||||
|     if (ret != 0) { | ||||
|       ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain); | ||||
|     if (ret) { | ||||
|       ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(), | ||||
|                message.retain); | ||||
|     } else { | ||||
|       ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(), | ||||
|                payload_length);  // NOLINT | ||||
|       ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(), | ||||
|                message.payload.length()); | ||||
|       this->status_momentary_warning("publish", 1000); | ||||
|     } | ||||
|   } | ||||
|   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 retain) { | ||||
|   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::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); } | ||||
| 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); } | ||||
| 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_; } | ||||
| void MQTTClientComponent::disable_birth_message() { | ||||
|   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_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() { | ||||
|   if (!this->shutdown_message_.topic.empty()) { | ||||
| @@ -557,13 +564,13 @@ void MQTTClientComponent::on_shutdown() { | ||||
|     this->publish(this->shutdown_message_); | ||||
|     yield(); | ||||
|   } | ||||
|   this->mqtt_client_.disconnect(true); | ||||
|   this->mqtt_backend_.disconnect(); | ||||
| } | ||||
|  | ||||
| #if ASYNC_TCP_SSL_ENABLED | ||||
| void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) { | ||||
|   this->mqtt_client_.setSecure(true); | ||||
|   this->mqtt_client_.addServerFingerprint(fingerprint.data()); | ||||
|   this->mqtt_backend_.setSecure(true); | ||||
|   this->mqtt_backend_.addServerFingerprint(fingerprint.data()); | ||||
| } | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,11 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/components/json/json_util.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" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -22,14 +26,6 @@ namespace mqtt { | ||||
| 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)>; | ||||
|  | ||||
| /// 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. | ||||
| struct MQTTSubscription { | ||||
|   std::string topic; | ||||
| @@ -139,7 +135,10 @@ class MQTTClientComponent : public Component { | ||||
|    */ | ||||
|   void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint); | ||||
| #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(); | ||||
|  | ||||
|   /** 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. | ||||
|    */ | ||||
|   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 | ||||
|   const std::string &get_topic_prefix() const; | ||||
|  | ||||
| @@ -277,6 +276,7 @@ class MQTTClientComponent : public Component { | ||||
|       .prefix = "homeassistant", | ||||
|       .retain = true, | ||||
|       .clean = false, | ||||
|       .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, | ||||
|   }; | ||||
|   std::string topic_prefix_{}; | ||||
|   MQTTMessage log_message_; | ||||
| @@ -284,7 +284,12 @@ class MQTTClientComponent : public Component { | ||||
|   int log_level_{ESPHOME_LOG_LEVEL}; | ||||
|  | ||||
|   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}; | ||||
|   network::IPAddress ip_; | ||||
|   bool dns_resolved_{false}; | ||||
| @@ -293,7 +298,7 @@ class MQTTClientComponent : public Component { | ||||
|   uint32_t reboot_timeout_{300000}; | ||||
|   uint32_t connect_begin_; | ||||
|   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) | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from esphome import automation | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| CODEOWNERS = ["@jesserockz"] | ||||
| @@ -5,3 +6,7 @@ CODEOWNERS = ["@jesserockz"] | ||||
| nfc_ns = cg.esphome_ns.namespace("nfc") | ||||
|  | ||||
| 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.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger), | ||||
|                 cv.Optional(CONF_ABOVE): cv.float_, | ||||
|                 cv.Optional(CONF_BELOW): cv.float_, | ||||
|                 cv.Optional(CONF_ABOVE): cv.templatable(cv.float_), | ||||
|                 cv.Optional(CONF_BELOW): cv.templatable(cv.float_), | ||||
|             }, | ||||
|             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(); | ||||
|     }); | ||||
|  | ||||
|     // Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised. | ||||
|     delay(300);  // NOLINT | ||||
|     App.setup(); | ||||
|  | ||||
|     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 = pn532_ns.class_("PN532", cg.PollingComponent) | ||||
|  | ||||
| PN532OnTagTrigger = pn532_ns.class_( | ||||
|     "PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag) | ||||
| ) | ||||
| PN532OnFinishedWriteTrigger = pn532_ns.class_( | ||||
|     "PN532OnFinishedWriteTrigger", automation.Trigger.template() | ||||
| ) | ||||
| @@ -30,7 +27,7 @@ PN532_SCHEMA = cv.Schema( | ||||
|         cv.GenerateID(): cv.declare_id(PN532), | ||||
|         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( | ||||
| @@ -42,7 +39,7 @@ PN532_SCHEMA = cv.Schema( | ||||
|         ), | ||||
|         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()) { | ||||
|     bool same_uid = false; | ||||
|     bool same_uid = true; | ||||
|     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) | ||||
|       return; | ||||
|   } | ||||
| @@ -376,9 +376,6 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) { | ||||
|   this->found_ = 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 esphome | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #include "esphome/components/nfc/nfc_tag.h" | ||||
| #include "esphome/components/nfc/nfc.h" | ||||
| #include "esphome/components/nfc/automation.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace pn532 { | ||||
| @@ -16,7 +17,6 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; | ||||
| static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; | ||||
|  | ||||
| class PN532BinarySensor; | ||||
| class PN532OnTagTrigger; | ||||
|  | ||||
| class PN532 : public PollingComponent { | ||||
|  public: | ||||
| @@ -30,8 +30,8 @@ class PN532 : public PollingComponent { | ||||
|   void loop() override; | ||||
|  | ||||
|   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_ontagremoved_trigger(PN532OnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } | ||||
|   void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.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) { | ||||
|     this->on_finished_write_callback_.add(std::move(callback)); | ||||
| @@ -79,8 +79,8 @@ class PN532 : public PollingComponent { | ||||
|  | ||||
|   bool requested_read_{false}; | ||||
|   std::vector<PN532BinarySensor *> binary_sensors_; | ||||
|   std::vector<PN532OnTagTrigger *> triggers_ontag_; | ||||
|   std::vector<PN532OnTagTrigger *> triggers_ontagremoved_; | ||||
|   std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_; | ||||
|   std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_; | ||||
|   std::vector<uint8_t> current_uid_; | ||||
|   nfc::NdefMessage *next_task_message_to_write_; | ||||
|   enum NfcTask { | ||||
| @@ -115,11 +115,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor { | ||||
|   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<> { | ||||
|  public: | ||||
|   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 | ||||
|  * 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 { | ||||
|  | ||||
| class RC522Spi : public rc522::RC522, | ||||
|   | ||||
| @@ -40,6 +40,24 @@ namespace remote_base { | ||||
|  | ||||
| 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 | ||||
| static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU; | ||||
| 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 uint32_t MICROSECONDS_IN_SECONDS = 1000000UL; | ||||
| 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) { | ||||
|   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; | ||||
|   std::vector<uint16_t> data; | ||||
|   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! | ||||
|     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); | ||||
| } | ||||
|  | ||||
| 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()); } | ||||
|  | ||||
|   | ||||
| @@ -6,10 +6,12 @@ | ||||
| namespace esphome { | ||||
| namespace remote_base { | ||||
|  | ||||
| std::vector<uint16_t> encode_pronto(const std::string &str); | ||||
|  | ||||
| struct ProntoData { | ||||
|   std::string data; | ||||
|  | ||||
|   bool operator==(const ProntoData &rhs) const { return data == rhs.data; } | ||||
|   bool operator==(const ProntoData &rhs) const; | ||||
| }; | ||||
|  | ||||
| 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::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: | ||||
|   void encode(RemoteTransmitData *dst, const ProntoData &data) override; | ||||
|   optional<ProntoData> decode(RemoteReceiveData src) override; | ||||
|   | ||||
| @@ -33,14 +33,8 @@ void SCD30Component::setup() { | ||||
| #endif | ||||
|  | ||||
|   /// 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]; | ||||
|  | ||||
|   if (!this->read_data_(raw_firmware_version, 3)) { | ||||
|   if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) { | ||||
|     this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED; | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
| @@ -49,7 +43,7 @@ void SCD30Component::setup() { | ||||
|            uint16_t(raw_firmware_version[0] & 0xFF)); | ||||
|  | ||||
|   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."); | ||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|       this->mark_failed(); | ||||
| @@ -69,7 +63,7 @@ void SCD30Component::setup() { | ||||
|   delay(30); | ||||
| #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."); | ||||
|     this->error_code_ = MEASUREMENT_INIT_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 | ||||
|   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."); | ||||
|       this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|       this->mark_failed(); | ||||
| @@ -92,7 +86,7 @@ void SCD30Component::setup() { | ||||
|   delay(30); | ||||
| #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."); | ||||
|     this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|     this->mark_failed(); | ||||
| @@ -103,7 +97,7 @@ void SCD30Component::setup() { | ||||
| #endif | ||||
|  | ||||
|   /// 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."); | ||||
|     this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|     this->mark_failed(); | ||||
| @@ -151,14 +145,14 @@ void SCD30Component::dump_config() { | ||||
| } | ||||
|  | ||||
| void SCD30Component::update() { | ||||
|   uint16_t raw_read_status[1]; | ||||
|   if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { | ||||
|   uint16_t raw_read_status; | ||||
|   if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGW(TAG, "Data not ready yet!"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) { | ||||
|   if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) { | ||||
|     ESP_LOGW(TAG, "Error reading measurement!"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
| @@ -166,7 +160,7 @@ void SCD30Component::update() { | ||||
|  | ||||
|   this->set_timeout(50, [this]() { | ||||
|     uint16_t raw_data[6]; | ||||
|     if (!this->read_data_(raw_data, 6)) { | ||||
|     if (!this->read_data(raw_data, 6)) { | ||||
|       this->status_set_warning(); | ||||
|       return; | ||||
|     } | ||||
| @@ -197,77 +191,16 @@ void SCD30Component::update() { | ||||
| } | ||||
|  | ||||
| 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; | ||||
|   } | ||||
|   delay(4); | ||||
|   uint16_t is_data_ready; | ||||
|   if (!this->read_data_(&is_data_ready, 1)) { | ||||
|   if (!this->read_data(&is_data_ready, 1)) { | ||||
|     return false; | ||||
|   } | ||||
|   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 esphome | ||||
|   | ||||
| @@ -2,13 +2,13 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace scd30 { | ||||
|  | ||||
| /// 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: | ||||
|   void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } | ||||
|   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; } | ||||
|  | ||||
|  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_(); | ||||
|  | ||||
|   enum ErrorCode { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ from esphome import core | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.components import sensirion_common | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_HUMIDITY, | ||||
| @@ -18,9 +19,12 @@ from esphome.const import ( | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensirion_common"] | ||||
|  | ||||
| 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_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||
|   | ||||
| @@ -25,15 +25,8 @@ void SCD4XComponent::setup() { | ||||
|  | ||||
|   // the sensor needs 1000 ms to enter the idle state | ||||
|   this->set_timeout(1000, [this]() { | ||||
|     // Check if measurement is ready before reading the value | ||||
|     if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_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)) { | ||||
|     uint16_t raw_read_status; | ||||
|     if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) { | ||||
|       ESP_LOGE(TAG, "Failed to read data ready status"); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
| @@ -41,9 +34,9 @@ void SCD4XComponent::setup() { | ||||
|  | ||||
|     uint32_t stop_measurement_delay = 0; | ||||
|     // 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"); | ||||
|       if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) { | ||||
|       if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) { | ||||
|         ESP_LOGE(TAG, "Failed to stop measurements"); | ||||
|         this->mark_failed(); | ||||
|         return; | ||||
| @@ -53,15 +46,8 @@ void SCD4XComponent::setup() { | ||||
|       stop_measurement_delay = 500; | ||||
|     } | ||||
|     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]; | ||||
|       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"); | ||||
|         this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_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), | ||||
|                uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8)); | ||||
|  | ||||
|       if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET, | ||||
|                                 (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { | ||||
|       if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET, | ||||
|                                (uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { | ||||
|         ESP_LOGE(TAG, "Error setting temperature offset."); | ||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|         this->mark_failed(); | ||||
| @@ -88,7 +74,7 @@ void SCD4XComponent::setup() { | ||||
|           return; | ||||
|         } | ||||
|       } 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."); | ||||
|           this->error_code_ = MEASUREMENT_INIT_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."); | ||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|         this->mark_failed(); | ||||
| @@ -104,7 +90,7 @@ void SCD4XComponent::setup() { | ||||
|       } | ||||
|  | ||||
|       // 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."); | ||||
|         this->error_code_ = MEASUREMENT_INIT_FAILED; | ||||
|         this->mark_failed(); | ||||
| @@ -164,19 +150,19 @@ void SCD4XComponent::update() { | ||||
|   } | ||||
|  | ||||
|   // 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(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint16_t raw_read_status[1]; | ||||
|   if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) { | ||||
|   uint16_t raw_read_status; | ||||
|   if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGW(TAG, "Data not ready yet!"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) { | ||||
|   if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { | ||||
|     ESP_LOGW(TAG, "Error reading measurement!"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
| @@ -184,7 +170,7 @@ void SCD4XComponent::update() { | ||||
|  | ||||
|   // Read off sensor data | ||||
|   uint16_t raw_data[3]; | ||||
|   if (!this->read_data_(raw_data, 3)) { | ||||
|   if (!this->read_data(raw_data, 3)) { | ||||
|     this->status_set_warning(); | ||||
|     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) { | ||||
|   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); | ||||
|     return true; | ||||
|   } 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 esphome | ||||
|   | ||||
| @@ -2,14 +2,14 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace scd4x { | ||||
|  | ||||
| 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: | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|   void setup() override; | ||||
| @@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice { | ||||
|   void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } | ||||
|  | ||||
|  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); | ||||
|  | ||||
|   ERRORCODE error_code_; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
|  | ||||
| from esphome.components import sensirion_common | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_CO2, | ||||
| @@ -21,9 +21,12 @@ from esphome.const import ( | ||||
|  | ||||
| CODEOWNERS = ["@sjtrny"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensirion_common"] | ||||
|  | ||||
| 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_ALTITUDE_COMPENSATION = "altitude_compensation" | ||||
|   | ||||
| @@ -7,55 +7,50 @@ namespace esphome { | ||||
| namespace sdp3x { | ||||
|  | ||||
| static const char *const TAG = "sdp3x.sensor"; | ||||
| static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06}; | ||||
| static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C}; | ||||
| static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; | ||||
| static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15}; | ||||
| static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03}; | ||||
| static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9}; | ||||
| static const uint16_t SDP3X_SOFT_RESET = 0x0006; | ||||
| static const uint16_t SDP3X_READ_ID1 = 0x367C; | ||||
| static const uint16_t SDP3X_READ_ID2 = 0xE102; | ||||
| static const uint16_t SDP3X_START_DP_AVG = 0x3615; | ||||
| static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603; | ||||
| static const uint16_t SDP3X_STOP_MEAS = 0x3FF9; | ||||
|  | ||||
| void SDP3XComponent::update() { this->read_pressure_(); } | ||||
|  | ||||
| void SDP3XComponent::setup() { | ||||
|   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 | ||||
|   } | ||||
|  | ||||
|   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 | ||||
|   } | ||||
|  | ||||
|   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!"); | ||||
|       this->mark_failed(); | ||||
|       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!"); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     uint8_t data[18]; | ||||
|     if (this->read(data, 18) != i2c::ERROR_OK) { | ||||
|     uint16_t data[6]; | ||||
|     if (this->read_data(data, 6) != i2c::ERROR_OK) { | ||||
|       ESP_LOGE(TAG, "Read ID SDP3X failed!"); | ||||
|       this->mark_failed(); | ||||
|       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 | ||||
|     // ref: | ||||
|     // 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) { | ||||
|       switch (data[3]) { | ||||
|     if (data[1] >> 8 == 0x02) { | ||||
|       switch (data[1] & 0xFF) { | ||||
|         case 0x01:  // SDP800-500Pa | ||||
|           ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa"); | ||||
|           break; | ||||
| @@ -75,15 +70,16 @@ void SDP3XComponent::setup() { | ||||
|           ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa"); | ||||
|           break; | ||||
|       } | ||||
|     } else if (data[2] == 0x01) { | ||||
|       if (data[3] == 0x01) { | ||||
|     } else if (data[1] >> 8 == 0x01) { | ||||
|       if ((data[1] & 0xFF) == 0x01) { | ||||
|         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"); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     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!"); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
| @@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() { | ||||
| } | ||||
|  | ||||
| void SDP3XComponent::read_pressure_() { | ||||
|   uint8_t data[9]; | ||||
|   if (this->read(data, 9) != i2c::ERROR_OK) { | ||||
|   uint16_t data[3]; | ||||
|   if (this->read_data(data, 3) != i2c::ERROR_OK) { | ||||
|     ESP_LOGW(TAG, "Couldn't read SDP3X data!"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) { | ||||
|     ESP_LOGW(TAG, "Invalid SDP3X data!"); | ||||
|     this->status_set_warning(); | ||||
|     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]); | ||||
|   int16_t pressure_raw = data[0]; | ||||
|   int16_t temperature_raw = data[1]; | ||||
|   int16_t scale_factor_raw = data[2]; | ||||
|   // scale factor is in Pa - convert to hPa | ||||
|   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, | ||||
| @@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() { | ||||
|  | ||||
| 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 esphome | ||||
|   | ||||
| @@ -2,14 +2,14 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
| #include "esphome/components/sensirion_common/i2c_sensirion.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sdp3x { | ||||
|  | ||||
| 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: | ||||
|   /// Schedule temperature+pressure readings. | ||||
|   void update() override; | ||||
| @@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se | ||||
|  protected: | ||||
|   /// Internal method to read the pressure from the component after it has been scheduled. | ||||
|   void read_pressure_(); | ||||
|  | ||||
|   bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); | ||||
|   MeasurementMode measurement_mode_; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.components import sensirion_common | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_PRESSURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
| @@ -8,10 +9,13 @@ from esphome.const import ( | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensirion_common"] | ||||
| CODEOWNERS = ["@Azimath"] | ||||
|  | ||||
| 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") | ||||
|   | ||||
							
								
								
									
										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