1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 08:31:47 +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/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz

View File

@@ -147,9 +147,9 @@ RUN \
/var/{cache,log}/* \
/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 \
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
VOLUME ["/esphome"]

View File

@@ -18,6 +18,7 @@ from esphome.const import (
CONF_PORT,
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent
@@ -200,8 +201,7 @@ def upload_using_esptool(config, port):
firmware_offset = "0x10000" if CORE.is_esp32 else "0x0"
flash_images = [
platformio_api.FlashImage(
path=idedata.firmware_bin_path,
offset=firmware_offset,
path=idedata.firmware_bin_path, offset=firmware_offset
),
*idedata.extra_flash_images,
]
@@ -607,10 +607,7 @@ def parse_args(argv):
"wizard",
help="A helpful setup wizard that will guide you through setting up ESPHome.",
)
parser_wizard.add_argument(
"configuration",
help="Your YAML configuration file.",
)
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
parser_fingerprint = subparsers.add_parser(
"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."
)
parser_dashboard.add_argument(
"configuration",
help="Your YAML configuration file directory.",
"configuration", help="Your YAML configuration file directory."
)
parser_dashboard.add_argument(
"--port",
@@ -641,6 +637,12 @@ def parse_args(argv):
type=int,
default=6052,
)
parser_dashboard.add_argument(
"--address",
help="The address to bind to.",
type=str,
default="0.0.0.0",
)
parser_dashboard.add_argument(
"--username",
help="The optional username to require for authentication.",
@@ -789,6 +791,10 @@ def run_esphome(argv):
return 1
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.dashboard = args.dashboard

View File

@@ -73,13 +73,6 @@ void AHT10Component::update() {
bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
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);
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");

View File

@@ -44,8 +44,9 @@ async def to_code(config):
width, height = image.size
frames = image.n_frames
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
width, height = image.size
new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio)
else:
if width > 500 or height > 500:
_LOGGER.warning(
@@ -59,7 +60,13 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("L", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
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:
data[pos] = pix
pos += 1
@@ -70,7 +77,13 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("RGB")
if CONF_RESIZE in config:
frame = frame.resize([width, height])
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:
data[pos] = pix[0]
pos += 1
@@ -85,6 +98,8 @@ async def to_code(config):
for frameIndex in range(frames):
image.seek(frameIndex)
frame = image.convert("1", dither=Image.NONE)
if CONF_RESIZE in config:
frame = frame.resize([width, height])
for y in range(height):
for x in range(width):
if frame.getpixel((x, y)):

View File

@@ -44,9 +44,11 @@ from esphome.const import (
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
@@ -76,9 +78,11 @@ DEVICE_CLASSES = [
DEVICE_CLASS_POWER,
DEVICE_CLASS_PRESENCE,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_TAMPER,
DEVICE_CLASS_UPDATE,
DEVICE_CLASS_VIBRATION,
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));
}
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 esphome

View File

@@ -59,7 +59,7 @@ class BLECharacteristic {
void parse_descriptors();
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
void write_value(uint8_t *new_val, int16_t new_val_size);
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]))
)
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))
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(
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))
if CONF_DESCRIPTOR_UUID in config:
@@ -108,7 +110,9 @@ async def to_code(config):
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
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))
if CONF_LAMBDA in config:

View File

@@ -20,6 +20,7 @@ from esphome.const import (
CONF_MODE,
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_ON_STATE,
CONF_PRESET,
CONF_SWING_MODE,
CONF_SWING_MODE_COMMAND_TOPIC,
@@ -34,6 +35,7 @@ from esphome.const import (
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
)
@@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True)
# Actions
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(
{
@@ -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.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):
if not CORE.has_id(config[CONF_ID]):

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart
from esphome.const import (
CONF_ID,
@@ -11,10 +12,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"]
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor"]
CONF_DSMR_ID = "dsmr_id"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_CRC_CHECK = "crc_check"
CONF_DECRYPTION_KEY = "decryption_key"
CONF_DSMR_ID = "dsmr_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
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_CRC_CHECK, default=True): cv.boolean,
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),
cv.only_with_arduino,
@@ -55,10 +62,19 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
uart_component = await cg.get_variable(config[CONF_UART_ID])
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:
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
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])
# DSMR Parser

View File

@@ -12,146 +12,218 @@ namespace 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() {
if (this->decryption_key_.empty())
this->receive_telegram_();
else
this->receive_encrypted_();
if (this->ready_to_request_data_()) {
if (this->decryption_key_.empty()) {
this->receive_telegram_();
} 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_() {
uint8_t tries = READ_TIMEOUT_MS / 5;
while (tries--) {
delay(5);
if (available()) {
if (this->available()) {
return true;
}
}
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_() {
while (true) {
if (!available()) {
if (!header_found_ || !available_within_timeout_()) {
if (!this->available()) {
if (!this->header_found_ || !this->available_within_timeout_()) {
return;
}
}
const char c = read();
const char c = this->read();
// Find a new telegram header, i.e. forward slash.
if (c == '/') {
ESP_LOGV(TAG, "Header of telegram found");
header_found_ = true;
footer_found_ = false;
telegram_len_ = 0;
this->header_found_ = true;
this->footer_found_ = false;
this->telegram_len_ = 0;
}
if (!header_found_)
if (!this->header_found_)
continue;
// Check for buffer overflow.
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
header_found_ = false;
footer_found_ = false;
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
if (this->telegram_len_ >= this->max_telegram_len_) {
this->header_found_ = false;
this->footer_found_ = false;
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
return;
}
// 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
// proper parsing remove these new line characters
while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r'))
telegram_len_--;
// in a new line, while the value belongs to the previous ObisId. For
// proper parsing, remove these new line characters.
if (c == '(') {
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.
telegram_[telegram_len_] = c;
telegram_len_++;
this->telegram_[this->telegram_len_] = c;
this->telegram_len_++;
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') {
ESP_LOGV(TAG, "Footer of telegram found");
footer_found_ = true;
this->footer_found_ = true;
continue;
}
// 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_telegram();
this->parse_telegram();
header_found_ = false;
this->header_found_ = false;
return;
}
}
}
void Dsmr::receive_encrypted_() {
// Encrypted buffer
uint8_t buffer[MAX_TELEGRAM_LENGTH];
size_t buffer_length = 0;
this->encrypted_telegram_len_ = 0;
size_t packet_size = 0;
while (true) {
if (!available()) {
if (!header_found_) {
if (!this->available()) {
if (!this->header_found_) {
return;
}
if (!available_within_timeout_()) {
if (!this->available_within_timeout_()) {
ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
return;
}
}
const char c = read();
const char c = this->read();
// Find a new telegram start byte.
if (!header_found_) {
if (!this->header_found_) {
if ((uint8_t) c != 0xDB) {
continue;
}
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
header_found_ = true;
this->header_found_ = true;
}
// Check for buffer overflow.
if (buffer_length >= MAX_TELEGRAM_LENGTH) {
header_found_ = false;
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
if (this->encrypted_telegram_len_ >= this->max_telegram_len_) {
this->header_found_ = false;
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
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
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);
}
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");
GCM<AES128> *gcmaes128{new GCM<AES128>()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// 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
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};
gcmaes128->setIV(&buffer[2], iv_size);
gcmaes128->setIV(&this->encrypted_telegram_[2], iv_size);
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
// the ciphertext start at byte 18
&buffer[18],
&this->encrypted_telegram_[18],
// cipher size
buffer_length - 17);
this->encrypted_telegram_len_ - 17);
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
this->telegram_len_ = strnlen(this->telegram_, this->max_telegram_len_);
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->telegram_len_);
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
parse_telegram();
this->parse_telegram();
header_found_ = false;
telegram_len_ = 0;
this->header_found_ = false;
this->telegram_len_ = 0;
return;
}
}
@@ -160,23 +232,32 @@ void Dsmr::receive_encrypted_() {
bool Dsmr::parse_telegram() {
MyData data;
ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_();
::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.
if (res.err) {
// 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());
return false;
} else {
this->status_clear_warning();
publish_sensors(data);
this->publish_sensors(data);
return true;
}
}
void Dsmr::dump_config() {
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##_);
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) {
ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear();
if (this->encrypted_telegram_ != nullptr) {
delete[] this->encrypted_telegram_;
this->encrypted_telegram_ = nullptr;
}
return;
}
@@ -205,10 +290,16 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
char temp[3] = {0};
for (int i = 0; i < 16; i++) {
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 esphome

View File

@@ -16,7 +16,6 @@
namespace esphome {
namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
static constexpr uint32_t READ_TIMEOUT_MS = 200;
using namespace ::dsmr::fields;
@@ -52,6 +51,7 @@ class Dsmr : public Component, public uart::UARTDevice {
public:
Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {}
void setup() override;
void loop() override;
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_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
#define DSMR_SET_SENSOR(s) \
void set_##s(sensor::Sensor *sensor) { s_##s##_ = sensor; }
@@ -96,9 +101,22 @@ class Dsmr : public Component, public uart::UARTDevice {
/// lost in the process.
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
char telegram_[MAX_TELEGRAM_LENGTH];
size_t max_telegram_len_;
char *telegram_{nullptr};
int telegram_len_{0};
uint8_t *encrypted_telegram_{nullptr};
int encrypted_telegram_len_{0};
// Serial parser
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_LENGTH "Content-Length"
static const char *const STREAM_HEADER =
"HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
"\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";
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Connection: close\r\n"
"Content-Type: multipart/x-mixed-replace;boundary=" 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_BOUNDARY = "\r\n"
"--" PART_BOUNDARY "\r\n";
CameraWebServer::CameraWebServer() {}
@@ -45,6 +52,7 @@ void CameraWebServer::setup() {
config.ctrl_port = this->port_;
config.max_open_sockets = 1;
config.backlog_conn = 2;
config.lru_purge_enable = true;
if (httpd_start(&this->httpd_, &config) != ESP_OK) {
mark_failed();
@@ -172,9 +180,6 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
ESP_LOGW(TAG, "STREAM: failed to acquire frame");
res = ESP_FAIL;
}
if (res == ESP_OK) {
res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
}
if (res == ESP_OK) {
size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
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) {
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) {
frames++;
int64_t frame_time = millis() - last_frame;
@@ -193,7 +201,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
}
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);

View File

@@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
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"]
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"]
@@ -56,6 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var))
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_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -11,6 +11,7 @@ namespace esphome {
namespace esp32_improv {
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; }
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
this->cancel_timeout("wifi-connect-timeout");
this->set_state_(improv::STATE_PROVISIONED);
std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
std::vector<std::string> urls = {ESPHOME_MY_LINK};
#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->set_timeout("end-service", 1000, [this] {
this->service_->stop();

View File

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

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP32
#include "esp32_touch.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
if (this->iir_filter_enabled_()) {
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_);
touch_pad_filter_start(this->iir_filter_);
} else {
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
}
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
if (should_print) {
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) {

View File

@@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = {
"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},
}
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,
"greeya": Protocol.PROTOCOL_GREEYAA,
"greeyan": Protocol.PROTOCOL_GREEYAN,
"greeyac": Protocol.PROTOCOL_GREEYAC,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI,
"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_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_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
@@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() {
}
this->heatpump_ir_ = protocol_constructor->second();
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() {
@@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() {
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,
swing_h_cmd);
}

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
#pragma once
#ifdef USE_ARDUINO
#include "abstract_aqi_calculator.h"
namespace esphome {
@@ -33,7 +31,7 @@ class AQICalculator : public AbstractAQICalculator {
int conc_lo = array[grid_index][0];
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]) {
@@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator {
} // namespace hm3301
} // namespace esphome
#endif // USE_ARDUINO

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "hm3301.h"
@@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7;
void HM3301Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up HM3301...");
hm3301_ = make_unique<HM330X>();
error_code_ = hm3301_->init();
if (error_code_ != NO_ERROR) {
if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) {
error_code_ = ERROR_COMM;
this->mark_failed();
return;
}
@@ -38,7 +35,7 @@ void HM3301Component::dump_config() {
float HM3301Component::get_setup_priority() const { return setup_priority::DATA; }
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");
this->status_set_warning();
return;
@@ -87,8 +84,6 @@ void HM3301Component::update() {
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) {
uint8_t sum = 0;
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 esphome
#endif // USE_ARDUINO

View File

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

View File

@@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All(
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40)),
_validate,
cv.only_with_arduino,
)
@@ -109,6 +108,3 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_AQI])
cg.add(var.set_aqi_sensor(sens))
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):
interval_sec = config[CONF_UPDATE_INTERVAL].seconds
interval_hz = 1.0 / interval_sec
interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds
interval_hz = 1000.0 / interval_msec
for datarate in sorted(HMC5883LDatarates.keys()):
if float(datarate) >= interval_hz:
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"]
DEPENDENCIES = ["logger", "wifi"]
AUTO_LOAD = ["improv"]
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):
var = cg.new_Pvariable(config[CONF_ID])
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::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
std::vector<std::string> urls = {url};
std::vector<std::string> urls;
#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(command, urls);
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
return data;
}
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
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;
};
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size();
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];
if (at == 0)
return byte == 'I';
@@ -141,22 +140,33 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
if (at < 8 + data_len)
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) {
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 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) {
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: {
wifi::WiFiAP sta{};
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->start_scanning();
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());
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[8] = response.size();
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);
}

View File

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

View File

@@ -15,17 +15,37 @@ namespace ledc {
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_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));
}
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
for (int i = 20; i >= 1; i--) {
const float min_frequency = ledc_min_frequency_for_bit_depth(i);
ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
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);
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 {};
}
@@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) {
ledcWrite(this->channel_, duty);
#endif
#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);
ledc_set_duty(speed_mode, chan_num, duty);
ledc_update_duty(speed_mode, chan_num);
@@ -63,11 +83,15 @@ void LEDCOutput::setup() {
ledcAttachPin(this->pin_->get_pin(), this->channel_);
#endif
#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 chan_num = static_cast<ledc_channel_t>(channel_ % 8);
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{};
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!");
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);
ledc_timer_config_t timer_conf{};

View File

@@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
void mark_shown_() {
#ifdef USE_POWER_SUPPLY
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();
return;
}

View File

@@ -1,10 +1,11 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "adapter.h"
#include "ac_adapter.h"
namespace esphome {
namespace midea {
namespace ac {
const char *const Constants::TAG = "midea";
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);
}
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,12 +2,15 @@
#ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
#include "esphome/components/climate/climate_traits.h"
#include "appliance_base.h"
#include "air_conditioner.h"
namespace esphome {
namespace midea {
namespace ac {
using MideaMode = dudanov::midea::ac::Mode;
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);
};
} // namespace ac
} // namespace midea
} // namespace esphome

View File

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

View File

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

View File

@@ -2,17 +2,25 @@
#ifdef USE_ARDUINO
// MideaUART
#include <Appliance/AirConditioner/AirConditioner.h>
#include "appliance_base.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace midea {
namespace ac {
using sensor::Sensor;
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:
void dump_config() override;
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_power_on() { this->base_.setPowerState(true); }
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:
void control(const ClimateCall &call) 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 *humidity_sensor_{nullptr};
Sensor *power_sensor_{nullptr};
};
} // namespace ac
} // namespace midea
} // namespace esphome

View File

@@ -2,84 +2,97 @@
#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/log.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/climate/climate.h"
#ifdef USE_REMOTE_TRANSMITTER
#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>
#include "ir_transmitter.h"
namespace esphome {
namespace midea {
using climate::ClimatePreset;
using climate::ClimateTraits;
using climate::ClimateMode;
using climate::ClimateSwingMode;
using climate::ClimateFanMode;
/* Stream from UART component */
class UARTStream : public Stream {
public:
void set_uart(uart::UARTComponent *uart) { this->uart_ = uart; }
template<typename T>
class ApplianceBase : public Component, public uart::UARTDevice, public climate::Climate, public Stream {
/* Stream interface implementation */
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,
"T must derive from dudanov::midea::ApplianceBase class");
public:
ApplianceBase() {
this->base_.setStream(this);
this->base_.setStream(&this->stream_);
this->base_.addOnStateCallback(std::bind(&ApplianceBase::on_status_change, this));
dudanov::midea::ApplianceBase::setLogger(
[](int level, const char *tag, int line, const String &format, va_list args) {
esp_log_vprintf_(level, tag, line, format.c_str(), args);
});
}
bool can_proceed() override {
return this->base_.getAutoconfStatus() != dudanov::midea::AutoconfStatus::AUTOCONF_PROGRESS;
}
float get_setup_priority() const override { return setup_priority::BEFORE_CONNECTION; }
void setup() override { this->base_.setup(); }
void loop() override { this->base_.loop(); }
#ifdef USE_REMOTE_TRANSMITTER
void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_.set_transmitter(transmitter); }
#endif
/* 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_response_timeout(uint32_t ms) { this->base_.setTimeout(ms); }
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_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;
#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:
T base_;
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_{};
UARTStream stream_;
#ifdef USE_REMOTE_TRANSMITTER
remote_transmitter::RemoteTransmitterComponent *transmitter_{nullptr};
IrTransmitter transmitter_;
#endif
};

View File

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

View File

@@ -7,6 +7,7 @@
namespace esphome {
namespace midea {
using remote_base::RemoteTransmitterBase;
using IrData = remote_base::MideaData;
class IrFollowMeData : public IrData {
@@ -38,6 +39,20 @@ class IrSpecialData : public IrData {
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 esphome

View File

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

View File

@@ -14,6 +14,7 @@ from esphome.const import (
CONF_DISCOVERY,
CONF_DISCOVERY_PREFIX,
CONF_DISCOVERY_RETAIN,
CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
CONF_ID,
CONF_KEEPALIVE,
CONF_LEVEL,
@@ -95,6 +96,12 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", 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):
# Populate default fields
@@ -153,6 +160,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_DISCOVERY_PREFIX, default="homeassistant"
): 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_BIRTH_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_retain = config[CONF_DISCOVERY_RETAIN]
discovery_prefix = config[CONF_DISCOVERY_PREFIX]
discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
if not discovery:
cg.add(var.disable_discovery())
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:
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]))

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_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_.unique_id_generator = unique_id_generator;
this->discovery_info_.retain = retain;
this->discovery_info_.clean = clean;
}

View File

@@ -55,6 +55,12 @@ struct Availability {
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
*
* 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.
bool retain; ///< Whether to retain discovery messages.
bool clean;
MQTTDiscoveryUniqueIdGenerator unique_id_generator;
};
enum MQTTClientState {
@@ -98,9 +105,11 @@ class MQTTClientComponent : public Component {
*
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
* @param prefix The Home Assistant discovery prefix.
* @param unique_id_generator Controls how UniqueId is generated.
* @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.
const MQTTDiscoveryInfo &get_discovery_info() const;
/// Globally disable Home Assistant discovery.

View File

@@ -114,9 +114,17 @@ bool MQTTComponent::send_discovery_() {
if (!unique_id.empty()) {
root[MQTT_UNIQUE_ID] = unique_id;
} 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_();
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
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);

View File

@@ -41,7 +41,7 @@ NumberInRangeCondition = number_ns.class_(
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.GenerateID(): cv.declare_id(Number),

View File

@@ -96,6 +96,7 @@ optional<bool> PMSX003Component::check_byte_() {
length_matches = payload_length == 28 || payload_length == 20;
break;
case PMSX003_TYPE_5003T:
case PMSX003_TYPE_5003S:
length_matches = payload_length == 28;
break;
case PMSX003_TYPE_5003ST:
@@ -133,20 +134,25 @@ optional<bool> PMSX003Component::check_byte_() {
void PMSX003Component::parse_data_() {
switch (this->type_) {
case PMSX003_TYPE_5003ST: {
uint16_t formaldehyde = this->get_16_bit_uint_(28);
float temperature = this->get_16_bit_uint_(30) / 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,
formaldehyde);
ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
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)
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: {
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_5003T,
PMSX003_TYPE_5003ST,
PMSX003_TYPE_5003S,
};
class PMSX003Component : public uart::UARTDevice, public Component {

View File

@@ -42,21 +42,23 @@ PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor)
TYPE_PMSX003 = "PMSX003"
TYPE_PMS5003T = "PMS5003T"
TYPE_PMS5003ST = "PMS5003ST"
TYPE_PMS5003S = "PMS5003S"
PMSX003Type = pmsx003_ns.enum("PMSX003Type")
PMSX003_TYPES = {
TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003,
TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T,
TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST,
TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S,
}
SENSORS_TO_TYPE = {
CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003ST],
CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST],
CONF_PM_10_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, TYPE_PMS5003S],
CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S],
CONF_TEMPERATURE: [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(
web_server_base.WebServerBase
),
}
},
cv.only_with_arduino,
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -17,14 +17,14 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
dst->set_carrier_frequency(38000);
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)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
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)
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
else
@@ -41,7 +41,7 @@ optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
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)) {
data.address |= mask;
} 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)) {
data.command |= mask;
} 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 space_(uint32_t usec);
void await_target_time_();
uint32_t target_time_;
#endif
#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;
}
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) {
if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) {
this->pin_->digital_write(true);
delayMicroseconds(usec);
this->pin_->digital_write(false);
return;
}
const uint32_t start_time = micros();
uint32_t current_time = start_time;
while (current_time - start_time < usec) {
const uint32_t elapsed = current_time - start_time;
this->pin_->digital_write(true);
delayMicroseconds(std::min(on_time, usec - elapsed));
this->pin_->digital_write(false);
if (elapsed + on_time >= usec)
return;
delayMicroseconds(std::min(usec - elapsed - on_time, off_time));
current_time = micros();
this->await_target_time_();
this->pin_->digital_write(true);
const uint32_t target = this->target_time_ + usec;
if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
while (true) { // Modulate with carrier frequency
this->target_time_ += on_time;
if (this->target_time_ >= target)
break;
this->await_target_time_();
this->pin_->digital_write(false);
this->target_time_ += off_time;
if (this->target_time_ >= target)
break;
this->await_target_time_();
this->pin_->digital_write(true);
}
}
this->target_time_ = target;
}
void RemoteTransmitterComponent::space_(uint32_t usec) {
this->await_target_time_();
this->pin_->digital_write(false);
delayMicroseconds(usec);
this->target_time_ += usec;
}
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
ESP_LOGD(TAG, "Sending remote code...");
uint32_t 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++) {
{
InterruptLock lock;
for (int32_t item : this->temp_.get_data()) {
if (item > 0) {
const auto length = uint32_t(item);
this->mark_(on_time, off_time, length);
} else {
const auto length = uint32_t(-item);
this->space_(length);
}
App.feed_wdt();
for (int32_t item : this->temp_.get_data()) {
if (item > 0) {
const auto length = uint32_t(item);
this->mark_(on_time, off_time, length);
} else {
const auto length = uint32_t(-item);
this->space_(length);
}
App.feed_wdt();
}
this->await_target_time_(); // wait for duration of last pulse
this->pin_->digital_write(false);
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)
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);
this->data_callback_.call(data);
break;
@@ -73,7 +73,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t 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());
this->advanced_data_callback_.call(data);
break;
@@ -97,7 +97,7 @@ bool RFBridgeComponent::parse_bridge_byte_(uint8_t byte) {
str += " ";
}
}
ESP_LOGD(TAG, "Received RFBridge Bucket: %s", str.c_str());
ESP_LOGI(TAG, "Received RFBridge Bucket: %s", str.c_str());
break;
}
default:
@@ -186,7 +186,7 @@ void RFBridgeComponent::dump_config() {
}
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_SNIFFING_ON);
this->write(RF_CODE_STOP);
@@ -194,7 +194,7 @@ void RFBridgeComponent::start_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_SNIFFING_OFF);
this->write(RF_CODE_STOP);
@@ -202,7 +202,7 @@ void RFBridgeComponent::stop_advanced_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_RFIN_BUCKET);
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_ID2[2] = {0xE1, 0x02};
static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
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
}
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) {
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;
}
uint8_t data[18];
if (this->read(data, 18) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID SDP3X failed!");
this->mark_failed();
return;
}
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
ESP_LOGE(TAG, "CRC ID SDP3X failed!");
this->mark_failed();
return;
}
uint8_t data[18];
if (this->read(data, 18) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID SDP3X failed!");
this->mark_failed();
return;
}
// SDP8xx
// ref:
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
if (data[2] == 0x02) {
switch (data[3]) {
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]))) {
ESP_LOGE(TAG, "CRC ID SDP3X failed!");
this->mark_failed();
return;
}
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!");
if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_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() {
LOG_SENSOR(" ", "SDP3X", this);
@@ -91,8 +115,12 @@ void SDP3XComponent::read_pressure_() {
}
int16_t pressure_raw = encode_uint16(data[0], data[1]);
float pressure = pressure_raw / pressure_scale_factor_;
ESP_LOGV(TAG, "Got raw pressure=%d, scale factor =%.3f ", pressure_raw, pressure_scale_factor_);
int16_t temperature_raw = encode_uint16(data[3], data[4]);
int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
// scale factor is in Pa - convert to hPa
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);
this->publish_state(pressure);

View File

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

View File

@@ -14,6 +14,14 @@ CODEOWNERS = ["@Azimath"]
sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
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 = (
sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
@@ -24,6 +32,9 @@ CONFIG_SCHEMA = (
.extend(
{
cv.GenerateID(): cv.declare_id(SDP3XComponent),
cv.Optional(
CONF_MEASUREMENT_MODE, default="differential_pressure"
): cv.enum(MEASUREMENT_MODE),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -36,3 +47,4 @@ async def to_code(config):
await cg.register_component(var, config)
await i2c.register_i2c_device(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
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.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) {
// 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();
delay(5);
this->write_array(command, SENSEAIR_REQUEST_LENGTH);
if (response == nullptr)
return true;
bool ret = this->read_array(response, response_length);
this->flush();
return ret;

View File

@@ -71,7 +71,7 @@ void SNTPComponent::loop() {
if (!time.is_valid())
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);
this->time_sync_callback_.call();
this->has_time_ = true;

View File

@@ -22,7 +22,7 @@ i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffer
void TCA9548AComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up TCA9548A...");
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");
this->mark_failed();
return;

View File

@@ -24,6 +24,7 @@ class TCA9548AComponent : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::IO; }
void update();
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")
if CONF_RESTORE_VALUE in config:
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:
raise cv.Invalid(
"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:
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:
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();
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);
this->time_sync_callback_.call();

View File

@@ -3,6 +3,7 @@ from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.yaml_util import make_data_base
from esphome import pins, automation
from esphome.const import (
CONF_BAUD_RATE,
@@ -14,6 +15,17 @@ from esphome.const import (
CONF_DATA,
CONF_RX_BUFFER_SIZE,
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
@@ -31,6 +43,8 @@ ESP8266UartComponent = uart_ns.class_(
UARTDevice = uart_ns.class_("UARTDevice")
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
@@ -75,6 +89,59 @@ CONF_STOP_BITS = "stop_bits"
CONF_DATA_BITS = "data_bits"
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(
cv.Schema(
{
@@ -91,12 +158,38 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INVERT): cv.invalid(
"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),
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):
cg.add_global(uart_ns.using)
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_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!
UART_DEVICE_SCHEMA = cv.Schema(

View File

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

View File

@@ -2,9 +2,13 @@
#include <vector>
#include <cstring>
#include "esphome/core/defines.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#ifdef USE_UART_DEBUGGER
#include "esphome/core/automation.h"
#endif
namespace esphome {
namespace uart {
@@ -15,6 +19,14 @@ enum UARTParityOptions {
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);
class UARTComponent {
@@ -50,6 +62,12 @@ class UARTComponent {
void set_baud_rate(uint32_t baud_rate) { baud_rate_ = 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:
virtual void check_logger_conflict() = 0;
bool check_read_timeout_(size_t len = 1);
@@ -61,6 +79,9 @@ class UARTComponent {
uint8_t stop_bits_;
uint8_t data_bits_;
UARTParityOptions parity_;
#ifdef USE_UART_DEBUGGER
CallbackManager<void(UARTDirection, uint8_t)> debug_callback_{};
#endif
};
} // namespace uart

View File

@@ -117,26 +117,32 @@ void ESP32ArduinoUARTComponent::dump_config() {
void ESP32ArduinoUARTComponent::write_array(const uint8_t *data, size_t len) {
this->hw_serial_->write(data, len);
#ifdef USE_UART_DEBUGGER
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) {
if (!this->check_read_timeout_())
return false;
*data = this->hw_serial_->peek();
return true;
}
bool ESP32ArduinoUARTComponent::read_array(uint8_t *data, size_t len) {
if (!this->check_read_timeout_(len))
return false;
this->hw_serial_->readBytes(data, len);
#ifdef USE_UART_DEBUGGER
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;
}
int ESP32ArduinoUARTComponent::available() { return this->hw_serial_->available(); }
void ESP32ArduinoUARTComponent::flush() {
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++)
this->sw_serial_->write_byte(data[i]);
}
#ifdef USE_UART_DEBUGGER
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) {
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++)
data[i] = this->sw_serial_->read_byte();
}
#ifdef USE_UART_DEBUGGER
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;
}
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);
uart_write_bytes(this->uart_num_, data, len);
xSemaphoreGive(this->lock_);
#ifdef USE_UART_DEBUGGER
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) {
if (!this->check_read_timeout_())
return false;
@@ -152,6 +155,7 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) {
xSemaphoreGive(this->lock_);
return true;
}
bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
size_t length_to_read = 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)
uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_RATE_MS);
xSemaphoreGive(this->lock_);
#ifdef USE_UART_DEBUGGER
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;
}

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

View File

@@ -42,9 +42,9 @@ class ZaDataProcessor {
class ZaSensorStore {
public:
float co2 = NAN;
float temperature = NAN;
float humidity = NAN;
uint16_t co2 = -1;
uint16_t temperature = -1;
uint16_t humidity = -1;
void setup(InternalGPIOPin *pin_clock, InternalGPIOPin *pin_data);
static void interrupt(ZaSensorStore *arg);
@@ -79,7 +79,7 @@ class ZyAuraSensor : public PollingComponent {
sensor::Sensor *temperature_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

View File

@@ -296,9 +296,11 @@ def icon(value):
value = string_strict(value)
if not value:
return value
if value.startswith("mdi:"):
if re.match("^[\\w\\-]+:[\\w\\-]+$", 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):

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2021.11.0-dev"
__version__ = "2021.12.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -8,31 +8,11 @@ PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "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"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
SECRETS_FILES = {"secrets.yaml", "secrets.yml"}
CONF_ABOVE = "above"
CONF_ACCELERATION = "acceleration"
@@ -47,6 +27,7 @@ CONF_ACTIVE_POWER = "active_power"
CONF_ADDRESS = "address"
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
CONF_ADVANCED = "advanced"
CONF_AFTER = "after"
CONF_ALPHA = "alpha"
CONF_ALTITUDE = "altitude"
CONF_AND = "and"
@@ -93,6 +74,7 @@ CONF_BUFFER_SIZE = "buffer_size"
CONF_BUILD_PATH = "build_path"
CONF_BUS_VOLTAGE = "bus_voltage"
CONF_BUSY_PIN = "busy_pin"
CONF_BYTES = "bytes"
CONF_CALCULATED_LUX = "calculated_lux"
CONF_CALIBRATE_LINEAR = "calibrate_linear"
CONF_CALIBRATION = "calibration"
@@ -164,6 +146,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
CONF_DC_PIN = "dc_pin"
CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
CONF_DEBOUNCE = "debounce"
CONF_DEBUG = "debug"
CONF_DECAY_MODE = "decay_mode"
CONF_DECELERATION = "deceleration"
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_TRANSITION_LENGTH = "default_transition_length"
CONF_DELAY = "delay"
CONF_DELIMITER = "delimiter"
CONF_DELTA = "delta"
CONF_DEVICE = "device"
CONF_DEVICE_CLASS = "device_class"
@@ -184,6 +168,7 @@ CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
CONF_DISCOVERY = "discovery"
CONF_DISCOVERY_PREFIX = "discovery_prefix"
CONF_DISCOVERY_RETAIN = "discovery_retain"
CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
CONF_DISTANCE = "distance"
CONF_DITHER = "dither"
CONF_DIV_RATIO = "div_ratio"
@@ -192,6 +177,8 @@ CONF_DNS2 = "dns2"
CONF_DOMAIN = "domain"
CONF_DRY_ACTION = "dry_action"
CONF_DRY_MODE = "dry_mode"
CONF_DUMMY_RECEIVER = "dummy_receiver"
CONF_DUMMY_RECEIVER_ID = "dummy_receiver_id"
CONF_DUMP = "dump"
CONF_DURATION = "duration"
CONF_EAP = "eap"
@@ -871,9 +858,11 @@ DEVICE_CLASS_OPENING = "opening"
DEVICE_CLASS_PLUG = "plug"
DEVICE_CLASS_PRESENCE = "presence"
DEVICE_CLASS_PROBLEM = "problem"
DEVICE_CLASS_RUNNING = "running"
DEVICE_CLASS_SAFETY = "safety"
DEVICE_CLASS_SMOKE = "smoke"
DEVICE_CLASS_SOUND = "sound"
DEVICE_CLASS_TAMPER = "tamper"
DEVICE_CLASS_UPDATE = "update"
DEVICE_CLASS_VIBRATION = "vibration"
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);
}
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
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
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::can_proceed() { return true; }
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_ERROR;
enum RetryResult { DONE, RETRY };
class Component {
public:
/** Where the component's initialization should happen.
@@ -180,7 +182,35 @@ class Component {
*/
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.
*
@@ -198,6 +228,8 @@ class Component {
*/
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.
*
* @param name The identifier for this timeout function.

View File

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

View File

@@ -6,10 +6,10 @@
#include <cstring>
#if defined(USE_ESP8266)
#ifdef USE_WIFI
#include <ESP8266WiFi.h>
#endif
#include <osapi.h>
#include <user_interface.h>
// for xt_rsil()/xt_wsr_ps()
#include <Arduino.h>
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include <Esp.h>
#elif defined(USE_ESP_IDF)
@@ -30,8 +30,8 @@ namespace esphome {
static const char *const TAG = "helpers";
void get_mac_address_raw(uint8_t *mac) {
#ifdef USE_ESP32
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
#if defined(USE_ESP32)
#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
// 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
// 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
esp_efuse_mac_get_default(mac);
#endif
#endif
#if (defined USE_ESP8266 && defined USE_WIFI)
WiFi.macAddress(mac);
#elif defined(USE_ESP8266)
wifi_get_macaddr(STATION_IF, mac);
#endif
}
std::string get_mac_address() {
char tmp[20];
uint8_t mac[6];
get_mac_address_raw(mac);
#ifdef USE_WIFI
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);
return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
std::string get_mac_address_pretty() {
char tmp[20];
uint8_t mac[6];
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 std::string(tmp);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
#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) {
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;
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_WIFI
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
#else
IRAM_ATTR InterruptLock::InterruptLock() {}
IRAM_ATTR InterruptLock::~InterruptLock() {}
#endif
#endif
#ifdef USE_ESP32
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }

View File

@@ -25,14 +25,13 @@
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);
/// Get the MAC address as a string, using lower case hex notation.
/// This can be used as way to identify this ESP.
/// Get the device MAC address as a string, in lowercase hex notation.
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();
#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_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, ...);
class HighFrequencyLoopRequester {

View File

@@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
item->timeout = timeout;
item->last_execution = now;
item->last_execution_major = this->millis_major_;
item->f = std::move(func);
item->void_callback = std::move(func);
item->remove = false;
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_;
if (item->last_execution > now)
item->last_execution_major--;
item->f = std::move(func);
item->void_callback = std::move(func);
item->remove = false;
this->push_(std::move(item));
}
bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
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() {
if (this->empty_())
return {};
@@ -95,10 +129,9 @@ void IRAM_ATTR HOT Scheduler::call() {
ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
while (!this->empty_()) {
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)", type, item->name.c_str(),
item->interval, item->last_execution, item->last_execution_major, item->next_execution(),
item->next_execution_major());
ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(),
item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
item->next_execution(), item->next_execution_major());
this->pop_raw_();
old_items.push_back(std::move(item));
@@ -129,6 +162,7 @@ void IRAM_ATTR HOT Scheduler::call() {
}
while (!this->empty_()) {
RetryResult retry_result = RETRY;
// use scoping to indicate visibility of `item` variable
{
// Don't copy-by value yet
@@ -147,17 +181,19 @@ void IRAM_ATTR HOT Scheduler::call() {
}
#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)", type, item->name.c_str(),
item->interval, item->last_execution, now);
ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(),
item->name.c_str(), item->interval, item->last_execution, now);
#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 cancelled
{
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;
}
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) {
const uint32_t before = item->last_execution;
const uint32_t amount = (now - item->last_execution) / item->interval;
item->last_execution += amount * item->interval;
if (item->last_execution < before)
item->last_execution_major++;
if (item->type == SchedulerItem::RETRY)
item->interval *= item->backoff_multiplier;
}
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);
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();
void call();
@@ -25,13 +29,20 @@ class Scheduler {
struct SchedulerItem {
Component *component;
std::string name;
enum Type { TIMEOUT, INTERVAL } type;
enum Type { TIMEOUT, INTERVAL, RETRY } type;
union {
uint32_t interval;
uint32_t timeout;
};
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;
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);
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_();

View File

@@ -970,16 +970,17 @@ def start_web_server(args):
server.add_socket(socket)
else:
_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,
settings.config_dir,
)
app.listen(args.port)
app.listen(args.port, args.address)
if args.open_ui:
import webbrowser
webbrowser.open(f"localhost:{args.port}")
webbrowser.open(f"http://{args.address}:{args.port}")
if settings.status_use_ping:
status_thread = PingStatusThread()

View File

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

View File

@@ -28,6 +28,7 @@ build_flags =
lib_deps =
esphome/noise-c@0.1.4 ; api
makuna/NeoPixelBus@2.6.9 ; neopixelbus
esphome/Improv@1.0.0 ; improv_serial / esp32_improv
build_flags =
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
src_filter =
@@ -41,15 +42,16 @@ lib_deps =
${common.lib_deps}
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
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
mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651
seeed-studio/Grove - Laser PM2.5 Sensor HM3301@1.0.3 ; hm3301
glmnet/Dsmr@0.5 ; dsmr
rweather/Crypto@0.2.0 ; dsmr
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 =
${common.build_flags}
-DUSE_ARDUINO

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