1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-08 11:01:50 +00:00

Compare commits

...

60 Commits

Author SHA1 Message Date
Jesse Hills
2232038e8d Alter improv_serial log levels 2021-11-26 13:01:33 +13:00
Maurice Makaay
ceb9b1d1ff Allow empty UART debug: option, logging in hex format by default (#2771)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
2021-11-25 11:51:56 +13:00
Martin
ccfa1e23f0 Add support for sdp8xx (#2779) 2021-11-25 11:28:19 +13:00
rsumner
290da8df2d Fix LEDC resolution calculation on ESP32-C3/S2/S3 (#2794)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-25 11:22:51 +13:00
Martin
4b1d73791d remove LEDC_HIGH_SPEED_MODE for C3, S2, S3 (#2791) 2021-11-25 08:06:08 +13:00
Jesse Hills
7e8012c1a0 Allow specifying the dashboard bind address (#2787) 2021-11-25 07:59:32 +13:00
Maurice Makaay
15cd602e8b Add support for P1 Data Request pin control (#2676) 2021-11-23 09:34:10 +01:00
krunkel
598f5b241f Remove unnecessary write in AHT10 update (#2675) 2021-11-23 09:26:16 +01:00
dependabot[bot]
335e69e6cd Bump black from 21.10b0 to 21.11b1 (#2760) 2021-11-23 09:24:28 +01:00
Paul Monigatti
05fe5db030 Relax the icon validator to allow non-mdi icons (#2764) 2021-11-23 09:21:14 +01:00
Andreas Hergert
710096b1c6 Fixed wrong setup of tc9548a (#2766) 2021-11-23 09:20:55 +01:00
Dave T
07b882c801 Fix distorted gif frames when resizing (#2774) 2021-11-23 09:20:36 +01:00
cvwillegen
3e5331a263 Prettier date time display after time sync (#2778) 2021-11-23 09:20:20 +01:00
Oxan van Leeuwen
897277992b Introduce str_snprintf helper function (#2780) 2021-11-23 20:30:49 +13:00
Samuel Sieb
1424091ee5 Remove floating point ops from the ISR (#2751)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2021-11-22 12:11:36 +13:00
Kamil Trzciński
61ec16cdfc esp32_camera_web_server: Improve support for MotionEye (#2777) 2021-11-22 12:09:11 +13:00
Dave T
e5cb5756aa Fix frame scaling for animated gifs (#2750) 2021-11-18 23:20:32 +01:00
Maurice Makaay
9e1c3e8f01 Allow UART debug configuration with no after: definition (#2753) 2021-11-18 22:41:26 +01:00
Martin
448e1690aa Add retry handler (#2721)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-18 11:59:40 +13:00
Martin
8267f01ccd Remove arduino dependency from hm3301 (#2745) 2021-11-18 08:03:46 +13:00
Sergey V. DUDANOV
6f9439e1bc Fix byte order in NEC protocol implementation (#2534) 2021-11-17 18:35:50 +01:00
spattinson
06994c0dfc Change LUT for ttgo t5 2.13inch to improve partial refresh (#2475) 2021-11-17 18:28:36 +01:00
Maurice Makaay
dee5d639e2 Add max_telegram_length option to dsmr (#2674)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-17 18:24:02 +01:00
Jesse Hills
df6730be55 Move to use improv lib from platformio (#2741) 2021-11-17 18:23:17 +01:00
Franck Nijhof
6c1ef398bb Re-instate device class update for binary sensors (#2743) 2021-11-17 23:28:31 +13:00
Evgeny
0469e19f54 Fix HM3301 AQI index calculator (#2739) 2021-11-17 09:52:40 +01:00
Jesse Hills
dbcfa7b599 Remove duplicated const data in esp8266 boards (#2740) 2021-11-17 16:22:38 +13:00
rotarykite
df68403b6d Fix senseair component uart read timeout (#2658)
Co-authored-by: DAVe3283 <DAVe3283+GitHub@gmail.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Chua Jun Chieh <junchieh.chua@softspace.com.my>
2021-11-17 07:57:03 +13:00
Ryan Hoffman
57bdc2b885 Add ble_client binary_output (#2200)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-17 07:30:42 +13:00
Ryan Hoffman
f565ff5def Use as_reversed_hex_array in ble_sensor to fix UUID parsing (#2737)
#1627 renamed as_hex_array to as_reversed_hex_array but forgot to rename these users.
2021-11-16 18:53:36 +01:00
H. Árkosi Róbert
8ece639987 Change log level from DEBUG to INFO for sniffing services (#2736)
Sniffing for codes only happens if the user deliberately asked for it with the related service through HA - to find out the codes present in the air. The resulted data shouldn't be printed out only in debug mode, as this is information required to be known on demand for later use, not actually a debug info. Changing log level from DEBUG to INFO for sniffing services has two benefits:
- no need to run firmware with DEBUG enabled for occasional sniffing with devices in production (no need to flash back and forth with different log levels set just for this reason)
- if the user still wants DEBUG enabled, sniffed data appears in different color, it's easier to find between the lines.
2021-11-16 23:28:12 +13:00
Jan Harkes
b35f509784 Allow for subsecond sampling of hmc5883l (#2735) 2021-11-16 09:16:43 +01:00
Jesse Hills
f1954df573 Fix zeroconf time comparisons (#2733)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-11-16 12:47:06 +13:00
Jesse Hills
9e4fa5dcf1 Improv serial/checksum changes (#2731)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-11-16 11:02:45 +13:00
Jesse Hills
0809673ba9 Add zeroconf as a direct dependency and lock the version (#2729) 2021-11-16 09:53:52 +13:00
cvwillegen
b386284180 Ignore secrets.yaml on command line (#2715) 2021-11-15 20:06:55 +01:00
Krzysztof Białek
515519bc87 Provide an option to select MQTT unique_id generator (#2701)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-11-15 15:49:18 +01:00
Oxan van Leeuwen
5404163be0 Clean-up MAC address helpers (#2713) 2021-11-15 15:48:16 +01:00
Alexandre-Jacques St-Jacques
0b193eee43 Remove unnecessary duplicate touch_pad_filter_start (#2724) 2021-11-15 11:58:22 +13:00
Jesse Hills
7333123ba4 Fix indentation of write_lambda for modbus_controller number (#2722) 2021-11-15 10:59:48 +13:00
Sergey V. DUDANOV
d99c5ed890 RemoteTransmitter fix. Bug from version 2021.10. Some changes. (#2706) 2021-11-15 10:40:35 +13:00
Oxan van Leeuwen
04740fbcbb Install test requirements in lint Docker image (#2719) 2021-11-15 10:04:43 +13:00
Oxan van Leeuwen
6a7440f7d3 Feed WDT between doing ESP32 touchpad measurements (#2720) 2021-11-15 09:45:25 +13:00
Oxan van Leeuwen
14299bb2cc Drop unused constants from const.py (#2718) 2021-11-15 08:07:58 +13:00
Oxan van Leeuwen
66cebfc992 Restore InterruptLock on wifi-less ESP8266 (#2712) 2021-11-15 08:05:11 +13:00
Maurice Makaay
108b8e6705 Fix rom/rtc.h deprecation compile warning for debug component (#2520) 2021-11-14 16:17:13 +01:00
Clifford Roche
4eaa6afa4d Add greeyac protocol to IR Climate / HeatpumpIR (#2694) 2021-11-14 16:11:21 +01:00
Krzysztof Białek
f643a46bbf Allow setting custom command_topic for Select and Number components (#2714) 2021-11-14 14:59:34 +01:00
Sergey V. DUDANOV
aae63a7ff3 Add climate on_state trigger (#2707) 2021-11-13 15:42:15 +01:00
NeoAcheron
582567696e pmsx003: add support for PMS5003S device (#2710) 2021-11-13 15:14:23 +01:00
Jesse Hills
2e0c89409d Bump ESPAsyncWebServer to 2.1.0 (#2686) 2021-11-13 21:22:32 +13:00
lcavalli
7bb7456a8b Update device classes for binary sensors (#2703) 2021-11-12 13:17:10 +13:00
Jesse Hills
0372e12b81 Defines tidy (#2696)
* Move webserver defines inside arduino block

* Move esp8266 flash define

* Move prometheus define
2021-11-11 10:56:54 +01:00
Jesse Hills
a6873c1520 Only allow prometheus when using arduino (#2697) 2021-11-11 10:56:35 +01:00
Jesse Hills
f11220da3a Remove my.ha links from improv (#2695) 2021-11-11 15:15:37 +13:00
Oxan van Leeuwen
bb9793d5b7 Enable addressable light power supply based on raw values (#2690) 2021-11-11 11:53:25 +13:00
Maurice Makaay
e99af991ec Uart debugging support (#2478)
Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
Co-authored-by: Maurice Makaay <account-github@makaay.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-11-11 11:34:17 +13:00
Carlos Garcia Saura
abf3708cc2 [remote_transmitter] accurate pulse timing for ESP8266 (#2476) 2021-11-11 11:28:45 +13:00
Jesse Hills
4395d6156d Fix template number initial value being NaN (#2692) 2021-11-10 23:24:48 +01:00
Jesse Hills
04ba53c870 Bump version to 2021.12.0-dev 2021-11-11 10:10:05 +13:00
106 changed files with 1634 additions and 703 deletions

View File

@@ -72,7 +72,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/improv_serial/* @esphome/core esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz esphome/components/inkplate6/* @jesserockz

View File

@@ -147,9 +147,9 @@ RUN \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini /
RUN \ RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \
&& /platformio_install_deps.py /platformio.ini && /platformio_install_deps.py /platformio.ini
VOLUME ["/esphome"] VOLUME ["/esphome"]

View File

@@ -18,6 +18,7 @@ from esphome.const import (
CONF_PORT, CONF_PORT,
CONF_ESPHOME, CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent from esphome.helpers import indent
@@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [ flash_images = [
platformio_api.FlashImage( platformio_api.FlashImage(
path=idedata.firmware_bin_path, path=idedata.firmware_bin_path, offset=firmware_offset
offset=firmware_offset,
), ),
*idedata.extra_flash_images, *idedata.extra_flash_images,
] ]
@@ -607,10 +607,7 @@ def parse_args(argv):
"wizard", "wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.", help="A helpful setup wizard that will guide you through setting up ESPHome.",
) )
parser_wizard.add_argument( parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
"configuration",
help="Your YAML configuration file.",
)
parser_fingerprint = subparsers.add_parser( parser_fingerprint = subparsers.add_parser(
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
@@ -632,8 +629,7 @@ def parse_args(argv):
"dashboard", help="Create a simple web server for a dashboard." "dashboard", help="Create a simple web server for a dashboard."
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"configuration", "configuration", help="Your YAML configuration file directory."
help="Your YAML configuration file directory.",
) )
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--port", "--port",
@@ -641,6 +637,12 @@ def parse_args(argv):
type=int, type=int,
default=6052, default=6052,
) )
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument( parser_dashboard.add_argument(
"--username", "--username",
help="The optional username to require for authentication.", help="The optional username to require for authentication.",
@@ -789,6 +791,10 @@ def run_esphome(argv):
return 1 return 1
for conf_path in args.configuration: for conf_path in args.configuration:
if any(os.path.basename(conf_path) == x for x in SECRETS_FILES):
_LOGGER.warning("Skipping secrets file %s", conf_path)
continue
CORE.config_path = conf_path CORE.config_path = conf_path
CORE.dashboard = args.dashboard CORE.dashboard = args.dashboard

View File

@@ -73,13 +73,6 @@ void AHT10Component::update() {
bool success = false; bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) { for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
delayMicroseconds(4);
uint8_t reg = 0;
if (this->write(&reg, 1) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
continue;
}
delay(delay_ms); delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");

View File

@@ -44,8 +44,9 @@ async def to_code(config):
width, height = image.size width, height = image.size
frames = image.n_frames frames = image.n_frames
if CONF_RESIZE in config: if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE]) new_width_max, new_height_max = config[CONF_RESIZE]
width, height = image.size ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
else: else:
if width > 500 or height > 500: if width > 500 or height > 500:
_LOGGER.warning( _LOGGER.warning(
@@ -59,7 +60,13 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE) frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels: for pix in pixels:
data[pos] = pix data[pos] = pix
pos += 1 pos += 1
@@ -70,7 +77,13 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("RGB") frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
pixels = list(frame.getdata()) pixels = list(frame.getdata())
if len(pixels) != height * width:
raise core.EsphomeError(
f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})"
)
for pix in pixels: for pix in pixels:
data[pos] = pix[0] data[pos] = pix[0]
pos += 1 pos += 1
@@ -85,6 +98,8 @@ async def to_code(config):
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE) frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
if frame.getpixel((x, y)): if frame.getpixel((x, y)):

View File

@@ -44,9 +44,11 @@ from esphome.const import (
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY, DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND, DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
@@ -76,9 +78,11 @@ DEVICE_CLASSES = [
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE, DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY, DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE, DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND, DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE, DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION, DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,

View File

@@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
} }
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
auto client = this->service->client;
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
}
}
} // namespace ble_client } // namespace ble_client
} // namespace esphome } // namespace esphome

View File

@@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors(); void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid); BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
BLEService *service; BLEService *service;
}; };

View File

@@ -0,0 +1,67 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output, ble_client, esp32_ble_tracker
from esphome.const import CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
BLEBinaryOutput = ble_client_ns.class_(
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
)
CONFIG_SCHEMA = cv.All(
output.BINARY_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(ble_client.BLE_CLIENT_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_char_uuid16(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_char_uuid32(
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
)
)
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128))
yield output.register_output(var, config)
yield ble_client.register_ble_node(var, config)
yield cg.register_component(var, config)

View File

@@ -0,0 +1,71 @@
#include "ble_binary_output.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace ble_client {
static const char *const TAG = "ble_binary_output";
void BLEBinaryOutput::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
LOG_BINARY_OUTPUT(this);
}
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
switch (event) {
case ESP_GATTC_OPEN_EVT:
this->client_state_ = espbt::ClientState::ESTABLISHED;
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
this->client_state_ = espbt::ClientState::IDLE;
break;
case ESP_GATTC_WRITE_CHAR_EVT: {
if (param->write.status == 0) {
break;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
break;
}
if (param->write.handle == chr->handle) {
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
}
break;
}
default:
break;
}
}
void BLEBinaryOutput::write_state(bool state) {
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
if (chr == nullptr) {
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
this->char_uuid_.to_string().c_str());
return;
}
uint8_t state_as_uint = (uint8_t) state;
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
chr->write_value(&state_as_uint, sizeof(state_as_uint));
}
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/output/binary_output.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
namespace esphome {
namespace ble_client {
namespace espbt = esphome::esp32_ble_tracker;
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
public:
void dump_config() override;
void loop() override {}
float get_setup_priority() const override { return setup_priority::DATA; }
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
protected:
void write_state(bool state) override;
espbt::ESPBTUUID service_uuid_;
espbt::ESPBTUUID char_uuid_;
espbt::ClientState client_state_;
};
} // namespace ble_client
} // namespace esphome
#endif

View File

@@ -67,7 +67,7 @@ async def to_code(config):
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
) )
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_SERVICE_UUID]) uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
@@ -87,7 +87,9 @@ async def to_code(config):
elif len(config[CONF_CHARACTERISTIC_UUID]) == len( elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format esp32_ble_tracker.bt_uuid128_format
): ):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_CHARACTERISTIC_UUID]) uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if CONF_DESCRIPTOR_UUID in config:
@@ -108,7 +110,9 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len( elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format esp32_ble_tracker.bt_uuid128_format
): ):
uuid128 = esp32_ble_tracker.as_hex_array(config[CONF_DESCRIPTOR_UUID]) uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:

View File

@@ -20,6 +20,7 @@ from esphome.const import (
CONF_MODE, CONF_MODE,
CONF_MODE_COMMAND_TOPIC, CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC, CONF_MODE_STATE_TOPIC,
CONF_ON_STATE,
CONF_PRESET, CONF_PRESET,
CONF_SWING_MODE, CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC, CONF_SWING_MODE_COMMAND_TOPIC,
@@ -34,6 +35,7 @@ from esphome.const import (
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_MQTT_ID, CONF_MQTT_ID,
) )
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions # Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action) ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template())
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{ {
@@ -161,6 +164,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic cv.requires_component("mqtt"), cv.publish_topic
), ),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
} }
) )
@@ -256,6 +264,10 @@ async def setup_climate_core_(var, config):
) )
) )
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
async def register_climate(var, config): async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View File

@@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_; Climate *climate_;
}; };
class StateTrigger : public Trigger<> {
public:
StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); });
}
};
} // namespace climate } // namespace climate
} // namespace esphome } // namespace esphome

View File

@@ -4,20 +4,23 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
#ifdef USE_ESP32 #ifdef USE_ESP32
#if ESP_IDF_VERSION_MAJOR >= 4
#include <esp32/rom/rtc.h>
#else
#include <rom/rtc.h> #include <rom/rtc.h>
#include <esp_idf_version.h> #endif
#endif #endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <Esp.h> #include <Esp.h>
#endif #endif
#ifdef USE_ESP_IDF
#include <esp_heap_caps.h>
#include <esp_system.h>
#endif
namespace esphome { namespace esphome {
namespace debug { namespace debug {

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart from esphome.components import uart
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
@@ -11,10 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor"] AUTO_LOAD = ["sensor", "text_sensor"]
CONF_DSMR_ID = "dsmr_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_CRC_CHECK = "crc_check" CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_id"
CONF_GAS_MBUS_ID = "gas_mbus_id" CONF_GAS_MBUS_ID = "gas_mbus_id"
CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
CONF_REQUEST_INTERVAL = "request_interval"
CONF_REQUEST_PIN = "request_pin"
# Hack to prevent compile error due to ambiguity with lib namespace # Hack to prevent compile error due to ambiguity with lib namespace
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
@@ -46,6 +50,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_DECRYPTION_KEY): _validate_key,
cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean,
cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_,
cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_,
cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_REQUEST_INTERVAL): cv.positive_time_period_milliseconds,
} }
).extend(uart.UART_DEVICE_SCHEMA), ).extend(uart.UART_DEVICE_SCHEMA),
cv.only_with_arduino, cv.only_with_arduino,
@@ -55,10 +62,19 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
uart_component = await cg.get_variable(config[CONF_UART_ID]) uart_component = await cg.get_variable(config[CONF_UART_ID])
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
if CONF_DECRYPTION_KEY in config: if CONF_DECRYPTION_KEY in config:
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
await cg.register_component(var, config) await cg.register_component(var, config)
if CONF_REQUEST_PIN in config:
request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
cg.add(var.set_request_pin(request_pin))
if CONF_REQUEST_INTERVAL in config:
cg.add(
var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)
)
cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID])
# DSMR Parser # DSMR Parser

View File

@@ -12,146 +12,218 @@ namespace dsmr {
static const char *const TAG = "dsmr"; static const char *const TAG = "dsmr";
void Dsmr::setup() {
this->telegram_ = new char[this->max_telegram_len_]; // NOLINT
if (this->request_pin_ != nullptr) {
this->request_pin_->setup();
}
}
void Dsmr::loop() { void Dsmr::loop() {
if (this->decryption_key_.empty()) if (this->ready_to_request_data_()) {
this->receive_telegram_(); if (this->decryption_key_.empty()) {
else this->receive_telegram_();
this->receive_encrypted_(); } else {
this->receive_encrypted_();
}
}
}
bool Dsmr::ready_to_request_data_() {
// When using a request pin, then wait for the next request interval.
if (this->request_pin_ != nullptr) {
if (!this->requesting_data_ && this->request_interval_reached_()) {
this->start_requesting_data_();
}
}
// Otherwise, sink serial data until next request interval.
else {
if (this->request_interval_reached_()) {
this->start_requesting_data_();
}
if (!this->requesting_data_) {
while (this->available()) {
this->read();
}
}
}
return this->requesting_data_;
}
bool Dsmr::request_interval_reached_() {
if (this->last_request_time_ == 0) {
return true;
}
return millis() - this->last_request_time_ > this->request_interval_;
} }
bool Dsmr::available_within_timeout_() { bool Dsmr::available_within_timeout_() {
uint8_t tries = READ_TIMEOUT_MS / 5; uint8_t tries = READ_TIMEOUT_MS / 5;
while (tries--) { while (tries--) {
delay(5); delay(5);
if (available()) { if (this->available()) {
return true; return true;
} }
} }
return false; return false;
} }
void Dsmr::start_requesting_data_() {
if (!this->requesting_data_) {
if (this->request_pin_ != nullptr) {
ESP_LOGV(TAG, "Start requesting data from P1 port");
this->request_pin_->digital_write(true);
} else {
ESP_LOGV(TAG, "Start reading data from P1 port");
}
this->requesting_data_ = true;
this->last_request_time_ = millis();
}
}
void Dsmr::stop_requesting_data_() {
if (this->requesting_data_) {
if (this->request_pin_ != nullptr) {
ESP_LOGV(TAG, "Stop requesting data from P1 port");
this->request_pin_->digital_write(false);
} else {
ESP_LOGV(TAG, "Stop reading data from P1 port");
}
while (this->available()) {
this->read();
}
this->requesting_data_ = false;
}
}
void Dsmr::receive_telegram_() { void Dsmr::receive_telegram_() {
while (true) { while (true) {
if (!available()) { if (!this->available()) {
if (!header_found_ || !available_within_timeout_()) { if (!this->header_found_ || !this->available_within_timeout_()) {
return; return;
} }
} }
const char c = read(); const char c = this->read();
// Find a new telegram header, i.e. forward slash. // Find a new telegram header, i.e. forward slash.
if (c == '/') { if (c == '/') {
ESP_LOGV(TAG, "Header of telegram found"); ESP_LOGV(TAG, "Header of telegram found");
header_found_ = true; this->header_found_ = true;
footer_found_ = false; this->footer_found_ = false;
telegram_len_ = 0; this->telegram_len_ = 0;
} }
if (!header_found_) if (!this->header_found_)
continue; continue;
// Check for buffer overflow. // Check for buffer overflow.
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { if (this->telegram_len_ >= this->max_telegram_len_) {
header_found_ = false; this->header_found_ = false;
footer_found_ = false; this->footer_found_ = false;
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return; return;
} }
// Some v2.2 or v3 meters will send a new value which starts with '(' // Some v2.2 or v3 meters will send a new value which starts with '('
// in a new line while the value belongs to the previous ObisId. For // in a new line, while the value belongs to the previous ObisId. For
// proper parsing remove these new line characters // proper parsing, remove these new line characters.
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) if (c == '(') {
telegram_len_--; while (true) {
auto previous_char = this->telegram_[this->telegram_len_ - 1];
if (previous_char == '\n' || previous_char == '\r') {
this->telegram_len_--;
} else {
break;
}
}
}
// Store the byte in the buffer. // Store the byte in the buffer.
telegram_[telegram_len_] = c; this->telegram_[this->telegram_len_] = c;
telegram_len_++; this->telegram_len_++;
// Check for a footer, i.e. exlamation mark, followed by a hex checksum. // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') { if (c == '!') {
ESP_LOGV(TAG, "Footer of telegram found"); ESP_LOGV(TAG, "Footer of telegram found");
footer_found_ = true; this->footer_found_ = true;
continue; continue;
} }
// Check for the end of the hex checksum, i.e. a newline. // Check for the end of the hex checksum, i.e. a newline.
if (footer_found_ && c == '\n') { if (this->footer_found_ && c == '\n') {
// Parse the telegram and publish sensor values. // Parse the telegram and publish sensor values.
parse_telegram(); this->parse_telegram();
header_found_ = false; this->header_found_ = false;
return; return;
} }
} }
} }
void Dsmr::receive_encrypted_() { void Dsmr::receive_encrypted_() {
// Encrypted buffer this->encrypted_telegram_len_ = 0;
uint8_t buffer[MAX_TELEGRAM_LENGTH];
size_t buffer_length = 0;
size_t packet_size = 0; size_t packet_size = 0;
while (true) { while (true) {
if (!available()) { if (!this->available()) {
if (!header_found_) { if (!this->header_found_) {
return; return;
} }
if (!available_within_timeout_()) { if (!this->available_within_timeout_()) {
ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
return; return;
} }
} }
const char c = read(); const char c = this->read();
// Find a new telegram start byte. // Find a new telegram start byte.
if (!header_found_) { if (!this->header_found_) {
if ((uint8_t) c != 0xDB) { if ((uint8_t) c != 0xDB) {
continue; continue;
} }
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
header_found_ = true; this->header_found_ = true;
} }
// Check for buffer overflow. // Check for buffer overflow.
if (buffer_length >= MAX_TELEGRAM_LENGTH) { if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
header_found_ = false; this->header_found_ = false;
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return; return;
} }
buffer[buffer_length++] = c; this->encrypted_telegram_[this->encrypted_telegram_len_++] = c;
if (packet_size == 0 && buffer_length > 20) { if (packet_size == 0 && this->encrypted_telegram_len_ > 20) {
// Complete header + data bytes // Complete header + data bytes
packet_size = 13 + (buffer[11] << 8 | buffer[12]); packet_size = 13 + (this->encrypted_telegram_[11] << 8 | this->encrypted_telegram_[12]);
ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
} }
if (buffer_length == packet_size && packet_size > 0) { if (this->encrypted_telegram_len_ == packet_size && packet_size > 0) {
ESP_LOGV(TAG, "End of encrypted telegram found"); ESP_LOGV(TAG, "End of encrypted telegram found");
GCM<AES128> *gcmaes128{new GCM<AES128>()}; GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter // the iv is 8 bytes of the system title + 4 bytes frame counter
// system title is at byte 2 and frame counter at byte 15 // system title is at byte 2 and frame counter at byte 15
for (int i = 10; i < 14; i++) for (int i = 10; i < 14; i++)
buffer[i] = buffer[i + 4]; this->encrypted_telegram_[i] = this->encrypted_telegram_[i + 4];
constexpr uint16_t iv_size{12}; constexpr uint16_t iv_size{12};
gcmaes128->setIV(&buffer[2], iv_size); gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_), gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
// the ciphertext start at byte 18 // the ciphertext start at byte 18
&buffer[18], &this->encrypted_telegram_[18],
// cipher size // cipher size
buffer_length - 17); this->encrypted_telegram_len_ - 17);
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory) delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
parse_telegram(); this->parse_telegram();
header_found_ = false; this->header_found_ = false;
telegram_len_ = 0; this->telegram_len_ = 0;
return; return;
} }
} }
@@ -160,23 +232,32 @@ void Dsmr::receive_encrypted_() {
bool Dsmr::parse_telegram() { bool Dsmr::parse_telegram() {
MyData data; MyData data;
ESP_LOGV(TAG, "Trying to parse telegram"); ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_();
::dsmr::ParseResult<void> res = ::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, ::dsmr::P1Parser::parse(&data, this->telegram_, this->telegram_len_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
if (res.err) { if (res.err) {
// Parsing error, show it // Parsing error, show it
auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); auto err_str = res.fullError(this->telegram_, this->telegram_ + this->telegram_len_);
ESP_LOGE(TAG, "%s", err_str.c_str()); ESP_LOGE(TAG, "%s", err_str.c_str());
return false; return false;
} else { } else {
this->status_clear_warning(); this->status_clear_warning();
publish_sensors(data); this->publish_sensors(data);
return true; return true;
} }
} }
void Dsmr::dump_config() { void Dsmr::dump_config() {
ESP_LOGCONFIG(TAG, "DSMR:"); ESP_LOGCONFIG(TAG, "DSMR:");
ESP_LOGCONFIG(TAG, " Max telegram length: %d", this->max_telegram_len_);
if (this->request_pin_ != nullptr) {
LOG_PIN(" Request Pin: ", this->request_pin_);
}
if (this->request_interval_ > 0) {
ESP_LOGCONFIG(TAG, " Request Interval: %.1fs", this->request_interval_ / 1e3f);
}
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_); #define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@@ -189,6 +270,10 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
if (decryption_key.length() == 0) { if (decryption_key.length() == 0) {
ESP_LOGI(TAG, "Disabling decryption"); ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear(); this->decryption_key_.clear();
if (this->encrypted_telegram_ != nullptr) {
delete[] this->encrypted_telegram_;
this->encrypted_telegram_ = nullptr;
}
return; return;
} }
@@ -205,10 +290,16 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
char temp[3] = {0}; char temp[3] = {0};
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); strncpy(temp, &(decryption_key.c_str()[i * 2]), 2);
decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16));
}
if (this->encrypted_telegram_ == nullptr) {
this->encrypted_telegram_ = new uint8_t[this->max_telegram_len_]; // NOLINT
} }
} }
void Dsmr::set_max_telegram_length(size_t length) { max_telegram_len_ = length; }
} // namespace dsmr } // namespace dsmr
} // namespace esphome } // namespace esphome

View File

@@ -16,7 +16,6 @@
namespace esphome { namespace esphome {
namespace dsmr { namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
static constexpr uint32_t READ_TIMEOUT_MS = 200; static constexpr uint32_t READ_TIMEOUT_MS = 200;
using namespace ::dsmr::fields; using namespace ::dsmr::fields;
@@ -52,6 +51,7 @@ class Dsmr : public Component, public uart::UARTDevice {
public: public:
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
void setup() override;
void loop() override; void loop() override;
bool parse_telegram(); bool parse_telegram();
@@ -72,6 +72,11 @@ class Dsmr : public Component, public uart::UARTDevice {
void set_decryption_key(const std::string &decryption_key); void set_decryption_key(const std::string &decryption_key);
void set_max_telegram_length(size_t length);
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
// Sensor setters // Sensor setters
#define DSMR_SET_SENSOR(s) \ #define DSMR_SET_SENSOR(s) \
void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; } void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
@@ -96,9 +101,22 @@ class Dsmr : public Component, public uart::UARTDevice {
/// lost in the process. /// lost in the process.
bool available_within_timeout_(); bool available_within_timeout_();
// Data request
GPIOPin *request_pin_{nullptr};
uint32_t request_interval_{0};
uint32_t last_request_time_{0};
bool requesting_data_{false};
bool ready_to_request_data_();
bool request_interval_reached_();
void start_requesting_data_();
void stop_requesting_data_();
// Telegram buffer // Telegram buffer
char telegram_[MAX_TELEGRAM_LENGTH]; size_t max_telegram_len_;
char *telegram_{nullptr};
int telegram_len_{0}; int telegram_len_{0};
uint8_t *encrypted_telegram_{nullptr};
int encrypted_telegram_len_{0};
// Serial parser // Serial parser
bool header_found_{false}; bool header_found_{false};

View File

@@ -21,12 +21,19 @@ static const char *const TAG = "esp32_camera_web_server";
#define CONTENT_TYPE "image/jpeg" #define CONTENT_TYPE "image/jpeg"
#define CONTENT_LENGTH "Content-Length" #define CONTENT_LENGTH "Content-Length"
static const char *const STREAM_HEADER = static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
"HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "Access-Control-Allow-Origin: *\r\n"
"\r\n"; "Connection: close\r\n"
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n"; "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n"
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; "\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_ERROR = "Content-Type: text/plain\r\n"
"\r\n"
"No frames send.\r\n"
"--" PART_BOUNDARY "\r\n";
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
static const char *const STREAM_BOUNDARY = "\r\n"
"--" PART_BOUNDARY "\r\n";
CameraWebServer::CameraWebServer() {} CameraWebServer::CameraWebServer() {}
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
config.ctrl_port = this->port_; config.ctrl_port = this->port_;
config.max_open_sockets = 1; config.max_open_sockets = 1;
config.backlog_conn = 2; config.backlog_conn = 2;
config.lru_purge_enable = true;
if (httpd_start(&this->httpd_, &config) != ESP_OK) { if (httpd_start(&this->httpd_, &config) != ESP_OK) {
mark_failed(); mark_failed();
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
ESP_LOGW(TAG, "STREAM: failed to acquire frame"); ESP_LOGW(TAG, "STREAM: failed to acquire frame");
res = ESP_FAIL; res = ESP_FAIL;
} }
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) { if (res == ESP_OK) {
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length()); size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
res = httpd_send_all(req, part_buf, hlen); res = httpd_send_all(req, part_buf, hlen);
@@ -182,6 +187,9 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
if (res == ESP_OK) { if (res == ESP_OK) {
res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length()); res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
} }
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) { if (res == ESP_OK) {
frames++; frames++;
int64_t frame_time = millis() - last_frame; int64_t frame_time = millis() - last_frame;
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
} }
if (!frames) { if (!frames) {
res = httpd_send_all(req, STREAM_500, strlen(STREAM_500)); res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
} }
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames); ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);

View File

@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID from esphome.const import CONF_ID
AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]
@@ -56,6 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var)) cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV") cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.0.0")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -11,6 +11,7 @@ namespace esphome {
namespace esp32_improv { namespace esp32_improv {
static const char *const TAG = "esp32_improv.component"; static const char *const TAG = "esp32_improv.component";
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
this->cancel_timeout("wifi-connect-timeout"); this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED); this->set_state_(improv::STATE_PROVISIONED);
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; std::vector<std::string> urls = {ESPHOME_MY_LINK};
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url}); #ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url);
#endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
this->send_response_(data); this->send_response_(data);
this->set_timeout("end-service", 1000, [this] { this->set_timeout("end-service", 1000, [this] {
this->service_->stop(); this->service_->stop();

View File

@@ -1,9 +1,8 @@
#pragma once #pragma once
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h" #include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/improv/improv.h" #include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/output/binary_output.h" #include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
@@ -12,6 +11,8 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <improv.h>
namespace esphome { namespace esphome {
namespace esp32_improv { namespace esp32_improv {

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esp32_touch.h" #include "esp32_touch.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
if (this->iir_filter_enabled_()) { if (this->iir_filter_enabled_()) {
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_); ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_);
touch_pad_filter_start(this->iir_filter_);
} else { } else {
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
} }
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
if (should_print) { if (should_print) {
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value); ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
} }
App.feed_wdt();
} }
if (should_print) { if (should_print) {

View File

@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
"wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0},
"xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13},
} }
FLASH_SIZE_1_MB = 2 ** 20
FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2
FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB
FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB
FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB
ESP8266_FLASH_SIZES = {
"d1": FLASH_SIZE_4_MB,
"d1_mini": FLASH_SIZE_4_MB,
"d1_mini_lite": FLASH_SIZE_1_MB,
"d1_mini_pro": FLASH_SIZE_16_MB,
"esp01": FLASH_SIZE_512_KB,
"esp01_1m": FLASH_SIZE_1_MB,
"esp07": FLASH_SIZE_4_MB,
"esp12e": FLASH_SIZE_4_MB,
"esp210": FLASH_SIZE_4_MB,
"esp8285": FLASH_SIZE_1_MB,
"esp_wroom_02": FLASH_SIZE_2_MB,
"espduino": FLASH_SIZE_4_MB,
"espectro": FLASH_SIZE_4_MB,
"espino": FLASH_SIZE_4_MB,
"espinotee": FLASH_SIZE_4_MB,
"espmxdevkit": FLASH_SIZE_1_MB,
"espresso_lite_v1": FLASH_SIZE_4_MB,
"espresso_lite_v2": FLASH_SIZE_4_MB,
"gen4iod": FLASH_SIZE_512_KB,
"heltec_wifi_kit_8": FLASH_SIZE_4_MB,
"huzzah": FLASH_SIZE_4_MB,
"inventone": FLASH_SIZE_4_MB,
"modwifi": FLASH_SIZE_2_MB,
"nodemcu": FLASH_SIZE_4_MB,
"nodemcuv2": FLASH_SIZE_4_MB,
"oak": FLASH_SIZE_4_MB,
"phoenix_v1": FLASH_SIZE_4_MB,
"phoenix_v2": FLASH_SIZE_4_MB,
"sonoff_basic": FLASH_SIZE_1_MB,
"sonoff_s20": FLASH_SIZE_1_MB,
"sonoff_sv": FLASH_SIZE_1_MB,
"sonoff_th": FLASH_SIZE_1_MB,
"sparkfunBlynk": FLASH_SIZE_4_MB,
"thing": FLASH_SIZE_512_KB,
"thingdev": FLASH_SIZE_512_KB,
"wifi_slot": FLASH_SIZE_1_MB,
"wifiduino": FLASH_SIZE_4_MB,
"wifinfo": FLASH_SIZE_1_MB,
"wio_link": FLASH_SIZE_4_MB,
"wio_node": FLASH_SIZE_4_MB,
"xinabox_cw01": FLASH_SIZE_4_MB,
}
ESP8266_LD_SCRIPTS = {
FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"),
FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"),
FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"),
FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"),
FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"),
}

View File

@@ -30,6 +30,7 @@ PROTOCOLS = {
"gree": Protocol.PROTOCOL_GREE, "gree": Protocol.PROTOCOL_GREE,
"greeya": Protocol.PROTOCOL_GREEYAA, "greeya": Protocol.PROTOCOL_GREEYAA,
"greeyan": Protocol.PROTOCOL_GREEYAN, "greeyan": Protocol.PROTOCOL_GREEYAN,
"greeyac": Protocol.PROTOCOL_GREEYAC,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI, "hitachi": Protocol.PROTOCOL_HITACHI,
"hyundai": Protocol.PROTOCOL_HYUNDAI, "hyundai": Protocol.PROTOCOL_HYUNDAI,
@@ -111,4 +112,6 @@ def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.15") # 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")

View File

@@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
} }
this->heatpump_ir_ = protocol_constructor->second(); this->heatpump_ir_ = protocol_constructor->second();
climate_ir::ClimateIR::setup(); climate_ir::ClimateIR::setup();
if (this->sensor_) {
this->sensor_->add_on_state_callback([this](float state) {
this->current_temperature = state;
IRSenderESPHome esp_sender(this->transmitter_);
this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5)));
// current temperature changed, publish state
this->publish_state();
});
this->current_temperature = this->sensor_->state;
} else
this->current_temperature = NAN;
} }
void HeatpumpIRClimate::transmit_state() { void HeatpumpIRClimate::transmit_state() {
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_);
IRSenderESPHome esp_sender(0, this->transmitter_); IRSenderESPHome esp_sender(this->transmitter_);
heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd,
swing_h_cmd); swing_h_cmd);
} }

View File

@@ -25,6 +25,7 @@ enum Protocol {
PROTOCOL_GREE, PROTOCOL_GREE,
PROTOCOL_GREEYAA, PROTOCOL_GREEYAA,
PROTOCOL_GREEYAN, PROTOCOL_GREEYAN,
PROTOCOL_GREEYAC,
PROTOCOL_HISENSE_AUD, PROTOCOL_HISENSE_AUD,
PROTOCOL_HITACHI, PROTOCOL_HITACHI,
PROTOCOL_HYUNDAI, PROTOCOL_HYUNDAI,

View File

@@ -11,8 +11,8 @@ namespace heatpumpir {
class IRSenderESPHome : public IRSender { class IRSenderESPHome : public IRSender {
public: public:
IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter)
: IRSender(pin), transmit_(transmitter->transmit()){}; : IRSender(0), transmit_(transmitter->transmit()){};
void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming) void setFrequency(int frequency) override; // NOLINT(readability-identifier-naming)
void space(int space_length) override; void space(int space_length) override;
void mark(int mark_length) override; void mark(int mark_length) override;

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include <cstdint> #include <cstdint>
namespace esphome { namespace esphome {
@@ -13,5 +12,3 @@ class AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
namespace esphome { namespace esphome {
@@ -33,7 +31,7 @@ class AQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0]; int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1]; int conc_hi = array[grid_index][1];
return ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
} }
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
@@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "caqi_calculator.h" #include "caqi_calculator.h"
#include "aqi_calculator.h" #include "aqi_calculator.h"
@@ -29,5 +27,3 @@ class AQICalculatorFactory {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,7 +1,5 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "abstract_aqi_calculator.h" #include "abstract_aqi_calculator.h"
@@ -37,9 +35,7 @@ class CAQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0]; int conc_lo = array[grid_index][0];
int conc_hi = array[grid_index][1]; int conc_hi = array[grid_index][1];
int aqi = ((aqi_hi - aqi_lo) / (conc_hi - conc_lo)) * (value - conc_lo) + aqi_lo; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
return aqi;
} }
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) { int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
@@ -54,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "hm3301.h" #include "hm3301.h"
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
void HM3301Component::setup() { void HM3301Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up HM3301..."); ESP_LOGCONFIG(TAG, "Setting up HM3301...");
hm3301_ = make_unique<HM330X>(); if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
error_code_ = hm3301_->init(); error_code_ = ERROR_COMM;
if (error_code_ != NO_ERROR) {
this->mark_failed(); this->mark_failed();
return; return;
} }
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
void HM3301Component::update() { void HM3301Component::update() {
if (!this->read_sensor_value_(data_buffer_)) { if (this->read(data_buffer_, 29) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read result failed"); ESP_LOGW(TAG, "Read result failed");
this->status_set_warning(); this->status_set_warning();
return; return;
@@ -87,8 +84,6 @@ void HM3301Component::update() {
this->status_clear_warning(); this->status_clear_warning();
} }
bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); }
bool HM3301Component::validate_checksum_(const uint8_t *data) { bool HM3301Component::validate_checksum_(const uint8_t *data) {
uint8_t sum = 0; uint8_t sum = 0;
for (int i = 0; i < 28; i++) { for (int i = 0; i < 28; i++) {
@@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) {
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -1,17 +1,15 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h" #include "esphome/components/i2c/i2c.h"
#include "aqi_calculator_factory.h" #include "aqi_calculator_factory.h"
#include <Seeed_HM330X.h>
namespace esphome { namespace esphome {
namespace hm3301 { namespace hm3301 {
static const uint8_t SELECT_COMM_CMD = 0X88;
class HM3301Component : public PollingComponent, public i2c::I2CDevice { class HM3301Component : public PollingComponent, public i2c::I2CDevice {
public: public:
HM3301Component() = default; HM3301Component() = default;
@@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
void update() override; void update() override;
protected: protected:
std::unique_ptr<HM330X> hm3301_; enum {
NO_ERROR = 0,
HM330XErrorCode error_code_{NO_ERROR}; ERROR_PARAM = -1,
ERROR_COMM = -2,
ERROR_OTHERS = -128,
} error_code_{NO_ERROR};
uint8_t data_buffer_[30]; uint8_t data_buffer_[30];
@@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice {
AQICalculatorType aqi_calc_type_; AQICalculatorType aqi_calc_type_;
AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory();
bool read_sensor_value_(uint8_t *);
bool validate_checksum_(const uint8_t *); bool validate_checksum_(const uint8_t *);
uint16_t get_sensor_value_(const uint8_t *, uint8_t); uint16_t get_sensor_value_(const uint8_t *, uint8_t);
}; };
} // namespace hm3301 } // namespace hm3301
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View File

@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40)), .extend(i2c.i2c_device_schema(0x40)),
_validate, _validate,
cv.only_with_arduino,
) )
@@ -109,6 +108,3 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_AQI]) sens = await sensor.new_sensor(config[CONF_AQI])
cg.add(var.set_aqi_sensor(sens)) cg.add(var.set_aqi_sensor(sens))
cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE]))
# https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301
cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3")

View File

@@ -114,8 +114,8 @@ CONFIG_SCHEMA = (
def auto_data_rate(config): def auto_data_rate(config):
interval_sec = config[CONF_UPDATE_INTERVAL].seconds interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
interval_hz = 1.0 / interval_sec interval_hz = 1000.0 / interval_msec
for datarate in sorted(HMC5883LDatarates.keys()): for datarate in sorted(HMC5883LDatarates.keys()):
if float(datarate) >= interval_hz: if float(datarate) >= interval_hz:
return HMC5883LDatarates[datarate] return HMC5883LDatarates[datarate]

View File

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

View File

@@ -1,93 +0,0 @@
#include "improv.h"
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
return parse_improv_data(data.data(), data.size());
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
ImprovCommand improv_command;
Command command = (Command) data[0];
uint8_t data_length = data[1];
if (data_length != length - 3) {
improv_command.command = UNKNOWN;
return improv_command;
}
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t) calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
}
if (command == WIFI_SETTINGS) {
uint8_t ssid_length = data[2];
uint8_t ssid_start = 3;
size_t ssid_end = ssid_start + ssid_length;
uint8_t pass_length = data[ssid_end];
size_t pass_start = ssid_end + 1;
size_t pass_end = pass_start + pass_length;
std::string ssid(data + ssid_start, data + ssid_end);
std::string password(data + pass_start, data + pass_end);
return {.command = command, .ssid = ssid, .password = password};
}
improv_command.command = command;
return improv_command;
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
return out;
}
#ifdef USE_ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (auto str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
return out;
}
#endif // USE_ARDUINO
} // namespace improv

View File

@@ -1,62 +0,0 @@
#pragma once
#ifdef ARDUINO
#include "WString.h"
#endif // ARDUINO
#include <cstdint>
#include <string>
#include <vector>
namespace improv {
static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000";
static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001";
static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002";
static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003";
static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004";
static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005";
enum Error : uint8_t {
ERROR_NONE = 0x00,
ERROR_INVALID_RPC = 0x01,
ERROR_UNKNOWN_RPC = 0x02,
ERROR_UNABLE_TO_CONNECT = 0x03,
ERROR_NOT_AUTHORIZED = 0x04,
ERROR_UNKNOWN = 0xFF,
};
enum State : uint8_t {
STATE_STOPPED = 0x00,
STATE_AWAITING_AUTHORIZATION = 0x01,
STATE_AUTHORIZED = 0x02,
STATE_PROVISIONING = 0x03,
STATE_PROVISIONED = 0x04,
};
enum Command : uint8_t {
UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02,
GET_CURRENT_STATE = 0x02,
GET_DEVICE_INFO = 0x03,
BAD_CHECKSUM = 0xFF,
};
static const uint8_t CAPABILITY_IDENTIFY = 0x01;
struct ImprovCommand {
Command command;
std::string ssid;
std::string password;
};
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
#ifdef ARDUINO
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
#endif // ARDUINO
} // namespace improv

View File

@@ -5,7 +5,6 @@ import esphome.final_validate as fv
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["logger", "wifi"] DEPENDENCIES = ["logger", "wifi"]
AUTO_LOAD = ["improv"]
improv_serial_ns = cg.esphome_ns.namespace("improv_serial") improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
@@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add_library("esphome/Improv", "1.0.0")

View File

@@ -92,27 +92,26 @@ void ImprovSerialComponent::loop() {
} }
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) { std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; std::vector<std::string> urls;
std::vector<std::string> urls = {url};
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
auto ip = wifi::global_wifi_component->wifi_sta_ip(); auto ip = wifi::global_wifi_component->wifi_sta_ip();
std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
urls.push_back(webserver_url); urls.push_back(webserver_url);
#endif #endif
std::vector<uint8_t> data = improv::build_rpc_response(command, urls); std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
return data; return data;
} }
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() { std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()}; std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos); std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
return data; return data;
}; };
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size(); size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte); this->rx_buffer_.push_back(byte);
ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte); ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte);
const uint8_t *raw = &this->rx_buffer_[0]; const uint8_t *raw = &this->rx_buffer_[0];
if (at == 0) if (at == 0)
return byte == 'I'; return byte == 'I';
@@ -141,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
if (at < 8 + data_len) if (at < 8 + data_len)
return true; return true;
if (at == 8 + data_len) { if (at == 8 + data_len)
return true;
if (at == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (uint8_t i = 0; i < at; i++)
checksum += raw[i];
if (checksum != byte) {
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) { if (type == TYPE_RPC) {
this->set_error_(improv::ERROR_NONE); this->set_error_(improv::ERROR_NONE);
auto command = improv::parse_improv_data(&raw[9], data_len); auto command = improv::parse_improv_data(&raw[9], data_len, false);
return this->parse_improv_payload_(command); return this->parse_improv_payload_(command);
} }
} }
return true;
// If we got here then the command coming is is improv, but not an RPC command
return false;
} }
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) { bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
switch (command.command) { switch (command.command) {
case improv::BAD_CHECKSUM:
ESP_LOGW(TAG, "Error decoding Improv payload");
this->set_error_(improv::ERROR_INVALID_RPC);
return false;
case improv::WIFI_SETTINGS: { case improv::WIFI_SETTINGS: {
wifi::WiFiAP sta{}; wifi::WiFiAP sta{};
sta.set_ssid(command.ssid); sta.set_ssid(command.ssid);
@@ -166,7 +176,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
wifi::global_wifi_component->set_sta(sta); wifi::global_wifi_component->set_sta(sta);
wifi::global_wifi_component->start_scanning(); wifi::global_wifi_component->start_scanning();
this->set_state_(improv::STATE_PROVISIONING); this->set_state_(improv::STATE_PROVISIONING);
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), ESP_LOGI(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
command.password.c_str()); command.password.c_str());
auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this); auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
@@ -233,6 +243,12 @@ void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
data[7] = TYPE_RPC_RESPONSE; data[7] = TYPE_RPC_RESPONSE;
data[8] = response.size(); data[8] = response.size();
data.insert(data.end(), response.begin(), response.end()); data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
this->write_data_(data); this->write_data_(data);
} }

View File

@@ -1,11 +1,12 @@
#pragma once #pragma once
#include "esphome/components/improv/improv.h"
#include "esphome/components/wifi/wifi_component.h" #include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <improv.h>
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <HardwareSerial.h> #include <HardwareSerial.h>
#endif #endif

View File

@@ -15,17 +15,37 @@ namespace ledc {
static const char *const TAG = "ledc.output"; static const char *const TAG = "ledc.output";
#ifdef USE_ESP_IDF
static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1;
#if SOC_LEDC_SUPPORT_HS_MODE
// Only ESP32 has LEDC_HIGH_SPEED_MODE
inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; }
#else
// S2, C3, S3 only support LEDC_LOW_SPEED_MODE
// See
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview
inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
#endif
#else
static const int MAX_RES_BITS = 20;
#endif
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) {
const float max_div_num = ((1 << 20) - 1) / 256.0f; float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
return 80e6f / (max_div_num * float(1 << bit_depth)); return 80e6f / (max_div_num * float(1 << bit_depth));
} }
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) { optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
for (int i = 20; i >= 1; i--) { ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
const float min_frequency = ledc_min_frequency_for_bit_depth(i); for (int i = MAX_RES_BITS; i >= 1; i--) {
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
const float max_frequency = ledc_max_frequency_for_bit_depth(i); const float max_frequency = ledc_max_frequency_for_bit_depth(i);
if (min_frequency <= frequency && frequency <= max_frequency) if (min_frequency <= frequency && frequency <= max_frequency) {
ESP_LOGD(TAG, "Resolution calculated as %d", i);
return i; return i;
}
} }
return {}; return {};
} }
@@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
ledcWrite(this->channel_, duty); ledcWrite(this->channel_, duty);
#endif #endif
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
ledc_set_duty(speed_mode, chan_num, duty); ledc_set_duty(speed_mode, chan_num, duty);
ledc_update_duty(speed_mode, chan_num); ledc_update_duty(speed_mode, chan_num);
@@ -63,11 +83,15 @@ void LEDCOutput::setup() {
ledcAttachPin(this->pin_->get_pin(), this->channel_); ledcAttachPin(this->pin_->get_pin(), this->channel_);
#endif #endif
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
if (bit_depth_ < 1) {
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
this->status_set_warning();
}
ledc_timer_config_t timer_conf{}; ledc_timer_config_t timer_conf{};
timer_conf.speed_mode = speed_mode; timer_conf.speed_mode = speed_mode;
@@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) {
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
return; return;
} }
auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; auto speed_mode = get_speed_mode(channel_);
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
ledc_timer_config_t timer_conf{}; ledc_timer_config_t timer_conf{};

View File

@@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
void mark_shown_() { void mark_shown_() {
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
for (const auto &c : *this) { for (const auto &c : *this) {
if (c.get().is_on()) { if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
this->power_.request(); this->power_.request();
return; return;
} }

View File

@@ -1,10 +1,11 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "adapter.h" #include "ac_adapter.h"
namespace esphome { namespace esphome {
namespace midea { namespace midea {
namespace ac {
const char *const Constants::TAG = "midea"; const char *const Constants::TAG = "midea";
const std::string Constants::FREEZE_PROTECTION = "freeze protection"; const std::string Constants::FREEZE_PROTECTION = "freeze protection";
@@ -171,6 +172,7 @@ void Converters::to_climate_traits(ClimateTraits &traits, const dudanov::midea::
traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION); traits.add_supported_custom_preset(Constants::FREEZE_PROTECTION);
} }
} // namespace ac
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -2,12 +2,15 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h> #include <Appliance/AirConditioner/AirConditioner.h>
#include "esphome/components/climate/climate_traits.h" #include "esphome/components/climate/climate_traits.h"
#include "appliance_base.h" #include "air_conditioner.h"
namespace esphome { namespace esphome {
namespace midea { namespace midea {
namespace ac {
using MideaMode = dudanov::midea::ac::Mode; using MideaMode = dudanov::midea::ac::Mode;
using MideaSwingMode = dudanov::midea::ac::SwingMode; using MideaSwingMode = dudanov::midea::ac::SwingMode;
@@ -41,6 +44,7 @@ class Converters {
static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities); static void to_climate_traits(ClimateTraits &traits, const dudanov::midea::ac::Capabilities &capabilities);
}; };
} // namespace ac
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -7,6 +7,7 @@
namespace esphome { namespace esphome {
namespace midea { namespace midea {
namespace ac {
template<typename... Ts> class MideaActionBase : public Action<Ts...> { template<typename... Ts> class MideaActionBase : public Action<Ts...> {
public: public:
@@ -55,6 +56,7 @@ template<typename... Ts> class PowerOffAction : public MideaActionBase<Ts...> {
void play(Ts... x) override { this->parent_->do_power_off(); } void play(Ts... x) override { this->parent_->do_power_off(); }
}; };
} // namespace ac
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -2,13 +2,11 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "air_conditioner.h" #include "air_conditioner.h"
#include "adapter.h" #include "ac_adapter.h"
#ifdef USE_REMOTE_TRANSMITTER
#include "midea_ir.h"
#endif
namespace esphome { namespace esphome {
namespace midea { namespace midea {
namespace ac {
static void set_sensor(Sensor *sensor, float value) { static void set_sensor(Sensor *sensor, float value) {
if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value)) if (sensor != nullptr && (!sensor->has_state() || sensor->get_raw_state() != value))
@@ -122,7 +120,7 @@ void AirConditioner::dump_config() {
void AirConditioner::do_follow_me(float temperature, bool beeper) { void AirConditioner::do_follow_me(float temperature, bool beeper) {
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper); IrFollowMeData data(static_cast<uint8_t>(lroundf(temperature)), beeper);
this->transmit_ir(data); this->transmitter_.transmit(data);
#else #else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif #endif
@@ -131,7 +129,7 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
void AirConditioner::do_swing_step() { void AirConditioner::do_swing_step() {
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
IrSpecialData data(0x01); IrSpecialData data(0x01);
this->transmit_ir(data); this->transmitter_.transmit(data);
#else #else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif #endif
@@ -143,13 +141,14 @@ void AirConditioner::do_display_toggle() {
} else { } else {
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
IrSpecialData data(0x08); IrSpecialData data(0x08);
this->transmit_ir(data); this->transmitter_.transmit(data);
#else #else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component"); ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
#endif #endif
} }
} }
} // namespace ac
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -2,17 +2,25 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h> #include <Appliance/AirConditioner/AirConditioner.h>
#include "appliance_base.h" #include "appliance_base.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
namespace esphome { namespace esphome {
namespace midea { namespace midea {
namespace ac {
using sensor::Sensor; using sensor::Sensor;
using climate::ClimateCall; using climate::ClimateCall;
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateMode;
using climate::ClimateSwingMode;
using climate::ClimateFanMode;
class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner> { class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>, public climate::Climate {
public: public:
void dump_config() override; void dump_config() override;
void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; } void set_outdoor_temperature_sensor(Sensor *sensor) { this->outdoor_sensor_ = sensor; }
@@ -31,15 +39,26 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>
void do_beeper_off() { this->set_beeper_feedback(false); } void do_beeper_off() { this->set_beeper_feedback(false); }
void do_power_on() { this->base_.setPowerState(true); } void do_power_on() { this->base_.setPowerState(true); }
void do_power_off() { this->base_.setPowerState(false); } void do_power_off() { this->base_.setPowerState(false); }
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
protected: protected:
void control(const ClimateCall &call) override; void control(const ClimateCall &call) override;
ClimateTraits traits() override; ClimateTraits traits() override;
std::set<ClimateMode> supported_modes_{};
std::set<ClimateSwingMode> supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{};
std::set<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{};
Sensor *outdoor_sensor_{nullptr}; Sensor *outdoor_sensor_{nullptr};
Sensor *humidity_sensor_{nullptr}; Sensor *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr}; Sensor *power_sensor_{nullptr};
}; };
} // namespace ac
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -2,84 +2,97 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
// MideaUART
#include <Appliance/ApplianceBase.h>
#include <Helpers/Logger.h>
// Include global defines
#include "esphome/core/defines.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include "esphome/components/climate/climate.h" #include "esphome/components/climate/climate.h"
#ifdef USE_REMOTE_TRANSMITTER #include "ir_transmitter.h"
#include "esphome/components/remote_base/midea_protocol.h"
#include "esphome/components/remote_transmitter/remote_transmitter.h"
#endif
#include <Appliance/ApplianceBase.h>
#include <Helpers/Logger.h>
namespace esphome { namespace esphome {
namespace midea { namespace midea {
using climate::ClimatePreset; /* Stream from UART component */
using climate::ClimateTraits; class UARTStream : public Stream {
using climate::ClimateMode; public:
using climate::ClimateSwingMode; void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
using climate::ClimateFanMode;
template<typename T> /* Stream interface implementation */
class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
int available() override { return this->uart_->available(); }
int read() override {
uint8_t data;
this->uart_->read_byte(&data);
return data;
}
int peek() override {
uint8_t data;
this->uart_->peek_byte(&data);
return data;
}
size_t write(uint8_t data) override {
this->uart_->write_byte(data);
return 1;
}
size_t write(const uint8_t *data, size_t size) override {
this->uart_->write_array(data, size);
return size;
}
void flush() override { this->uart_->flush(); }
protected:
uart::UARTComponent *uart_;
};
template<typename T> class ApplianceBase : public Component {
static_assert(std::is_base_of<dudanov::midea::ApplianceBase, T>::value, static_assert(std::is_base_of<dudanov::midea::ApplianceBase, T>::value,
"T must derive from dudanov::midea::ApplianceBase class"); "T must derive from dudanov::midea::ApplianceBase class");
public: public:
ApplianceBase() { ApplianceBase() {
this->base_.setStream(this); this->base_.setStream(&this->stream_);
this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this)); this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
dudanov::midea::ApplianceBase::setLogger( dudanov::midea::ApplianceBase::setLogger(
[](int level, const char *tag, int line, const String &format, va_list args) { [](int level, const char *tag, int line, const String &format, va_list args) {
esp_log_vprintf_(level, tag, line, format.c_str(), args); esp_log_vprintf_(level, tag, line, format.c_str(), args);
}); });
} }
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS; #ifdef USE_REMOTE_TRANSMITTER
} void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; } #endif
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); } /* UART communication */
void set_uart_parent(uart::UARTComponent *parent) { this->stream_.set_uart(parent); }
void set_period(uint32_t ms) { this->base_.setPeriod(ms); } void set_period(uint32_t ms) { this->base_.setPeriod(ms); }
void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); } void set_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); } void set_request_attempts(uint32_t attempts) { this->base_.setNumAttempts(attempts); }
/* Component methods */
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); }
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
}
void set_beeper_feedback(bool state) { this->base_.setBeeper(state); } void set_beeper_feedback(bool state) { this->base_.setBeeper(state); }
void set_autoconf(bool value) { this->base_.setAutoconf(value); } void set_autoconf(bool value) { this->base_.setAutoconf(value); }
void set_supported_modes(const std::set<ClimateMode> &modes) { this->supported_modes_ = modes; }
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { this->supported_swing_modes_ = modes; }
void set_supported_presets(const std::set<ClimatePreset> &presets) { this->supported_presets_ = presets; }
void set_custom_presets(const std::set<std::string> &presets) { this->supported_custom_presets_ = presets; }
void set_custom_fan_modes(const std::set<std::string> &modes) { this->supported_custom_fan_modes_ = modes; }
virtual void on_status_change() = 0; virtual void on_status_change() = 0;
#ifdef USE_REMOTE_TRANSMITTER
void set_transmitter(remote_transmitter::RemoteTransmitterComponent *transmitter) {
this->transmitter_ = transmitter;
}
void transmit_ir(remote_base::MideaData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
#endif
int available() override { return uart::UARTDevice::available(); }
int read() override { return uart::UARTDevice::read(); }
int peek() override { return uart::UARTDevice::peek(); }
void flush() override { uart::UARTDevice::flush(); }
size_t write(uint8_t data) override { return uart::UARTDevice::write(data); }
protected: protected:
T base_; T base_;
std::set<ClimateMode> supported_modes_{}; UARTStream stream_;
std::set<ClimateSwingMode> supported_swing_modes_{};
std::set<ClimatePreset> supported_presets_{};
std::set<std::string> supported_custom_presets_{};
std::set<std::string> supported_custom_fan_modes_{};
#ifdef USE_REMOTE_TRANSMITTER #ifdef USE_REMOTE_TRANSMITTER
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr}; IrTransmitter transmitter_;
#endif #endif
}; };

View File

@@ -40,9 +40,9 @@ AUTO_LOAD = ["sensor"]
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
CONF_POWER_USAGE = "power_usage" CONF_POWER_USAGE = "power_usage"
CONF_HUMIDITY_SETPOINT = "humidity_setpoint" CONF_HUMIDITY_SETPOINT = "humidity_setpoint"
midea_ns = cg.esphome_ns.namespace("midea") midea_ac_ns = cg.esphome_ns.namespace("midea").namespace("ac")
AirConditioner = midea_ns.class_("AirConditioner", climate.Climate, cg.Component) AirConditioner = midea_ac_ns.class_("AirConditioner", climate.Climate, cg.Component)
Capabilities = midea_ns.namespace("Constants") Capabilities = midea_ac_ns.namespace("Constants")
def templatize(value): def templatize(value):
@@ -156,13 +156,13 @@ CONFIG_SCHEMA = cv.All(
) )
# Actions # Actions
FollowMeAction = midea_ns.class_("FollowMeAction", automation.Action) FollowMeAction = midea_ac_ns.class_("FollowMeAction", automation.Action)
DisplayToggleAction = midea_ns.class_("DisplayToggleAction", automation.Action) DisplayToggleAction = midea_ac_ns.class_("DisplayToggleAction", automation.Action)
SwingStepAction = midea_ns.class_("SwingStepAction", automation.Action) SwingStepAction = midea_ac_ns.class_("SwingStepAction", automation.Action)
BeeperOnAction = midea_ns.class_("BeeperOnAction", automation.Action) BeeperOnAction = midea_ac_ns.class_("BeeperOnAction", automation.Action)
BeeperOffAction = midea_ns.class_("BeeperOffAction", automation.Action) BeeperOffAction = midea_ac_ns.class_("BeeperOffAction", automation.Action)
PowerOnAction = midea_ns.class_("PowerOnAction", automation.Action) PowerOnAction = midea_ac_ns.class_("PowerOnAction", automation.Action)
PowerOffAction = midea_ns.class_("PowerOffAction", automation.Action) PowerOffAction = midea_ac_ns.class_("PowerOffAction", automation.Action)
MIDEA_ACTION_BASE_SCHEMA = cv.Schema( MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
{ {

View File

@@ -7,6 +7,7 @@
namespace esphome { namespace esphome {
namespace midea { namespace midea {
using remote_base::RemoteTransmitterBase;
using IrData = remote_base::MideaData; using IrData = remote_base::MideaData;
class IrFollowMeData : public IrData { class IrFollowMeData : public IrData {
@@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {} IrSpecialData(uint8_t code) : IrData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
}; };
class IrTransmitter {
public:
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
void transmit(IrData &data) {
data.finalize();
auto transmit = this->transmitter_->transmit();
remote_base::MideaProtocol().encode(transmit.get_data(), data);
transmit.perform();
}
protected:
RemoteTransmitterBase *transmitter_{nullptr};
};
} // namespace midea } // namespace midea
} // namespace esphome } // namespace esphome

View File

@@ -129,14 +129,14 @@ async def to_code(config):
return_type=cg.optional.template(float), return_type=cg.optional.template(float),
) )
cg.add(var.set_template(template_)) cg.add(var.set_template(template_))
if CONF_WRITE_LAMBDA in config: if CONF_WRITE_LAMBDA in config:
template_ = await cg.process_lambda( template_ = await cg.process_lambda(
config[CONF_WRITE_LAMBDA], config[CONF_WRITE_LAMBDA],
[ [
(ModbusNumber.operator("ptr"), "item"), (ModbusNumber.operator("ptr"), "item"),
(cg.float_, "x"), (cg.float_, "x"),
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"), (cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
], ],
return_type=cg.optional.template(float), return_type=cg.optional.template(float),
) )
cg.add(var.set_write_template(template_)) cg.add(var.set_write_template(template_))

View File

@@ -14,6 +14,7 @@ from esphome.const import (
CONF_DISCOVERY, CONF_DISCOVERY,
CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_PREFIX,
CONF_DISCOVERY_RETAIN, CONF_DISCOVERY_RETAIN,
CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
CONF_ID, CONF_ID,
CONF_KEEPALIVE, CONF_KEEPALIVE,
CONF_LEVEL, CONF_LEVEL,
@@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
"legacy": MQTTDiscoveryUniqueIdGenerator.MQTT_LEGACY_UNIQUE_ID_GENERATOR,
"mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
}
def validate_config(value): def validate_config(value):
# Populate default fields # Populate default fields
@@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional( cv.Optional(
CONF_DISCOVERY_PREFIX, default="homeassistant" CONF_DISCOVERY_PREFIX, default="homeassistant"
): cv.publish_topic, ): cv.publish_topic,
cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
),
cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
@@ -231,13 +241,22 @@ async def to_code(config):
discovery = config[CONF_DISCOVERY] discovery = config[CONF_DISCOVERY]
discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_retain = config[CONF_DISCOVERY_RETAIN]
discovery_prefix = config[CONF_DISCOVERY_PREFIX] discovery_prefix = config[CONF_DISCOVERY_PREFIX]
discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
if not discovery: if not discovery:
cg.add(var.disable_discovery()) cg.add(var.disable_discovery())
elif discovery == "CLEAN": elif discovery == "CLEAN":
cg.add(var.set_discovery_info(discovery_prefix, discovery_retain, True)) cg.add(
var.set_discovery_info(
discovery_prefix, discovery_unique_id_generator, discovery_retain, True
)
)
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
cg.add(var.set_discovery_info(discovery_prefix, discovery_retain)) cg.add(
var.set_discovery_info(
discovery_prefix, discovery_unique_id_generator, discovery_retain
)
)
cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX])) cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX]))

View File

@@ -535,8 +535,10 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); } void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
void MQTTClientComponent::set_discovery_info(std::string &&prefix, bool retain, bool clean) { void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
bool retain, bool clean) {
this->discovery_info_.prefix = std::move(prefix); this->discovery_info_.prefix = std::move(prefix);
this->discovery_info_.unique_id_generator = unique_id_generator;
this->discovery_info_.retain = retain; this->discovery_info_.retain = retain;
this->discovery_info_.clean = clean; this->discovery_info_.clean = clean;
} }

View File

@@ -55,6 +55,12 @@ struct Availability {
std::string payload_not_available; std::string payload_not_available;
}; };
/// available discovery unique_id generators
enum MQTTDiscoveryUniqueIdGenerator {
MQTT_LEGACY_UNIQUE_ID_GENERATOR = 0,
MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
};
/** Internal struct for MQTT Home Assistant discovery /** Internal struct for MQTT Home Assistant discovery
* *
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>. * See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
@@ -63,6 +69,7 @@ struct MQTTDiscoveryInfo {
std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled. std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled.
bool retain; ///< Whether to retain discovery messages. bool retain; ///< Whether to retain discovery messages.
bool clean; bool clean;
MQTTDiscoveryUniqueIdGenerator unique_id_generator;
}; };
enum MQTTClientState { enum MQTTClientState {
@@ -98,9 +105,11 @@ class MQTTClientComponent : public Component {
* *
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>. * See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
* @param prefix The Home Assistant discovery prefix. * @param prefix The Home Assistant discovery prefix.
* @param unique_id_generator Controls how UniqueId is generated.
* @param retain Whether to retain discovery messages. * @param retain Whether to retain discovery messages.
*/ */
void set_discovery_info(std::string &&prefix, bool retain, bool clean = false); void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
bool clean = false);
/// Get Home Assistant discovery info. /// Get Home Assistant discovery info.
const MQTTDiscoveryInfo &get_discovery_info() const; const MQTTDiscoveryInfo &get_discovery_info() const;
/// Globally disable Home Assistant discovery. /// Globally disable Home Assistant discovery.

View File

@@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() {
if (!unique_id.empty()) { if (!unique_id.empty()) {
root[MQTT_UNIQUE_ID] = unique_id; root[MQTT_UNIQUE_ID] = unique_id;
} else { } else {
// default to almost-unique ID. It's a hack but the only way to get that const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
// gorgeous device registry view. if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_(); char friendly_name_hash[9];
sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
} else {
// default to almost-unique ID. It's a hack but the only way to get that
// gorgeous device registry view.
root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
}
} }
JsonObject &device_info = root.createNestedObject(MQTT_DEVICE); JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);

View File

@@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_(
icon = cv.icon icon = cv.icon
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{ {
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.GenerateID(): cv.declare_id(Number), cv.GenerateID(): cv.declare_id(Number),

View File

@@ -96,6 +96,7 @@ optional<bool> PMSX003Component::check_byte_() {
length_matches = payload_length == 28 || payload_length == 20; length_matches = payload_length == 28 || payload_length == 20;
break; break;
case PMSX003_TYPE_5003T: case PMSX003_TYPE_5003T:
case PMSX003_TYPE_5003S:
length_matches = payload_length == 28; length_matches = payload_length == 28;
break; break;
case PMSX003_TYPE_5003ST: case PMSX003_TYPE_5003ST:
@@ -133,20 +134,25 @@ optional<bool> PMSX003Component::check_byte_() {
void PMSX003Component::parse_data_() { void PMSX003Component::parse_data_() {
switch (this->type_) { switch (this->type_) {
case PMSX003_TYPE_5003ST: { case PMSX003_TYPE_5003ST: {
uint16_t formaldehyde = this->get_16_bit_uint_(28);
float temperature = this->get_16_bit_uint_(30) / 10.0f; float temperature = this->get_16_bit_uint_(30) / 10.0f;
float humidity = this->get_16_bit_uint_(32) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f;
ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", temperature, humidity, ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
formaldehyde);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature); this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr) if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity); this->humidity_sensor_->publish_state(humidity);
// The rest of the PMS5003ST matches the PMS5003S, continue on
}
case PMSX003_TYPE_5003S: {
uint16_t formaldehyde = this->get_16_bit_uint_(28);
ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde);
if (this->formaldehyde_sensor_ != nullptr) if (this->formaldehyde_sensor_ != nullptr)
this->formaldehyde_sensor_->publish_state(formaldehyde); this->formaldehyde_sensor_->publish_state(formaldehyde);
// The rest of the PMS5003ST matches the PMS5003, continue on // The rest of the PMS5003S matches the PMS5003, continue on
} }
case PMSX003_TYPE_X003: { case PMSX003_TYPE_X003: {
uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4); uint16_t pm_1_0_std_concentration = this->get_16_bit_uint_(4);

View File

@@ -11,6 +11,7 @@ enum PMSX003Type {
PMSX003_TYPE_X003 = 0, PMSX003_TYPE_X003 = 0,
PMSX003_TYPE_5003T, PMSX003_TYPE_5003T,
PMSX003_TYPE_5003ST, PMSX003_TYPE_5003ST,
PMSX003_TYPE_5003S,
}; };
class PMSX003Component : public uart::UARTDevice, public Component { class PMSX003Component : public uart::UARTDevice, public Component {

View File

@@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
TYPE_PMSX003 = "PMSX003" TYPE_PMSX003 = "PMSX003"
TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003T = "PMS5003T"
TYPE_PMS5003ST = "PMS5003ST" TYPE_PMS5003ST = "PMS5003ST"
TYPE_PMS5003S = "PMS5003S"
PMSX003Type = pmsx003_ns.enum("PMSX003Type") PMSX003Type = pmsx003_ns.enum("PMSX003Type")
PMSX003_TYPES = { PMSX003_TYPES = {
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST, TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S,
} }
SENSORS_TO_TYPE = { SENSORS_TO_TYPE = {
CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST], CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST], CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S],
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST], CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_FORMALDEHYDE: [TYPE_PMS5003ST], CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S],
} }

View File

@@ -15,7 +15,8 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase web_server_base.WebServerBase
), ),
} },
cv.only_with_arduino,
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)

View File

@@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
dst->set_carrier_frequency(38000); dst->set_carrier_frequency(38000);
dst->item(HEADER_HIGH_US, HEADER_LOW_US); dst->item(HEADER_HIGH_US, HEADER_LOW_US);
for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { for (uint16_t mask = 1; mask; mask <<= 1) {
if (data.address & mask) if (data.address & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else else
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
} }
for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { for (uint16_t mask = 1; mask; mask <<= 1) {
if (data.command & mask) if (data.command & mask)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else else
@@ -41,7 +41,7 @@ optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
return {}; return {};
for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { for (uint16_t mask = 1; mask; mask <<= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
data.address |= mask; data.address |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
@@ -51,7 +51,7 @@ optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
} }
} }
for (uint32_t mask = 1UL << 15; mask != 0; mask >>= 1) { for (uint16_t mask = 1; mask; mask <<= 1) {
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
data.command |= mask; data.command |= mask;
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { } else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {

View File

@@ -32,6 +32,9 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec); void mark_(uint32_t on_time, uint32_t off_time, uint32_t usec);
void space_(uint32_t usec); void space_(uint32_t usec);
void await_target_time_();
uint32_t target_time_;
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32

View File

@@ -33,56 +33,64 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen
*off_time_period = period - *on_time_period; *off_time_period = period - *on_time_period;
} }
void RemoteTransmitterComponent::await_target_time_() {
const uint32_t current_time = micros();
if (this->target_time_ == 0)
this->target_time_ = current_time;
else if (this->target_time_ > current_time)
delayMicroseconds(this->target_time_ - current_time);
}
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) { this->await_target_time_();
this->pin_->digital_write(true); this->pin_->digital_write(true);
delayMicroseconds(usec);
this->pin_->digital_write(false); const uint32_t target = this->target_time_ + usec;
return; if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
} while (true) { // Modulate with carrier frequency
this->target_time_ += on_time;
const uint32_t start_time = micros(); if (this->target_time_ >= target)
uint32_t current_time = start_time; break;
this->await_target_time_();
while (current_time - start_time < usec) { this->pin_->digital_write(false);
const uint32_t elapsed = current_time - start_time;
this->pin_->digital_write(true); this->target_time_ += off_time;
if (this->target_time_ >= target)
delayMicroseconds(std::min(on_time, usec - elapsed)); break;
this->pin_->digital_write(false); this->await_target_time_();
if (elapsed + on_time >= usec) this->pin_->digital_write(true);
return; }
delayMicroseconds(std::min(usec - elapsed - on_time, off_time));
current_time = micros();
} }
this->target_time_ = target;
} }
void RemoteTransmitterComponent::space_(uint32_t usec) { void RemoteTransmitterComponent::space_(uint32_t usec) {
this->await_target_time_();
this->pin_->digital_write(false); this->pin_->digital_write(false);
delayMicroseconds(usec); this->target_time_ += usec;
} }
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
ESP_LOGD(TAG, "Sending remote code..."); ESP_LOGD(TAG, "Sending remote code...");
uint32_t on_time, off_time; uint32_t on_time, off_time;
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
this->target_time_ = 0;
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
{ for (int32_t item : this->temp_.get_data()) {
InterruptLock lock; if (item > 0) {
for (int32_t item : this->temp_.get_data()) { const auto length = uint32_t(item);
if (item > 0) { this->mark_(on_time, off_time, length);
const auto length = uint32_t(item); } else {
this->mark_(on_time, off_time, length); const auto length = uint32_t(-item);
} else { this->space_(length);
const auto length = uint32_t(-item);
this->space_(length);
}
App.feed_wdt();
} }
App.feed_wdt();
} }
this->await_target_time_(); // wait for duration of last pulse
this->pin_->digital_write(false);
if (i + 1 < send_times) if (i + 1 < send_times)
delayMicroseconds(send_wait); this->target_time_ += send_wait;
} }
} }

View File

@@ -52,7 +52,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
if (action == RF_CODE_LEARN_OK) if (action == RF_CODE_LEARN_OK)
ESP_LOGD(TAG, "Learning success"); ESP_LOGD(TAG, "Learning success");
ESP_LOGD(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low, ESP_LOGI(TAG, "Received RFBridge Code: sync=0x%04X low=0x%04X high=0x%04X code=0x%06X", data.sync, data.low,
data.high, data.code); data.high, data.code);
this->data_callback_.call(data); this->data_callback_.call(data);
break; break;
@@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
data.code += next_byte; data.code += next_byte;
} }
ESP_LOGD(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length, ESP_LOGI(TAG, "Received RFBridge Advanced Code: length=0x%02X protocol=0x%02X code=0x%s", data.length,
data.protocol, data.code.c_str()); data.protocol, data.code.c_str());
this->advanced_data_callback_.call(data); this->advanced_data_callback_.call(data);
break; break;
@@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
str += " "; str += " ";
} }
} }
ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str()); ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str());
break; break;
} }
default: default:
@@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() {
} }
void RFBridgeComponent::start_advanced_sniffing() { void RFBridgeComponent::start_advanced_sniffing() {
ESP_LOGD(TAG, "Advanced Sniffing on"); ESP_LOGI(TAG, "Advanced Sniffing on");
this->write(RF_CODE_START); this->write(RF_CODE_START);
this->write(RF_CODE_SNIFFING_ON); this->write(RF_CODE_SNIFFING_ON);
this->write(RF_CODE_STOP); this->write(RF_CODE_STOP);
@@ -194,7 +194,7 @@ void RFBridgeComponent::start_advanced_sniffing() {
} }
void RFBridgeComponent::stop_advanced_sniffing() { void RFBridgeComponent::stop_advanced_sniffing() {
ESP_LOGD(TAG, "Advanced Sniffing off"); ESP_LOGI(TAG, "Advanced Sniffing off");
this->write(RF_CODE_START); this->write(RF_CODE_START);
this->write(RF_CODE_SNIFFING_OFF); this->write(RF_CODE_SNIFFING_OFF);
this->write(RF_CODE_STOP); this->write(RF_CODE_STOP);
@@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_sniffing() {
} }
void RFBridgeComponent::start_bucket_sniffing() { void RFBridgeComponent::start_bucket_sniffing() {
ESP_LOGD(TAG, "Raw Bucket Sniffing on"); ESP_LOGI(TAG, "Raw Bucket Sniffing on");
this->write(RF_CODE_START); this->write(RF_CODE_START);
this->write(RF_CODE_RFIN_BUCKET); this->write(RF_CODE_RFIN_BUCKET);
this->write(RF_CODE_STOP); this->write(RF_CODE_STOP);

View File

@@ -11,6 +11,7 @@ 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_ID1[2] = {0x36, 0x7C};
static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02}; 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_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 uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
void SDP3XComponent::update() { this->read_pressure_(); } void SDP3XComponent::update() { this->read_pressure_(); }
@@ -26,46 +27,69 @@ void SDP3XComponent::setup() {
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
} }
delayMicroseconds(20000); this->set_timeout(20, [this] {
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
this->mark_failed();
return;
}
if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
this->mark_failed();
return;
}
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) { uint8_t data[18];
ESP_LOGE(TAG, "Read ID1 SDP3X failed!"); if (this->read(data, 18) != i2c::ERROR_OK) {
this->mark_failed(); ESP_LOGE(TAG, "Read ID SDP3X failed!");
return; this->mark_failed();
} return;
if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) { }
ESP_LOGE(TAG, "Read ID2 SDP3X failed!"); if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
this->mark_failed(); ESP_LOGE(TAG, "CRC ID SDP3X failed!");
return; this->mark_failed();
} return;
}
uint8_t data[18]; // SDP8xx
if (this->read(data, 18) != i2c::ERROR_OK) { // ref:
ESP_LOGE(TAG, "Read ID SDP3X failed!"); // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
this->mark_failed(); if (data[2] == 0x02) {
return; switch (data[3]) {
} case 0x01: // SDP800-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
break;
case 0x0A: // SDP810-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP810-500Pa");
break;
case 0x04: // SDP801-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP801-500Pa");
break;
case 0x0D: // SDP811-500Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP811-500Pa");
break;
case 0x02: // SDP800-125Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP800-125Pa");
break;
case 0x0B: // SDP810-125Pa
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
break;
}
} else if (data[2] == 0x01) {
if (data[3] == 0x01) {
ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
} else if (data[3] == 0x02) {
ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
}
}
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) { if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "CRC ID SDP3X failed!"); ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
this->mark_failed(); this->mark_failed();
return; return;
} }
ESP_LOGCONFIG(TAG, "SDP3X started!");
if (data[3] == 0x01) { });
ESP_LOGCONFIG(TAG, "SDP3X is SDP31");
pressure_scale_factor_ = 60.0f * 100.0f; // Scale factors converted to hPa per count
} else if (data[3] == 0x02) {
ESP_LOGCONFIG(TAG, "SDP3X is SDP32");
pressure_scale_factor_ = 240.0f * 100.0f;
}
if (this->write(SDP3X_START_DP_AVG, 2) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, "SDP3X started!");
} }
void SDP3XComponent::dump_config() { void SDP3XComponent::dump_config() {
LOG_SENSOR(" ", "SDP3X", this); LOG_SENSOR(" ", "SDP3X", this);
@@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() {
} }
int16_t pressure_raw = encode_uint16(data[0], data[1]); int16_t pressure_raw = encode_uint16(data[0], data[1]);
float pressure = pressure_raw / pressure_scale_factor_; int16_t temperature_raw = encode_uint16(data[3], data[4]);
ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_); int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
// 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,
temperature_raw);
ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure); ESP_LOGD(TAG, "Got Pressure=%.3f hPa", pressure);
this->publish_state(pressure); this->publish_state(pressure);

View File

@@ -7,6 +7,8 @@
namespace esphome { namespace esphome {
namespace sdp3x { namespace sdp3x {
enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
public: public:
/// Schedule temperature+pressure readings. /// Schedule temperature+pressure readings.
@@ -16,14 +18,14 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_measurement_mode(MeasurementMode mode) { measurement_mode_ = mode; }
protected: protected:
/// Internal method to read the pressure from the component after it has been scheduled. /// Internal method to read the pressure from the component after it has been scheduled.
void read_pressure_(); void read_pressure_();
bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum); bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
MeasurementMode measurement_mode_;
float pressure_scale_factor_ = 0.0f; // hPa per count
}; };
} // namespace sdp3x } // namespace sdp3x

View File

@@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"]
sdp3x_ns = cg.esphome_ns.namespace("sdp3x") sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice) SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
MeasurementMode = sdp3x_ns.enum("MeasurementMode")
MEASUREMENT_MODE = {
"mass_flow": MeasurementMode.MASS_FLOW_AVG,
"differential_pressure": MeasurementMode.DP_AVG,
}
CONF_MEASUREMENT_MODE = "measurement_mode"
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
sensor.sensor_schema( sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL, unit_of_measurement=UNIT_HECTOPASCAL,
@@ -24,6 +32,9 @@ CONFIG_SCHEMA = (
.extend( .extend(
{ {
cv.GenerateID(): cv.declare_id(SDP3XComponent), cv.GenerateID(): cv.declare_id(SDP3XComponent),
cv.Optional(
CONF_MEASUREMENT_MODE, default="differential_pressure"
): cv.enum(MEASUREMENT_MODE),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@@ -36,3 +47,4 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
await sensor.register_sensor(var, config) await sensor.register_sensor(var, config)
cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE]))

View File

@@ -30,7 +30,7 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
icon = cv.icon icon = cv.icon
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{ {
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select), cv.GenerateID(): cv.declare_id(Select),

View File

@@ -141,12 +141,16 @@ void SenseAirComponent::abc_get_period() {
} }
bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) { bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
// Verify we have somewhere to store the response
if (response == nullptr) {
return false;
}
// Write wake up byte required by some S8 sensor models
this->write_byte(0);
this->flush(); this->flush();
delay(5);
this->write_array(command, SENSEAIR_REQUEST_LENGTH); this->write_array(command, SENSEAIR_REQUEST_LENGTH);
if (response == nullptr)
return true;
bool ret = this->read_array(response, response_length); bool ret = this->read_array(response, response_length);
this->flush(); this->flush();
return ret; return ret;

View File

@@ -71,7 +71,7 @@ void SNTPComponent::loop() {
if (!time.is_valid()) if (!time.is_valid())
return; return;
ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour, ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
time.minute, time.second); time.minute, time.second);
this->time_sync_callback_.call(); this->time_sync_callback_.call();
this->has_time_ = true; this->has_time_ = true;

View File

@@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer
void TCA9548AComponent::setup() { void TCA9548AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); ESP_LOGCONFIG(TAG, "Setting up TCA9548A...");
uint8_t status = 0; uint8_t status = 0;
if (this->read_register(0x00, &status, 1) != i2c::ERROR_OK) { if (this->read(&status, 1) != i2c::ERROR_OK) {
ESP_LOGI(TAG, "TCA9548A failed"); ESP_LOGI(TAG, "TCA9548A failed");
this->mark_failed(); this->mark_failed();
return; return;

View File

@@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice {
public: public:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::IO; }
void update(); void update();
i2c::ErrorCode switch_to_channel(uint8_t channel); i2c::ErrorCode switch_to_channel(uint8_t channel);

View File

@@ -35,6 +35,9 @@ def validate(config):
raise cv.Invalid("initial_value cannot be used with lambda") raise cv.Invalid("initial_value cannot be used with lambda")
if CONF_RESTORE_VALUE in config: if CONF_RESTORE_VALUE in config:
raise cv.Invalid("restore_value cannot be used with lambda") raise cv.Invalid("restore_value cannot be used with lambda")
elif CONF_INITIAL_VALUE not in config:
config[CONF_INITIAL_VALUE] = config[CONF_MIN_VALUE]
if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
raise cv.Invalid( raise cv.Invalid(
"Either optimistic mode must be enabled, or set_action must be set, to handle the number being set." "Either optimistic mode must be enabled, or set_action must be set, to handle the number being set."
@@ -80,8 +83,7 @@ async def to_code(config):
else: else:
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if CONF_INITIAL_VALUE in config: cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
if CONF_RESTORE_VALUE in config: if CONF_RESTORE_VALUE in config:
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))

View File

@@ -35,7 +35,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
} }
auto time = this->now(); auto time = this->now();
ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour, ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
time.minute, time.second); time.minute, time.second);
this->time_sync_callback_.call(); this->time_sync_callback_.call();

