Merge branch 'beta' into bump-2022.4.0

This commit is contained in:
Jesse Hills 2022-04-20 17:06:08 +12:00
commit db7d946e1b
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
160 changed files with 7204 additions and 1171 deletions

View File

@ -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]

View File

@ -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

View File

@ -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 \

View File

@ -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)

View File

@ -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();

View File

@ -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);

View File

@ -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) {

View File

@ -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;

View File

@ -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]

View File

@ -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];

View File

@ -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_{};

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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();

View File

@ -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...> {

View File

@ -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(

View File

@ -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:

View File

@ -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

View File

@ -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 &param) {

View File

@ -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_; }

View File

@ -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}")

View File

@ -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() {

View File

@ -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

View File

@ -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))

View File

@ -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")

View File

@ -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

View File

@ -20,6 +20,7 @@ enum Protocol {
PROTOCOL_DAIKIN_ARC417,
PROTOCOL_DAIKIN_ARC480,
PROTOCOL_DAIKIN,
PROTOCOL_ELECTROLUXYAL,
PROTOCOL_FUEGO,
PROTOCOL_FUJITSU_AWYZ,
PROTOCOL_GREE,

View File

@ -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

View File

@ -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_);

View File

@ -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_);

View File

@ -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);

View File

@ -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]))

View File

@ -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)

View File

@ -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)

View File

@ -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)

View 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
)

View 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))

View 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

View 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

View 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))

View File

@ -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); }

View File

@ -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_() {

View File

@ -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) {

View File

@ -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; }

View File

@ -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_) {

View File

@ -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; }

View File

@ -23,13 +23,13 @@ std::string build_json(const json_build_t &f) {
#ifdef USE_ESP8266
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);
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#endif
const size_t request_size = std::min(free_heap - 2048, (size_t) 5120);
const size_t request_size = std::min(free_heap, (size_t) 512);
DynamicJsonDocument json_document(request_size);
if (json_document.memoryPool().buffer() == nullptr) {
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 "{}";
@ -37,7 +37,7 @@ std::string build_json(const json_build_t &f) {
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;
@ -51,13 +51,13 @@ void parse_json(const std::string &data, const json_parse_t &f) {
#ifdef USE_ESP8266
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);
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 - 2048, (size_t)(data.size() * 1.5));
size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5));
do {
DynamicJsonDocument json_document(request_size);
if (json_document.memoryPool().buffer() == nullptr) {
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;

View File

@ -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]))

View File

@ -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

View File

@ -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

View File

@ -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,
)
)

View File

@ -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);

View File

@ -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"))

View File

@ -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,
}

View File

@ -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(),

View File

@ -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.

View File

@ -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); }

View File

@ -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)

View File

@ -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]))

View 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

View 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)

View 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

View 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

View File

@ -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

View File

@ -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)

View File

@ -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)
)

View 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

View 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

View File

@ -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),
),

View File

@ -474,7 +474,7 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_
});
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
delay(100); // NOLINT
delay(300); // NOLINT
App.setup();
ESP_LOGI(TAG, "Waiting for OTA attempt.");

View File

@ -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),
}
),
}

View File

@ -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

View File

@ -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) {

View File

@ -0,0 +1 @@
CODEOWNERS = ["@andrewpc"]

View 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

View 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

View 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]))

View File

@ -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,

View File

@ -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()); }

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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"

View File

@ -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

View File

@ -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_;

View File

@ -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"

View File

@ -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

View File

@ -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_;
};

View File

@ -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")

View 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)

View File

