mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			88 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					993044c870 | ||
| 
						 | 
					a8c1b63edb | ||
| 
						 | 
					db7d946e1b | ||
| 
						 | 
					41d9059a2f | ||
| 
						 | 
					e26e0d7c01 | ||
| 
						 | 
					ad41c07a1f | ||
| 
						 | 
					5732f3b044 | ||
| 
						 | 
					712115b6ce | ||
| 
						 | 
					9283559c6b | ||
| 
						 | 
					6b393438e9 | ||
| 
						 | 
					343b9ab455 | ||
| 
						 | 
					dcb226b202 | ||
| 
						 | 
					2243021b58 | ||
| 
						 | 
					d5134e88b1 | ||
| 
						 | 
					c59adf612f | ||
| 
						 | 
					a82d8ea0c3 | ||
| 
						 | 
					ad57faa9a9 | ||
| 
						 | 
					a9b5e8d036 | ||
| 
						 | 
					8be704e591 | ||
| 
						 | 
					b622a8fa58 | ||
| 
						 | 
					a519e5c475 | ||
| 
						 | 
					d620b6dd5e | ||
| 
						 | 
					99335d986e | ||
| 
						 | 
					7895cd92cd | ||
| 
						 | 
					8b2c032da6 | ||
| 
						 | 
					da336247eb | ||
| 
						 | 
					dabd27d4be | ||
| 
						 | 
					fdda47db6e | ||
| 
						 | 
					efa6fd03e5 | ||
| 
						 | 
					9e3e34acf5 | ||
| 
						 | 
					a2d0c1bf18 | ||
| 
						 | 
					7663716ae8 | ||
| 
						 | 
					c2cacb3478 | ||
| 
						 | 
					84666b54b9 | ||
| 
						 | 
					2b91c23bf3 | ||
| 
						 | 
					3297267a16 | ||
| 
						 | 
					a9e653724c | ||
| 
						 | 
					5e79a1f500 | ||
| 
						 | 
					d4ff98680a | ||
| 
						 | 
					ba8d255cb4 | ||
| 
						 | 
					06f4ad922c | ||
| 
						 | 
					bff06e448b | ||
| 
						 | 
					d48ffa2913 | ||
| 
						 | 
					d97c3a7e01 | ||
| 
						 | 
					0b1161f7ef | ||
| 
						 | 
					061e1a471d | ||
| 
						 | 
					a39d874600 | ||
| 
						 | 
					de96376565 | ||
| 
						 | 
					c54c20ab3c | ||
| 
						 | 
					70fafa473b | ||
| 
						 | 
					2e436eae6b | ||
| 
						 | 
					fd7e861ff5 | ||
| 
						 | 
					792108686c | ||
| 
						 | 
					fa1b5117fd | ||
| 
						 | 
					b0bd9e0a34 | ||
| 
						 | 
					05dc97099a | ||
| 
						 | 
					9de61fcf58 | ||
| 
						 | 
					fc7348d46d | ||
| 
						 | 
					8be2456c7e | ||
| 
						 | 
					bb5f7249a6 | ||
| 
						 | 
					7f7175b184 | ||
| 
						 | 
					cf5c640ae4 | ||
| 
						 | 
					6b9371d105 | ||
| 
						 | 
					9a82057303 | ||
| 
						 | 
					48584e94c4 | ||
| 
						 | 
					fc94a5d0ee | ||
| 
						 | 
					d8024a5928 | ||
| 
						 | 
					2034ab4f6c | ||
| 
						 | 
					24029cc918 | ||
| 
						 | 
					9a9d5964ee | ||
| 
						 | 
					4e4a512107 | ||
| 
						 | 
					0729ed538e | ||
| 
						 | 
					24b75b7ed6 | ||
| 
						 | 
					58b70b42dd | ||
| 
						 | 
					1496bc1b07 | ||
| 
						 | 
					bfbf88b2ea | ||
| 
						 | 
					e621b938e3 | ||
| 
						 | 
					0372d17a11 | ||
| 
						 | 
					4525588116 | ||
| 
						 | 
					68e957c147 | ||
| 
						 | 
					99f5ed1461 | ||
| 
						 | 
					59f67796dc | ||
| 
						 | 
					aafdfa933e | ||
| 
						 | 
					3208c8ed1e | ||
| 
						 | 
					6bf733e24e | ||
| 
						 | 
					65d3e8fbfc | ||
| 
						 | 
					a29d65d47c | ||
| 
						 | 
					0af1edefff | 
@@ -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,13 +170,16 @@ 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/shelly_dimmer/* @edge90 @rnauber
 | 
			
		||||
esphome/components/sht4x/* @sjtrny
 | 
			
		||||
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 +227,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};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,8 @@ CELL_VOLTAGE_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
    device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    icon=ICON_FLASH,
 | 
			
		||||
    accuracy_decimals=3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 
 | 
			
		||||
@@ -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...> {
 | 
			
		||||
 
 | 
			
		||||
@@ -143,37 +143,37 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional("power_delivered_l1"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_delivered_l2"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_delivered_l3"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l1"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l2"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l3"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -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,47 +16,73 @@ 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_8BIT);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  DynamicJsonDocument json_document(free_heap);
 | 
			
		||||
  const size_t request_size = std::min(free_heap, (size_t) 512);
 | 
			
		||||
 | 
			
		||||
  DynamicJsonDocument json_document(request_size);
 | 
			
		||||
  if (json_document.capacity() == 0) {
 | 
			
		||||
    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();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity());
 | 
			
		||||
  std::string output;
 | 
			
		||||
  serializeJson(json_document, output);
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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_8BIT);
 | 
			
		||||
#endif
 | 
			
		||||
  bool pass = false;
 | 
			
		||||
  size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5));
 | 
			
		||||
  do {
 | 
			
		||||
    DynamicJsonDocument json_document(request_size);
 | 
			
		||||
    if (json_document.capacity() == 0) {
 | 
			
		||||
      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)
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user