View File

@@ -3,6 +3,7 @@ from typing import Optional
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv import esphome.final_validate as fv
from esphome.yaml_util import make_data_base
from esphome import pins, automation from esphome import pins, automation
from esphome.const import ( from esphome.const import (
CONF_BAUD_RATE, CONF_BAUD_RATE,
@@ -14,6 +15,17 @@ from esphome.const import (
CONF_DATA, CONF_DATA,
CONF_RX_BUFFER_SIZE, CONF_RX_BUFFER_SIZE,
CONF_INVERT, CONF_INVERT,
CONF_TRIGGER_ID,
CONF_SEQUENCE,
CONF_TIMEOUT,
CONF_DEBUG,
CONF_DIRECTION,
CONF_AFTER,
CONF_BYTES,
CONF_DELIMITER,
CONF_DUMMY_RECEIVER,
CONF_DUMMY_RECEIVER_ID,
CONF_LAMBDA,
) )
from esphome.core import CORE from esphome.core import CORE
@@ -31,6 +43,8 @@ ESP8266UartComponent = uart_ns.class_(
UARTDevice = uart_ns.class_("UARTDevice") UARTDevice = uart_ns.class_("UARTDevice")
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component)
MULTI_CONF = True MULTI_CONF = True
@@ -75,6 +89,59 @@ CONF_STOP_BITS = "stop_bits"
CONF_DATA_BITS = "data_bits" CONF_DATA_BITS = "data_bits"
CONF_PARITY = "parity" CONF_PARITY = "parity"
UARTDirection = uart_ns.enum("UARTDirection")
UART_DIRECTIONS = {
"RX": UARTDirection.UART_DIRECTION_RX,
"TX": UARTDirection.UART_DIRECTION_TX,
"BOTH": UARTDirection.UART_DIRECTION_BOTH,
}
# The reason for having CONF_BYTES at 150 by default:
#
# The log message buffer size is 512 bytes by default. About 35 bytes are
# used for the log prefix. That leaves us with 477 bytes for logging data.
# The default log output is hex, which uses 3 characters per represented
# byte (2 hex chars + 1 separator). That means that 477 / 3 = 159 bytes
# can be represented in a single log line. Using 150, because people love
# round numbers.
AFTER_DEFAULTS = {CONF_BYTES: 150, CONF_TIMEOUT: "100ms"}
# By default, log in hex format when no specific sequence is provided.
DEFAULT_DEBUG_OUTPUT = "UARTDebug::log_hex(direction, bytes, ':');"
DEFAULT_SEQUENCE = [{CONF_LAMBDA: make_data_base(DEFAULT_DEBUG_OUTPUT)}]
def maybe_empty_debug(value):
if value is None:
value = {}
return DEBUG_SCHEMA(value)
DEBUG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UARTDebugger),
cv.Optional(CONF_DIRECTION, default="BOTH"): cv.enum(
UART_DIRECTIONS, upper=True
),
cv.Optional(CONF_AFTER, default=AFTER_DEFAULTS): cv.Schema(
{
cv.Optional(
CONF_BYTES, default=AFTER_DEFAULTS[CONF_BYTES]
): cv.validate_bytes,
cv.Optional(
CONF_TIMEOUT, default=AFTER_DEFAULTS[CONF_TIMEOUT]
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_DELIMITER): cv.templatable(validate_raw_data),
}
),
cv.Optional(
CONF_SEQUENCE, default=DEFAULT_SEQUENCE
): automation.validate_automation(),
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
cv.GenerateID(CONF_DUMMY_RECEIVER_ID): cv.declare_id(UARTDummyReceiver),
}
)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@@ -91,12 +158,38 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INVERT): cv.invalid( cv.Optional(CONF_INVERT): cv.invalid(
"This option has been removed. Please instead use invert in the tx/rx pin schemas." "This option has been removed. Please instead use invert in the tx/rx pin schemas."
), ),
cv.Optional(CONF_DEBUG): maybe_empty_debug,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN),
) )
async def debug_to_code(config, parent):
trigger = cg.new_Pvariable(config[CONF_TRIGGER_ID], parent)
await cg.register_component(trigger, config)
for action in config[CONF_SEQUENCE]:
await automation.build_automation(
trigger,
[(UARTDirection, "direction"), (cg.std_vector.template(cg.uint8), "bytes")],
action,
)
cg.add(trigger.set_direction(config[CONF_DIRECTION]))
after = config[CONF_AFTER]
cg.add(trigger.set_after_bytes(after[CONF_BYTES]))
cg.add(trigger.set_after_timeout(after[CONF_TIMEOUT]))
if CONF_DELIMITER in after:
data = after[CONF_DELIMITER]
if isinstance(data, bytes):
data = list(data)
for byte in after[CONF_DELIMITER]:
cg.add(trigger.add_delimiter_byte(byte))
if config[CONF_DUMMY_RECEIVER]:
dummy = cg.new_Pvariable(config[CONF_DUMMY_RECEIVER_ID], parent)
await cg.register_component(dummy, {})
cg.add_define("USE_UART_DEBUGGER")
async def to_code(config): async def to_code(config):
cg.add_global(uart_ns.using) cg.add_global(uart_ns.using)
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
@@ -115,6 +208,9 @@ async def to_code(config):
cg.add(var.set_data_bits(config[CONF_DATA_BITS])) cg.add(var.set_data_bits(config[CONF_DATA_BITS]))
cg.add(var.set_parity(config[CONF_PARITY])) cg.add(var.set_parity(config[CONF_PARITY]))
if CONF_DEBUG in config:
await debug_to_code(config[CONF_DEBUG], var)
# A schema to use for all UART devices, all UART integrations must extend this! # A schema to use for all UART devices, all UART integrations must extend this!
UART_DEVICE_SCHEMA = cv.Schema( UART_DEVICE_SCHEMA = cv.Schema(

View File

@@ -45,17 +45,17 @@ class UARTDevice {
// Compat APIs // Compat APIs
int read() { int read() {
uint8_t data; uint8_t data;
if (!read_byte(&data)) if (!this->read_byte(&data))
return -1; return -1;
return data; return data;
} }
size_t write(uint8_t data) { size_t write(uint8_t data) {
write_byte(data); this->write_byte(data);
return 1; return 1;
} }
int peek() { int peek() {
uint8_t data; uint8_t data;
if (!peek_byte(&data)) if (!this->peek_byte(&data))
return -1; return -1;
return data; return data;
} }

View File

@@ -2,9 +2,13 @@
#include <vector> #include <vector>
#include <cstring> #include <cstring>
#include "esphome/core/defines.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_UART_DEBUGGER
#include "esphome/core/automation.h"
#endif
namespace esphome { namespace esphome {
namespace uart { namespace uart {
@@ -15,6 +19,14 @@ enum UARTParityOptions {
UART_CONFIG_PARITY_ODD, UART_CONFIG_PARITY_ODD,
}; };
#ifdef USE_UART_DEBUGGER
enum UARTDirection {
UART_DIRECTION_RX,
UART_DIRECTION_TX,
UART_DIRECTION_BOTH,
};
#endif
const LogString *parity_to_str(UARTParityOptions parity); const LogString *parity_to_str(UARTParityOptions parity);
class UARTComponent { class UARTComponent {
@@ -50,6 +62,12 @@ class UARTComponent {
void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; }
uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_baud_rate() const { return baud_rate_; }
#ifdef USE_UART_DEBUGGER
void add_debug_callback(std::function<void(UARTDirection, uint8_t)> &&callback) {
this->debug_callback_.add(std::move(callback));
}
#endif
protected: protected:
virtual void check_logger_conflict() = 0; virtual void check_logger_conflict() = 0;
bool check_read_timeout_(size_t len = 1); bool check_read_timeout_(size_t len = 1);
@@ -61,6 +79,9 @@ class UARTComponent {
uint8_t stop_bits_; uint8_t stop_bits_;
uint8_t data_bits_; uint8_t data_bits_;
UARTParityOptions parity_; UARTParityOptions parity_;
#ifdef USE_UART_DEBUGGER
CallbackManager<void(UARTDirection, uint8_t)> debug_callback_{};
#endif
}; };
} // namespace uart } // namespace uart

View File

@@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() {
void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) { void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) {
this->hw_serial_->write(data, len); this->hw_serial_->write(data, len);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
} }
#endif
} }
bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) { bool ESP32ArduinoUARTComponent::peek_byte(uint8_t *data) {
if (!this->check_read_timeout_()) if (!this->check_read_timeout_())
return false; return false;
*data = this->hw_serial_->peek(); *data = this->hw_serial_->peek();
return true; return true;
} }
bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) { bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) {
if (!this->check_read_timeout_(len)) if (!this->check_read_timeout_(len))
return false; return false;
this->hw_serial_->readBytes(data, len); this->hw_serial_->readBytes(data, len);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
} }
#endif
return true; return true;
} }
int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); } int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); }
void ESP32ArduinoUARTComponent::flush() { void ESP32ArduinoUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing..."); ESP_LOGVV(TAG, " Flushing...");

View File

@@ -130,9 +130,11 @@ void ESP8266UartComponent::write_array(const uint8_t *data, size_t len) {
for (size_t i = 0; i < len; i++) for (size_t i = 0; i < len; i++)
this->sw_serial_->write_byte(data[i]); this->sw_serial_->write_byte(data[i]);
} }
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
} }
#endif
} }
bool ESP8266UartComponent::peek_byte(uint8_t *data) { bool ESP8266UartComponent::peek_byte(uint8_t *data) {
if (!this->check_read_timeout_()) if (!this->check_read_timeout_())
@@ -153,10 +155,11 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
for (size_t i = 0; i < len; i++) for (size_t i = 0; i < len; i++)
data[i] = this->sw_serial_->read_byte(); data[i] = this->sw_serial_->read_byte();
} }
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
} }
#endif
return true; return true;
} }
int ESP8266UartComponent::available() { int ESP8266UartComponent::available() {

View File

@@ -130,10 +130,13 @@ void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
xSemaphoreTake(this->lock_, portMAX_DELAY); xSemaphoreTake(this->lock_, portMAX_DELAY);
uart_write_bytes(this->uart_num_, data, len); uart_write_bytes(this->uart_num_, data, len);
xSemaphoreGive(this->lock_); xSemaphoreGive(this->lock_);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
} }
#endif
} }
bool IDFUARTComponent::peek_byte(uint8_t *data) { bool IDFUARTComponent::peek_byte(uint8_t *data) {
if (!this->check_read_timeout_()) if (!this->check_read_timeout_())
return false; return false;
@@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) {
xSemaphoreGive(this->lock_); xSemaphoreGive(this->lock_);
return true; return true;
} }
bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
size_t length_to_read = len; size_t length_to_read = len;
if (!this->check_read_timeout_(len)) if (!this->check_read_timeout_(len))
@@ -165,12 +169,12 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
} }
if (length_to_read > 0) if (length_to_read > 0)
uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS); uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS);
xSemaphoreGive(this->lock_); xSemaphoreGive(this->lock_);
#ifdef USE_UART_DEBUGGER
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
ESP_LOGVV(TAG, " Read 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data[i]), data[i]); this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
} }
#endif
return true; return true;
} }