@ -0,0 +1,128 @@
#include "i2c_sensirion.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome {
namespace sensirion_common {
static const char *const TAG = "sensirion_i2c";
// To avoid memory allocations for small writes a stack buffer is used
static const size_t BUFFER_STACK_SIZE = 16;
bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) {
const uint8_t num_bytes = len * 3;
std::vector<uint8_t> buf(num_bytes);
last_error_ = this->read(buf.data(), num_bytes);
if (last_error_ != i2c::ERROR_OK) {
return false;
}
for (uint8_t i = 0; i < len; i++) {
const uint8_t j = 3 * i;
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
if (crc != buf[j + 2]) {
ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc);
last_error_ = i2c::ERROR_CRC;
return false;
}
data[i] = encode_uint16(buf[j], buf[j + 1]);
}
return true;
}
/***
* write command with parameters and insert crc
* use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters
*/
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
uint8_t data_len) {
uint8_t temp_stack[BUFFER_STACK_SIZE];
std::unique_ptr<uint8_t[]> temp_heap;
uint8_t *temp;
size_t required_buffer_len = data_len * 3 + 2;
// Is a dynamic allocation required ?
if (required_buffer_len >= BUFFER_STACK_SIZE) {
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
temp = temp_heap.get();
} else {
temp = temp_stack;
}
// First byte or word is the command
uint8_t raw_idx = 0;
if (command_len == 1) {
temp[raw_idx++] = command & 0xFF;
} else {
// command is 2 bytes
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = command >> 8;
temp[raw_idx++] = command & 0xFF;
#else
temp[raw_idx++] = command & 0xFF;
temp[raw_idx++] = command >> 8;
#endif
}
// add parameters folllowed by crc
// skipped if len == 0
for (size_t i = 0; i < data_len; i++) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
temp[raw_idx++] = data[i] >> 8;
temp[raw_idx++] = data[i] & 0xFF;
#else
temp[raw_idx++] = data[i] & 0xFF;
temp[raw_idx++] = data[i] >> 8;
#endif
temp[raw_idx++] = sht_crc_(data[i]);
}
last_error_ = this->write(temp, raw_idx);
return last_error_ == i2c::ERROR_OK;
}
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len,
uint8_t delay_ms) {
if (!this->write_command_(reg, command_len, nullptr, 0)) {
ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
return false;
}
delay(delay_ms);
bool result = this->read_data(data, len);
if (!result) {
ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_);
}
return result;
}
// The 8-bit CRC checksum is transmitted after each data word
uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) {
uint8_t bit;
uint8_t crc = 0xFF;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data >> 8;
#else
crc ^= data & 0xFF;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
crc ^= data & 0xFF;
#else
crc ^= data >> 8;
#endif
for (bit = 8; bit > 0; --bit) {
if (crc & 0x80) {
crc = (crc << 1) ^ crc_polynomial_;
} else {
crc = (crc << 1);
}
}
return crc;
}
} // namespace sensirion_common
} // namespace esphome

View File

@ -0,0 +1,155 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace sensirion_common {
/**
* Implementation of a i2c functions for Sensirion sensors
* Sensirion data requires crc checking.
* Each 16 bit word is/must be followed 8 bit CRC code
* (Applies to read and write - note the i2c command code doesn't need a CRC)
* Format:
* | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | ..
*/
class SensirionI2CDevice : public i2c::I2CDevice {
public:
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
/** Read data words from i2c device.
* handles crc check used by Sensirion sensors
* @param data pointer to raw result
* @param len number of words to read
* @return true if reading succeded
*/
bool read_data(uint16_t *data, uint8_t len);
/** Read 1 data word from i2c device.
* @param data reference to raw result
* @return true if reading succeded
*/
bool read_data(uint16_t &data) { return this->read_data(&data, 1); }
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(command, ADDR_16_BIT, data, len, delay);
}
/** Read 1 data word from 16 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay);
}
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) {
return get_register_(i2c_register, ADDR_8_BIT, data, len, delay);
}
/** Read 1 data word from 8 bit i2c register.
* @param i2c register
* @param data reference to raw result
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) {
return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay);
}
/** Write a command to the i2c device.
* @param command i2c command to send
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); }
/** Write a command and one data word to the i2c device .
* @param command i2c command to send
* @param data argument for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); }
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data vector<uint16> arguments for the i2c command
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const std::vector<uint16_t> &data) {
return write_command_(i2c_register, sizeof(T), data.data(), data.size());
}
/** Write a command with arguments as words
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
* @param data arguments for the i2c command
* @param len number of arguments (words)
* @return true if reading succeded
*/
template<class T> bool write_command(T i2c_register, const uint16_t *data, uint8_t len) {
// limit to 8 or 16 bit only
static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2,
"only 8 or 16 bit command types are supported.");
return write_command_(i2c_register, CommandLen(sizeof(T)), data, len);
}
protected:
uint8_t crc_polynomial_{0x31u}; // default for sensirion
/** Write a command with arguments as words
* @param command i2c command to send can be uint8_t or uint16_t
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data arguments for the i2c command
* @param data_len number of arguments (words)
* @return true if reading succeded
*/
bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len);
/** get data words from i2c register.
* handles crc check used by Sensirion sensors
* @param i2c register
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
* @param data pointer to raw result
* @param len number of words to read
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
* @return true if reading succeded
*/
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data data word for which the crc8 checksum is calculated
* @param len number of arguments (words)
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint16_t data);
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
* @param command i2c command to send
* @param data1 high byte of data word
* @param data2 low byte of data word
* @return 8 Bit CRC
*/
uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); }
/** last error code from i2c operation
*/
i2c::ErrorCode last_error_;
};
} // namespace sensirion_common
} // namespace esphome