View File

@@ -0,0 +1,193 @@
#include "esphome/core/defines.h"
#ifdef USE_UART_DEBUGGER
#include <vector>
#include "uart_debugger.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uart {
static const char *const TAG = "uart_debug";
UARTDebugger::UARTDebugger(UARTComponent *parent) {
parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) {
if (!this->is_my_direction_(direction) || this->is_recursive_()) {
return;
}
this->trigger_after_direction_change_(direction);
this->store_byte_(direction, byte);
this->trigger_after_delimiter_(byte);
this->trigger_after_bytes_();
});
}
void UARTDebugger::loop() { this->trigger_after_timeout_(); }
bool UARTDebugger::is_my_direction_(UARTDirection direction) {
return this->for_direction_ == UART_DIRECTION_BOTH || this->for_direction_ == direction;
}
bool UARTDebugger::is_recursive_() { return this->is_triggering_; }
void UARTDebugger::trigger_after_direction_change_(UARTDirection direction) {
if (this->has_buffered_bytes_() && this->for_direction_ == UART_DIRECTION_BOTH &&
this->last_direction_ != direction) {
this->fire_trigger_();
}
}
void UARTDebugger::store_byte_(UARTDirection direction, uint8_t byte) {
this->bytes_.push_back(byte);
this->last_direction_ = direction;
this->last_time_ = millis();
}
void UARTDebugger::trigger_after_delimiter_(uint8_t byte) {
if (this->after_delimiter_.empty() || !this->has_buffered_bytes_()) {
return;
}
if (this->after_delimiter_[this->after_delimiter_pos_] != byte) {
this->after_delimiter_pos_ = 0;
return;
}
this->after_delimiter_pos_++;
if (this->after_delimiter_pos_ == this->after_delimiter_.size()) {
this->fire_trigger_();
this->after_delimiter_pos_ = 0;
}
}
void UARTDebugger::trigger_after_bytes_() {
if (this->has_buffered_bytes_() && this->after_bytes_ > 0 && this->bytes_.size() >= this->after_bytes_) {
this->fire_trigger_();
}
}
void UARTDebugger::trigger_after_timeout_() {
if (this->has_buffered_bytes_() && this->after_timeout_ > 0 && millis() - this->last_time_ >= this->after_timeout_) {
this->fire_trigger_();
}
}
bool UARTDebugger::has_buffered_bytes_() { return !this->bytes_.empty(); }
void UARTDebugger::fire_trigger_() {
this->is_triggering_ = true;
trigger(this->last_direction_, this->bytes_);
this->bytes_.clear();
this->is_triggering_ = false;
}
void UARTDummyReceiver::loop() {
// Reading up to a limited number of bytes, to make sure that this loop()
// won't lock up the system on a continuous incoming stream of bytes.
uint8_t data;
int count = 50;
while (this->available() && count--) {
this->read_byte(&data);
}
}
void UARTDebug::log_hex(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator) {
std::string res;
if (direction == UART_DIRECTION_RX) {
res += "<<< ";
} else {
res += ">>> ";
}
size_t len = bytes.size();
char buf[5];
for (size_t i = 0; i < len; i++) {
if (i > 0) {
res += separator;
}
sprintf(buf, "%02X", bytes[i]);
res += buf;
}
ESP_LOGD(TAG, "%s", res.c_str());
}
void UARTDebug::log_string(UARTDirection direction, std::vector<uint8_t> bytes) {
std::string res;
if (direction == UART_DIRECTION_RX) {
res += "<<< \"";
} else {
res += ">>> \"";
}
size_t len = bytes.size();
char buf[5];
for (size_t i = 0; i < len; i++) {
if (bytes[i] == 7) {
res += "\\a";
} else if (bytes[i] == 8) {
res += "\\b";
} else if (bytes[i] == 9) {
res += "\\t";
} else if (bytes[i] == 10) {
res += "\\n";
} else if (bytes[i] == 11) {
res += "\\v";
} else if (bytes[i] == 12) {
res += "\\f";
} else if (bytes[i] == 13) {
res += "\\r";
} else if (bytes[i] == 27) {
res += "\\e";
} else if (bytes[i] == 34) {
res += "\\\"";
} else if (bytes[i] == 39) {
res += "\\'";
} else if (bytes[i] == 92) {
res += "\\\\";
} else if (bytes[i] < 32 || bytes[i] > 127) {
sprintf(buf, "\\x%02X", bytes[i]);
res += buf;
} else {
res += bytes[i];
}
}
res += '"';
ESP_LOGD(TAG, "%s", res.c_str());
}
void UARTDebug::log_int(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator) {
std::string res;
size_t len = bytes.size();
if (direction == UART_DIRECTION_RX) {
res += "<<< ";
} else {
res += ">>> ";
}
for (size_t i = 0; i < len; i++) {
if (i > 0) {
res += separator;
}
res += to_string(bytes[i]);
}
ESP_LOGD(TAG, "%s", res.c_str());
}
void UARTDebug::log_binary(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator) {
std::string res;
size_t len = bytes.size();
if (direction == UART_DIRECTION_RX) {
res += "<<< ";
} else {
res += ">>> ";
}
char buf[20];
for (size_t i = 0; i < len; i++) {
if (i > 0) {
res += separator;
}
sprintf(buf, "0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(bytes[i]), bytes[i]);
res += buf;
}
ESP_LOGD(TAG, "%s", res.c_str());
}
} // namespace uart
} // namespace esphome
#endif

View File

@@ -0,0 +1,101 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_UART_DEBUGGER
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "uart.h"
#include "uart_component.h"
namespace esphome {
namespace uart {
/// The UARTDebugger class adds debugging support to a UART bus.
///
/// It accumulates bytes that travel over the UART bus and triggers one or
/// more actions that can log the data at an appropriate time. What
/// 'appropriate time' means exactly, is determined by a number of
/// configurable constraints. E.g. when a given number of bytes is gathered
/// and/or when no more data has been seen for a given time interval.
class UARTDebugger : public Component, public Trigger<UARTDirection, std::vector<uint8_t>> {
public:
explicit UARTDebugger(UARTComponent *parent);
void loop() override;
/// Set the direction in which to inspect the bytes: incoming, outgoing
/// or both. When debugging in both directions, logging will be triggered
/// when the direction of the data stream changes.
void set_direction(UARTDirection direction) { this->for_direction_ = direction; }
/// Set the maximum number of bytes to accumulate. When the number of bytes
/// is reached, logging will be triggered.
void set_after_bytes(size_t size) { this->after_bytes_ = size; }
/// Set a timeout for the data stream. When no new bytes are seen during
/// this timeout, logging will be triggered.
void set_after_timeout(uint32_t timeout) { this->after_timeout_ = timeout; }
/// Add a delimiter byte. This can be called multiple times to setup a
/// multi-byte delimiter (a typical example would be '\r\n').
/// When the constructued byte sequence is found in the data stream,
/// logging will be triggered.
void add_delimiter_byte(uint8_t byte) { this->after_delimiter_.push_back(byte); }
protected:
UARTDirection for_direction_;
UARTDirection last_direction_{};
std::vector<uint8_t> bytes_{};
size_t after_bytes_;
uint32_t after_timeout_;
uint32_t last_time_{};
std::vector<uint8_t> after_delimiter_{};
size_t after_delimiter_pos_{};
bool is_triggering_{false};
bool is_my_direction_(UARTDirection direction);
bool is_recursive_();
void store_byte_(UARTDirection direction, uint8_t byte);
void trigger_after_direction_change_(UARTDirection direction);
void trigger_after_delimiter_(uint8_t byte);
void trigger_after_bytes_();
void trigger_after_timeout_();
bool has_buffered_bytes_();
void fire_trigger_();
};
/// This UARTDevice is used by the serial debugger to read data from a
/// serial interface when the 'dummy_receiver' option is enabled.
/// The data are not stored, nor processed. This is most useful when the
/// debugger is used to reverse engineer a serial protocol, for which no
/// specific UARTDevice implementation exists (yet), but for which the
/// incoming bytes must be read to drive the debugger.
class UARTDummyReceiver : public Component, public UARTDevice {
public:
UARTDummyReceiver(UARTComponent *parent) : UARTDevice(parent) {}
void loop() override;
};
/// This class contains some static methods, that can be used to easily
/// create a logging action for the debugger.
class UARTDebug {
public:
/// Log the bytes as hex values, separated by the provided separator
/// character.
static void log_hex(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator);
/// Log the bytes as string values, escaping unprintable characters.
static void log_string(UARTDirection direction, std::vector<uint8_t> bytes);
/// Log the bytes as integer values, separated by the provided separator
/// character.
static void log_int(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator);
/// Log the bytes as '<binary> (<hex>)' values, separated by the provided
/// separator.
static void log_binary(UARTDirection direction, std::vector<uint8_t> bytes, uint8_t separator);
};
} // namespace uart
} // namespace esphome
#endif