View File

@ -212,8 +212,8 @@ SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
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),
),

View File

@ -1,10 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_ID,
CONF_BASELINE,
CONF_ECO2,
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
CONF_TVOC,
ICON_RADIATOR,
DEVICE_CLASS_CARBON_DIOXIDE,
@ -17,17 +20,19 @@ from esphome.const import (
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
sgp30_ns = cg.esphome_ns.namespace("sgp30")
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
SGP30Component = sgp30_ns.class_(
"SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
)
CONF_ECO2_BASELINE = "eco2_baseline"
CONF_TVOC_BASELINE = "tvoc_baseline"
CONF_STORE_BASELINE = "store_baseline"
CONF_UPTIME = "uptime"
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONFIG_SCHEMA = (
cv.Schema(

View File

@ -36,14 +36,8 @@ void SGP30Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
// Serial Number identification
if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) {
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(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) {
this->mark_failed();
return;
}
@ -52,16 +46,12 @@ void SGP30Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// Featureset identification for future use
if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) {
uint16_t raw_featureset;
if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) {
this->mark_failed();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if (uint16_t(this->featureset_ >> 12) != 0x0) {
if (uint16_t(this->featureset_ >> 12) == 0x1) {
// ID matching a different sensor: SGPC3
@ -76,7 +66,7 @@ void SGP30Component::setup() {
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
// Sensor initialization
if (!this->write_command_(SGP30_CMD_IAQ_INIT)) {
if (!this->write_command(SGP30_CMD_IAQ_INIT)) {
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
this->error_code_ = MEASUREMENT_INIT_FAILED;
this->mark_failed();
@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() {
void SGP30Component::read_iaq_baseline_() {
if (this->is_sensor_baseline_reliable_()) {
if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) {
if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) {
ESP_LOGD(TAG, "Error getting baseline");
this->status_set_warning();
return;
}
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@ -274,14 +264,14 @@ void SGP30Component::dump_config() {
}
void SGP30Component::update() {
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) {
this->status_set_warning();
return;
}
this->seconds_since_last_store_ += this->update_interval_ / 1000;
this->set_timeout(50, [this]() {
uint16_t raw_data[2];
if (!this->read_data_(raw_data, 2)) {
if (!this->read_data(raw_data, 2)) {
this->status_set_warning();
return;
}
@ -305,56 +295,5 @@ void SGP30Component::update() {
});
}
bool SGP30Component::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);
}
uint8_t SGP30Component::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 SGP30Component::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 sgp30
} // namespace esphome

View File

@ -2,7 +2,7 @@
#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"
#include "esphome/core/preferences.h"
#include <cmath>
@ -15,7 +15,7 @@ struct SGP30Baselines {
} PACKED;
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
public:
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
void send_env_data_();
void read_iaq_baseline_();
bool is_sensor_baseline_reliable_();
void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_;
uint16_t featureset_;
uint32_t required_warm_up_time_;

View File

@ -1,25 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.components import i2c, sensor, sensirion_common
from esphome.const import (
CONF_STORE_BASELINE,
CONF_TEMPERATURE_SOURCE,
ICON_RADIATOR,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
STATE_CLASS_MEASUREMENT,
)
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["sensirion_common"]
CODEOWNERS = ["@SenexCrenshaw"]
sgp40_ns = cg.esphome_ns.namespace("sgp40")
SGP40Component = sgp40_ns.class_(
"SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
"SGP40Component",
sensor.Sensor,
cg.PollingComponent,
sensirion_common.SensirionI2CDevice,
)
CONF_COMPENSATION = "compensation"
CONF_HUMIDITY_SOURCE = "humidity_source"
CONF_TEMPERATURE_SOURCE = "temperature_source"
CONF_STORE_BASELINE = "store_baseline"
CONF_VOC_BASELINE = "voc_baseline"
CONFIG_SCHEMA = (

View File

@ -12,14 +12,14 @@ void SGP40Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
// Serial Number identification
if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) {
if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) {
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->read_data(raw_serial_number, 3)) {
this->mark_failed();
return;
}
@ -28,19 +28,19 @@ void SGP40Component::setup() {
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
// Featureset identification for future use
if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) {
if (!this->write_command(SGP40_CMD_GET_FEATURESET)) {
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
this->mark_failed();
return;
}
uint16_t raw_featureset[1];
if (!this->read_data_(raw_featureset, 1)) {
uint16_t raw_featureset;
if (!this->read_data(raw_featureset)) {
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
this->mark_failed();
return;
}
this->featureset_ = raw_featureset[0];
this->featureset_ = raw_featureset;
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
SGP40_FEATURESET);
@ -95,21 +95,21 @@ void SGP40Component::setup() {
void SGP40Component::self_test_() {
ESP_LOGD(TAG, "Self-test started");
if (!this->write_command_(SGP40_CMD_SELF_TEST)) {
if (!this->write_command(SGP40_CMD_SELF_TEST)) {
this->error_code_ = COMMUNICATION_FAILED;
ESP_LOGD(TAG, "Self-test communication failed");
this->mark_failed();
}
this->set_timeout(250, [this]() {
uint16_t reply[1];
if (!this->read_data_(reply, 1)) {
uint16_t reply;
if (!this->read_data(reply)) {
ESP_LOGD(TAG, "Self-test read_data_ failed");
this->mark_failed();
return;
}
if (reply[0] == 0xD400) {
if (reply == 0xD400) {
this->self_test_complete_ = true;
ESP_LOGD(TAG, "Self-test completed");
return;
@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() {
temperature = 25;
}
uint8_t command[8];
command[0] = 0x26;
command[1] = 0x0F;
uint16_t data[2];
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
command[2] = rhticks >> 8;
command[3] = rhticks & 0xFF;
command[4] = generate_crc_(command + 2, 2);
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
command[5] = tempticks >> 8;
command[6] = tempticks & 0xFF;
command[7] = generate_crc_(command + 5, 2);
// first paramater is the relative humidity ticks
data[0] = rhticks;
// second paramater is the temperature ticks
data[1] = tempticks;
if (this->write(command, 8) != i2c::ERROR_OK) {
if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) {
this->status_set_warning();
ESP_LOGD(TAG, "write error");
return UINT16_MAX;
ESP_LOGD(TAG, "write error (%d)", this->last_error_);
return false;
}
delay(30);
uint16_t raw_data[1];
if (!this->read_data_(raw_data, 1)) {
uint16_t raw_data;
if (!this->read_data(raw_data)) {
this->status_set_warning();
ESP_LOGD(TAG, "read_data_ error");
return UINT16_MAX;
}
return raw_data[0];
}
uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) {
// calculates 8-Bit checksum with given polynomial
uint8_t crc = SGP40_CRC8_INIT;
for (uint8_t i = 0; i < datalen; i++) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; b++) {
if (crc & 0x80) {
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
} else {
crc <<= 1;
}
}
}
return crc;
return raw_data;
}
void SGP40Component::update_voc_index() {
@ -293,56 +270,5 @@ void SGP40Component::dump_config() {
}
}
bool SGP40Component::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);
}
uint8_t SGP40Component::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 SGP40Component::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 sgp40
} // namespace esphome

View File

@ -2,7 +2,7 @@
#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"
#include "esphome/core/application.h"
#include "esphome/core/preferences.h"
#include "sensirion_voc_algorithm.h"
@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word
static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682;
static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f;
static const uint16_t SGP40_CMD_SELF_TEST = 0x280e;
static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F;
// Shortest time interval of 3H for storing baseline values.
// Prevents wear of the flash because of too many write operations
@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50;
class SGP40Component;
/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors.
class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice {
class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice {
public:
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2
/// Input sensor for humidity and temperature compensation.
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
bool write_command_(uint16_t command);
bool read_data_(uint16_t *data, uint8_t len);
int16_t sensirion_init_sensors_();
int16_t sgp40_probe_();
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
uint64_t serial_number_;
uint16_t featureset_;
int32_t measure_voc_index_();

Some files were not shown because too many files have changed in this diff Show More