View File

@@ -1183,7 +1183,7 @@ static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = {
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,

View File

@@ -28,4 +28,4 @@ async def to_code(config):
cg.add_library("FS", None) cg.add_library("FS", None)
cg.add_library("Update", None) cg.add_library("Update", None)
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1") cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0")

View File

@@ -57,38 +57,46 @@ void IRAM_ATTR ZaSensorStore::interrupt(ZaSensorStore *arg) {
void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) { void IRAM_ATTR ZaSensorStore::set_data_(ZaMessage *message) {
switch (message->type) { switch (message->type) {
case HUMIDITY: case HUMIDITY:
this->humidity = (message->value > 10000) ? NAN : (message->value / 100.0f); this->humidity = message->value;
break; break;
case TEMPERATURE: case TEMPERATURE:
this->temperature = (message->value > 5970) ? NAN : (message->value / 16.0f - 273.15f); this->temperature = message->value;
break; break;
case CO2: case CO2:
this->co2 = (message->value > 10000) ? NAN : message->value; this->co2 = message->value;
break;
default:
break; break;
} }
} }
bool ZyAuraSensor::publish_state_(sensor::Sensor *sensor, float *value) { bool ZyAuraSensor::publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value) {
// Sensor doesn't added to configuration // Sensor wasn't added to configuration
if (sensor == nullptr) { if (sensor == nullptr) {
return true; return true;
} }
sensor->publish_state(*value); float value = NAN;
switch (data_type) {
case HUMIDITY:
value = (*data_value > 10000) ? NAN : (*data_value / 100.0f);
break;
case TEMPERATURE:
value = (*data_value > 5970) ? NAN : (*data_value / 16.0f - 273.15f);
break;
case CO2:
value = (*data_value > 10000) ? NAN : *data_value;
break;
}
sensor->publish_state(value);
// Sensor reported wrong value // Sensor reported wrong value
if (std::isnan(*value)) { if (std::isnan(value)) {
ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?"); ESP_LOGW(TAG, "Sensor reported invalid data. Is the update interval too small?");
this->status_set_warning(); this->status_set_warning();
return false; return false;
} }
*value = NAN; *data_value = -1;
return true; return true;
} }
@@ -104,9 +112,9 @@ void ZyAuraSensor::dump_config() {
} }
void ZyAuraSensor::update() { void ZyAuraSensor::update() {
bool co2_result = this->publish_state_(this->co2_sensor_, &this->store_.co2); bool co2_result = this->publish_state_(CO2, this->co2_sensor_, &this->store_.co2);
bool temperature_result = this->publish_state_(this->temperature_sensor_, &this->store_.temperature); bool temperature_result = this->publish_state_(TEMPERATURE, this->temperature_sensor_, &this->store_.temperature);
bool humidity_result = this->publish_state_(this->humidity_sensor_, &this->store_.humidity); bool humidity_result = this->publish_state_(HUMIDITY, this->humidity_sensor_, &this->store_.humidity);
if (co2_result && temperature_result && humidity_result) { if (co2_result && temperature_result && humidity_result) {
this->status_clear_warning(); this->status_clear_warning();

View File

@@ -42,9 +42,9 @@ class ZaDataProcessor {
class ZaSensorStore { class ZaSensorStore {
public: public:
float co2 = NAN; uint16_t co2 = -1;
float temperature = NAN; uint16_t temperature = -1;
float humidity = NAN; uint16_t humidity = -1;
void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data); void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
static void interrupt(ZaSensorStore *arg); static void interrupt(ZaSensorStore *arg);
@@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
bool publish_state_(sensor::Sensor *sensor, float *value); bool publish_state_(ZaDataType data_type, sensor::Sensor *sensor, uint16_t *data_value);
}; };
} // namespace zyaura } // namespace zyaura

View File

@@ -296,9 +296,11 @@ def icon(value):
value = string_strict(value) value = string_strict(value)
if not value: if not value:
return value return value
if value.startswith("mdi:"): if re.match("^[\\w\\-]+:[\\w\\-]+$", value):
return value return value
raise Invalid('Icons should start with prefix "mdi:"') raise Invalid(
'Icons must match the format "[icon pack]:[icon]", e.g. "mdi:home-assistant"'
)
def boolean(value): def boolean(value):

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2021.11.0-dev" __version__ = "2021.12.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -8,31 +8,11 @@ PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266" PLATFORM_ESP8266 = "esp8266"
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
TARGET_FRAMEWORKS = ["arduino", "esp-idf"]
# See also https://github.com/platformio/platform-espressif8266/releases
ARDUINO_VERSION_ESP8266 = {
"dev": "https://github.com/platformio/platform-espressif8266.git",
"3.0.1": "platformio/espressif8266@3.1.0",
"3.0.0": "platformio/espressif8266@3.0.0",
"2.7.4": "platformio/espressif8266@2.6.2",
"2.7.3": "platformio/espressif8266@2.6.1",
"2.7.2": "platformio/espressif8266@2.6.0",
"2.7.1": "platformio/espressif8266@2.5.3",
"2.7.0": "platformio/espressif8266@2.5.0",
"2.6.3": "platformio/espressif8266@2.4.0",
"2.6.2": "platformio/espressif8266@2.3.1",
"2.6.1": "platformio/espressif8266@2.3.0",
"2.5.2": "platformio/espressif8266@2.2.3",
"2.5.1": "platformio/espressif8266@2.1.1",
"2.5.0": "platformio/espressif8266@2.0.4",
"2.4.2": "platformio/espressif8266@1.8.0",
"2.4.1": "platformio/espressif8266@1.7.3",
"2.4.0": "platformio/espressif8266@1.6.0",
"2.3.0": "platformio/espressif8266@1.5.0",
}
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
SECRETS_FILES = {"secrets.yaml", "secrets.yml"}
CONF_ABOVE = "above" CONF_ABOVE = "above"
CONF_ACCELERATION = "acceleration" CONF_ACCELERATION = "acceleration"
@@ -47,6 +27,7 @@ CONF_ACTIVE_POWER = "active_power"
CONF_ADDRESS = "address" CONF_ADDRESS = "address"
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id" CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
CONF_ADVANCED = "advanced" CONF_ADVANCED = "advanced"
CONF_AFTER = "after"
CONF_ALPHA = "alpha" CONF_ALPHA = "alpha"
CONF_ALTITUDE = "altitude" CONF_ALTITUDE = "altitude"
CONF_AND = "and" CONF_AND = "and"
@@ -93,6 +74,7 @@ CONF_BUFFER_SIZE = "buffer_size"
CONF_BUILD_PATH = "build_path" CONF_BUILD_PATH = "build_path"
CONF_BUS_VOLTAGE = "bus_voltage" CONF_BUS_VOLTAGE = "bus_voltage"
CONF_BUSY_PIN = "busy_pin" CONF_BUSY_PIN = "busy_pin"
CONF_BYTES = "bytes"
CONF_CALCULATED_LUX = "calculated_lux" CONF_CALCULATED_LUX = "calculated_lux"
CONF_CALIBRATE_LINEAR = "calibrate_linear" CONF_CALIBRATE_LINEAR = "calibrate_linear"
CONF_CALIBRATION = "calibration" CONF_CALIBRATION = "calibration"
@@ -164,6 +146,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
CONF_DC_PIN = "dc_pin" CONF_DC_PIN = "dc_pin"
CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr" CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
CONF_DEBOUNCE = "debounce" CONF_DEBOUNCE = "debounce"
CONF_DEBUG = "debug"
CONF_DECAY_MODE = "decay_mode" CONF_DECAY_MODE = "decay_mode"
CONF_DECELERATION = "deceleration" CONF_DECELERATION = "deceleration"
CONF_DEFAULT_MODE = "default_mode" CONF_DEFAULT_MODE = "default_mode"
@@ -171,6 +154,7 @@ CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"
CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low" CONF_DEFAULT_TARGET_TEMPERATURE_LOW = "default_target_temperature_low"
CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length" CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
CONF_DELAY = "delay" CONF_DELAY = "delay"
CONF_DELIMITER = "delimiter"
CONF_DELTA = "delta" CONF_DELTA = "delta"
CONF_DEVICE = "device" CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class" CONF_DEVICE_CLASS = "device_class"
@@ -184,6 +168,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
CONF_DISCOVERY = "discovery" CONF_DISCOVERY = "discovery"
CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_PREFIX = "discovery_prefix"
CONF_DISCOVERY_RETAIN = "discovery_retain" CONF_DISCOVERY_RETAIN = "discovery_retain"
CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
CONF_DISTANCE = "distance" CONF_DISTANCE = "distance"
CONF_DITHER = "dither" CONF_DITHER = "dither"
CONF_DIV_RATIO = "div_ratio" CONF_DIV_RATIO = "div_ratio"
@@ -192,6 +177,8 @@ CONF_DNS2 = "dns2"
CONF_DOMAIN = "domain" CONF_DOMAIN = "domain"
CONF_DRY_ACTION = "dry_action" CONF_DRY_ACTION = "dry_action"
CONF_DRY_MODE = "dry_mode" CONF_DRY_MODE = "dry_mode"
CONF_DUMMY_RECEIVER = "dummy_receiver"
CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id"
CONF_DUMP = "dump" CONF_DUMP = "dump"
CONF_DURATION = "duration" CONF_DURATION = "duration"
CONF_EAP = "eap" CONF_EAP = "eap"
@@ -871,9 +858,11 @@ DEVICE_CLASS_OPENING = "opening"
DEVICE_CLASS_PLUG = "plug" DEVICE_CLASS_PLUG = "plug"
DEVICE_CLASS_PRESENCE = "presence" DEVICE_CLASS_PRESENCE = "presence"
DEVICE_CLASS_PROBLEM = "problem" DEVICE_CLASS_PROBLEM = "problem"
DEVICE_CLASS_RUNNING = "running"
DEVICE_CLASS_SAFETY = "safety" DEVICE_CLASS_SAFETY = "safety"
DEVICE_CLASS_SMOKE = "smoke" DEVICE_CLASS_SMOKE = "smoke"
DEVICE_CLASS_SOUND = "sound" DEVICE_CLASS_SOUND = "sound"
DEVICE_CLASS_TAMPER = "tamper"
DEVICE_CLASS_UPDATE = "update" DEVICE_CLASS_UPDATE = "update"
DEVICE_CLASS_VIBRATION = "vibration" DEVICE_CLASS_VIBRATION = "vibration"
DEVICE_CLASS_WINDOW = "window" DEVICE_CLASS_WINDOW = "window"

View File

@@ -55,6 +55,15 @@ bool Component::cancel_interval(const std::string &name) { // NOLINT
return App.scheduler.cancel_interval(this, name); return App.scheduler.cancel_interval(this, name);
} }
void Component::set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult()> &&f, float backoff_increase_factor) { // NOLINT
App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
}
bool Component::cancel_retry(const std::string &name) { // NOLINT
return App.scheduler.cancel_retry(this, name);
}
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
return App.scheduler.set_timeout(this, name, timeout, std::move(f)); return App.scheduler.set_timeout(this, name, timeout, std::move(f));
} }
@@ -120,6 +129,10 @@ void Component::set_timeout(uint32_t timeout, std::function<void()> &&f) { // N
void Component::set_interval(uint32_t interval, std::function<void()> &&f) { // NOLINT void Component::set_interval(uint32_t interval, std::function<void()> &&f) { // NOLINT
App.scheduler.set_interval(this, "", interval, std::move(f)); App.scheduler.set_interval(this, "", interval, std::move(f));
} }
void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult()> &&f,
float backoff_increase_factor) { // NOLINT
App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
}
bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
bool Component::can_proceed() { return true; } bool Component::can_proceed() { return true; }
bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; } bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }

View File

@@ -61,6 +61,8 @@ extern const uint32_t STATUS_LED_OK;
extern const uint32_t STATUS_LED_WARNING; extern const uint32_t STATUS_LED_WARNING;
extern const uint32_t STATUS_LED_ERROR; extern const uint32_t STATUS_LED_ERROR;
enum RetryResult { DONE, RETRY };
class Component { class Component {
public: public:
/** Where the component's initialization should happen. /** Where the component's initialization should happen.
@@ -180,7 +182,35 @@ class Component {
*/ */
bool cancel_interval(const std::string &name); // NOLINT bool cancel_interval(const std::string &name); // NOLINT
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT /** Set an retry function with a unique name. Empty name means no cancelling possible.
*
* This will call f. If f returns RetryResult::RETRY f is called again after initial_wait_time ms.
* f should return RetryResult::DONE if no repeat is required. The initial wait time will be increased
* by backoff_increase_factor for each iteration. Default is doubling the time between iterations
* Can be cancelled via cancel_retry().
*
* IMPORTANT: Do not rely on this having correct timing. This is only called from
* loop() and therefore can be significantly delayed.
*
* @param name The identifier for this retry function.
* @param initial_wait_time The time in ms before f is called again
* @param max_attempts The maximum number of retries
* @param f The function (or lambda) that should be called
* @param backoff_increase_factor time between retries is increased by this factor on every retry
* @see cancel_retry()
*/
void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
std::function<RetryResult()> &&f, float backoff_increase_factor = 1.0f); // NOLINT
void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult()> &&f, // NOLINT
float backoff_increase_factor = 1.0f); // NOLINT
/** Cancel a retry function.
*
* @param name The identifier for this retry function.
* @return Whether a retry function was deleted.
*/
bool cancel_retry(const std::string &name); // NOLINT
/** Set a timeout function with a unique name. /** Set a timeout function with a unique name.
* *
@@ -198,6 +228,8 @@ class Component {
*/ */
void set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f); // NOLINT void set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f); // NOLINT
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT
/** Cancel a timeout function. /** Cancel a timeout function.
* *
* @param name The identifier for this timeout function. * @param name The identifier for this timeout function.

View File

@@ -19,7 +19,6 @@
#define USE_CLIMATE #define USE_CLIMATE
#define USE_COVER #define USE_COVER
#define USE_DEEP_SLEEP #define USE_DEEP_SLEEP
#define USE_ESP8266_PREFERENCES_FLASH
#define USE_FAN #define USE_FAN
#define USE_GRAPH #define USE_GRAPH
#define USE_HOMEASSISTANT_TIME #define USE_HOMEASSISTANT_TIME
@@ -30,25 +29,25 @@
#define USE_OTA_PASSWORD #define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK #define USE_OTA_STATE_CALLBACK
#define USE_POWER_SUPPLY #define USE_POWER_SUPPLY
#define USE_PROMETHEUS
#define USE_SELECT #define USE_SELECT
#define USE_SENSOR #define USE_SENSOR
#define USE_STATUS_LED #define USE_STATUS_LED
#define USE_SWITCH #define USE_SWITCH
#define USE_TEXT_SENSOR #define USE_TEXT_SENSOR
#define USE_TIME #define USE_TIME
#define USE_WEBSERVER #define USE_UART_DEBUGGER
#define USE_WIFI #define USE_WIFI
#define WEBSERVER_PORT 80 // NOLINT
// Arduino-specific feature flags // Arduino-specific feature flags
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#define USE_CAPTIVE_PORTAL #define USE_CAPTIVE_PORTAL
#define USE_JSON #define USE_JSON
#define USE_NEXTION_TFT_UPLOAD #define USE_NEXTION_TFT_UPLOAD
#define USE_MQTT #define USE_MQTT
#define USE_PROMETHEUS
#define USE_WEBSERVER
#define USE_WIFI_WPA2_EAP #define USE_WIFI_WPA2_EAP
#define WEBSERVER_PORT 80 // NOLINT
#endif #endif
// ESP32-specific feature flags // ESP32-specific feature flags
@@ -67,6 +66,7 @@
// ESP8266-specific feature flags // ESP8266-specific feature flags
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#define USE_ADC_SENSOR_VCC #define USE_ADC_SENSOR_VCC
#define USE_ESP8266_PREFERENCES_FLASH
#define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_HTTP_REQUEST_ESP8266_HTTPS
#define USE_SOCKET_IMPL_LWIP_TCP #define USE_SOCKET_IMPL_LWIP_TCP
#endif #endif

View File

@@ -6,10 +6,10 @@
#include <cstring> #include <cstring>
#if defined(USE_ESP8266) #if defined(USE_ESP8266)
#ifdef USE_WIFI
#include <ESP8266WiFi.h>
#endif
#include <osapi.h> #include <osapi.h>
#include <user_interface.h>
// for xt_rsil()/xt_wsr_ps()
#include <Arduino.h>
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include <Esp.h> #include <Esp.h>
#elif defined(USE_ESP_IDF) #elif defined(USE_ESP_IDF)
@@ -30,8 +30,8 @@ namespace esphome {
static const char *const TAG = "helpers"; static const char *const TAG = "helpers";
void get_mac_address_raw(uint8_t *mac) { void get_mac_address_raw(uint8_t *mac) {
#ifdef USE_ESP32 #if defined(USE_ESP32)
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC #if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
// On some devices, the MAC address that is burnt into EFuse does not // On some devices, the MAC address that is burnt into EFuse does not
// match the CRC that goes along with it. For those devices, this // match the CRC that goes along with it. For those devices, this
// work-around reads and uses the MAC address as-is from EFuse, // work-around reads and uses the MAC address as-is from EFuse,
@@ -40,30 +40,21 @@ void get_mac_address_raw(uint8_t *mac) {
#else #else
esp_efuse_mac_get_default(mac); esp_efuse_mac_get_default(mac);
#endif #endif
#endif #elif defined(USE_ESP8266)
#if (defined USE_ESP8266 && defined USE_WIFI) wifi_get_macaddr(STATION_IF, mac);
WiFi.macAddress(mac);
#endif #endif
} }
std::string get_mac_address() { std::string get_mac_address() {
char tmp[20];
uint8_t mac[6]; uint8_t mac[6];
get_mac_address_raw(mac); get_mac_address_raw(mac);
#ifdef USE_WIFI return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#else
return "";
#endif
return std::string(tmp);
} }
std::string get_mac_address_pretty() { std::string get_mac_address_pretty() {
char tmp[20];
uint8_t mac[6]; uint8_t mac[6];
get_mac_address_raw(mac); get_mac_address_raw(mac);
sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return std::string(tmp);
} }
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -334,6 +325,20 @@ bool str_startswith(const std::string &full, const std::string &start) { return
bool str_endswith(const std::string &full, const std::string &ending) { bool str_endswith(const std::string &full, const std::string &ending) {
return full.rfind(ending) == (full.size() - ending.size()); return full.rfind(ending) == (full.size() - ending.size());
} }
std::string str_snprintf(const char *fmt, size_t length, ...) {
std::string str;
va_list args;
str.resize(length);
va_start(args, length);
size_t out_length = vsnprintf(&str[0], length + 1, fmt, args);
va_end(args);
if (out_length < length)
str.resize(out_length);
return str;
}
std::string str_sprintf(const char *fmt, ...) { std::string str_sprintf(const char *fmt, ...) {
std::string str; std::string str;
va_list args; va_list args;
@@ -430,13 +435,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
} }
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#ifdef USE_WIFI
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
#else
IRAM_ATTR InterruptLock::InterruptLock() {}
IRAM_ATTR InterruptLock::~InterruptLock() {}
#endif
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }

View File

@@ -25,14 +25,13 @@
namespace esphome { namespace esphome {
/// Read the raw MAC address into the provided byte array (6 bytes). /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
void get_mac_address_raw(uint8_t *mac); void get_mac_address_raw(uint8_t *mac);
/// Get the MAC address as a string, using lower case hex notation. /// Get the device MAC address as a string, in lowercase hex notation.
/// This can be used as way to identify this ESP.
std::string get_mac_address(); std::string get_mac_address();
/// Get the MAC address as a string, using colon-separated upper case hex notation. /// Get the device MAC address as a string, in colon-separated uppercase hex notation.
std::string get_mac_address_pretty(); std::string get_mac_address_pretty();
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -58,7 +57,10 @@ bool str_equals_case_insensitive(const std::string &a, const std::string &b);
bool str_startswith(const std::string &full, const std::string &start); bool str_startswith(const std::string &full, const std::string &start);
bool str_endswith(const std::string &full, const std::string &ending); bool str_endswith(const std::string &full, const std::string &ending);
/// sprintf-like function returning std::string instead of writing to char array. /// snprintf-like function returning std::string with a given maximum length.
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...);
/// sprintf-like function returning std::string.
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
class HighFrequencyLoopRequester { class HighFrequencyLoopRequester {

View File

@@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
item->timeout = timeout; item->timeout = timeout;
item->last_execution = now; item->last_execution = now;
item->last_execution_major = this->millis_major_; item->last_execution_major = this->millis_major_;
item->f = std::move(func); item->void_callback = std::move(func);
item->remove = false; item->remove = false;
this->push_(std::move(item)); this->push_(std::move(item));
} }
@@ -65,13 +65,47 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
item->last_execution_major = this->millis_major_; item->last_execution_major = this->millis_major_;
if (item->last_execution > now) if (item->last_execution > now)
item->last_execution_major--; item->last_execution_major--;
item->f = std::move(func); item->void_callback = std::move(func);
item->remove = false; item->remove = false;
this->push_(std::move(item)); this->push_(std::move(item));
} }
bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) { bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::INTERVAL); return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
} }
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
uint8_t max_attempts, std::function<RetryResult()> &&func,
float backoff_increase_factor) {
const uint32_t now = this->millis_();
if (!name.empty())
this->cancel_retry(component, name);
if (initial_wait_time == SCHEDULER_DONT_RUN)
return;
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
initial_wait_time, max_attempts, backoff_increase_factor);
auto item = make_unique<SchedulerItem>();
item->component = component;
item->name = name;
item->type = SchedulerItem::RETRY;
item->interval = initial_wait_time;
item->retry_countdown = max_attempts;
item->backoff_multiplier = backoff_increase_factor;
item->last_execution = now - initial_wait_time;
item->last_execution_major = this->millis_major_;
if (item->last_execution > now)
item->last_execution_major--;
item->retry_callback = std::move(func);
item->remove = false;
this->push_(std::move(item));
}
bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::RETRY);
}
optional<uint32_t> HOT Scheduler::next_schedule_in() { optional<uint32_t> HOT Scheduler::next_schedule_in() {
if (this->empty_()) if (this->empty_())
return {}; return {};
@@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() {
ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now); ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
while (!this->empty_()) { while (!this->empty_()) {
auto item = std::move(this->items_[0]); auto item = std::move(this->items_[0]);
const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", type, item->name.c_str(), item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
item->interval, item->last_execution, item->last_execution_major, item->next_execution(), item->next_execution(), item->next_execution_major());
item->next_execution_major());
this->pop_raw_(); this->pop_raw_();
old_items.push_back(std::move(item)); old_items.push_back(std::move(item));
@@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() {
} }
while (!this->empty_()) { while (!this->empty_()) {
RetryResult retry_result = RETRY;
// use scoping to indicate visibility of `item` variable // use scoping to indicate visibility of `item` variable
{ {
// Don't copy-by value yet // Don't copy-by value yet
@@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() {
} }
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout"; ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(), item->name.c_str(), item->interval, item->last_execution, now);
item->interval, item->last_execution, now);
#endif #endif
// Warning: During f(), a lot of stuff can happen, including: // Warning: During callback(), a lot of stuff can happen, including:
// - timeouts/intervals get added, potentially invalidating vector pointers // - timeouts/intervals get added, potentially invalidating vector pointers
// - timeouts/intervals get cancelled // - timeouts/intervals get cancelled
{ {
WarnIfComponentBlockingGuard guard{item->component}; WarnIfComponentBlockingGuard guard{item->component};
item->f(); if (item->type == SchedulerItem::RETRY)
retry_result = item->retry_callback();
else
item->void_callback();
} }
} }
@@ -175,13 +211,16 @@ void IRAM_ATTR HOT Scheduler::call() {
continue; continue;
} }
if (item->type == SchedulerItem::INTERVAL) { if (item->type == SchedulerItem::INTERVAL ||
(item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) {
if (item->interval != 0) { if (item->interval != 0) {
const uint32_t before = item->last_execution; const uint32_t before = item->last_execution;
const uint32_t amount = (now - item->last_execution) / item->interval; const uint32_t amount = (now - item->last_execution) / item->interval;
item->last_execution += amount * item->interval; item->last_execution += amount * item->interval;
if (item->last_execution < before) if (item->last_execution < before)
item->last_execution_major++; item->last_execution_major++;
if (item->type == SchedulerItem::RETRY)
item->interval *= item->backoff_multiplier;
} }
this->push_(std::move(item)); this->push_(std::move(item));
} }

View File

@@ -15,6 +15,10 @@ class Scheduler {
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> &&func); void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> &&func);
bool cancel_interval(Component *component, const std::string &name); bool cancel_interval(Component *component, const std::string &name);
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult()> &&func, float backoff_increase_factor = 1.0f);
bool cancel_retry(Component *component, const std::string &name);
optional<uint32_t> next_schedule_in(); optional<uint32_t> next_schedule_in();
void call(); void call();
@@ -25,13 +29,20 @@ class Scheduler {
struct SchedulerItem { struct SchedulerItem {
Component *component; Component *component;
std::string name; std::string name;
enum Type { TIMEOUT, INTERVAL } type; enum Type { TIMEOUT, INTERVAL, RETRY } type;
union { union {
uint32_t interval; uint32_t interval;
uint32_t timeout; uint32_t timeout;
}; };
uint32_t last_execution; uint32_t last_execution;
std::function<void()> f; // Ideally this should be a union or std::variant
// but unions don't work with object like std::function
// union CallBack_{
std::function<void()> void_callback;
std::function<RetryResult()> retry_callback;
// };
uint8_t retry_countdown{3};
float backoff_multiplier{1.0f};
bool remove; bool remove;
uint8_t last_execution_major; uint8_t last_execution_major;
@@ -45,6 +56,18 @@ class Scheduler {
} }
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b); static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
const char *get_type_str() {
switch (this->type) {
case SchedulerItem::INTERVAL:
return "interval";
case SchedulerItem::RETRY:
return "retry";
case SchedulerItem::TIMEOUT:
return "timeout";
default:
return "";
}
}
}; };
uint32_t millis_(); uint32_t millis_();

View File

@@ -970,16 +970,17 @@ def start_web_server(args):
server.add_socket(socket) server.add_socket(socket)
else: else:
_LOGGER.info( _LOGGER.info(
"Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...", "Starting dashboard web server on http://%s:%s and configuration dir %s...",
args.address,
args.port, args.port,
settings.config_dir, settings.config_dir,
) )
app.listen(args.port) app.listen(args.port, args.address)
if args.open_ui: if args.open_ui:
import webbrowser import webbrowser
webbrowser.open(f"localhost:{args.port}") webbrowser.open(f"http://{args.address}:{args.port}")
if settings.status_use_ping: if settings.status_use_ping:
status_thread = PingStatusThread() status_thread = PingStatusThread()

View File

@@ -13,8 +13,9 @@ from zeroconf import (
RecordUpdateListener, RecordUpdateListener,
Zeroconf, Zeroconf,
ServiceBrowser, ServiceBrowser,
ServiceStateChange,
current_time_millis,
) )
from zeroconf._services import ServiceStateChange
_CLASS_IN = 1 _CLASS_IN = 1
_FLAGS_QR_QUERY = 0x0000 # query _FLAGS_QR_QUERY = 0x0000 # query
@@ -88,7 +89,7 @@ class DashboardStatus(threading.Thread):
entries = self.zc.cache.entries_with_name(key) entries = self.zc.cache.entries_with_name(key)
if not entries: if not entries:
return False return False
now = time.time() * 1000 now = current_time_millis()
return any( return any(
(entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries (entry.created + DashboardStatus.OFFLINE_AFTER) >= now for entry in entries
@@ -99,7 +100,7 @@ class DashboardStatus(threading.Thread):
self.on_update( self.on_update(
{key: self.host_status(host) for key, host in self.key_to_host.items()} {key: self.host_status(host) for key, host in self.key_to_host.items()}
) )
now = time.time() * 1000 now = current_time_millis()
for host in self.query_hosts: for host in self.query_hosts:
entries = self.zc.cache.entries_with_name(host) entries = self.zc.cache.entries_with_name(host)
if not entries or all( if not entries or all(

View File

@@ -28,6 +28,7 @@ build_flags =
lib_deps = lib_deps =
esphome/noise-c@0.1.4 ; api esphome/noise-c@0.1.4 ; api
makuna/NeoPixelBus@2.6.9 ; neopixelbus makuna/NeoPixelBus@2.6.9 ; neopixelbus
esphome/Improv@1.0.0 ; improv_serial / esp32_improv
build_flags = build_flags =
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
src_filter = src_filter =
@@ -41,15 +42,16 @@ lib_deps =
${common.lib_deps} ${common.lib_deps}
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
ottowinter/ArduinoJson-esphomelib@5.13.3 ; json ottowinter/ArduinoJson-esphomelib@5.13.3 ; json
esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base
fastled/FastLED@3.3.2 ; fastled_base fastled/FastLED@3.3.2 ; fastled_base
mikalhart/TinyGPSPlus@1.0.2 ; gps mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651 freekode/TM1651@1.0.1 ; tm1651
seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301
glmnet/Dsmr@0.5 ; dsmr glmnet/Dsmr@0.5 ; dsmr
rweather/Crypto@0.2.0 ; dsmr rweather/Crypto@0.2.0 ; dsmr
dudanov/MideaUART@1.1.8 ; midea dudanov/MideaUART@1.1.8 ; midea
tonia/HeatpumpIR@1.0.15 ; heatpumpir ; PIO isn't update releases correctly, see:
; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
https://github.com/ToniA/arduino-heatpumpir.git#1.0.18 ; heatpumpir
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
-DUSE_ARDUINO -DUSE_ARDUINO

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