mirror of
https://github.com/esphome/esphome.git
synced 2025-11-03 08:31:47 +00:00
Compare commits
60 Commits
2021.11.0b
...
improv_ser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2232038e8d | ||
|
|
ceb9b1d1ff | ||
|
|
ccfa1e23f0 | ||
|
|
290da8df2d | ||
|
|
4b1d73791d | ||
|
|
7e8012c1a0 | ||
|
|
15cd602e8b | ||
|
|
598f5b241f | ||
|
|
335e69e6cd | ||
|
|
05fe5db030 | ||
|
|
710096b1c6 | ||
|
|
07b882c801 | ||
|
|
3e5331a263 | ||
|
|
897277992b | ||
|
|
1424091ee5 | ||
|
|
61ec16cdfc | ||
|
|
e5cb5756aa | ||
|
|
9e1c3e8f01 | ||
|
|
448e1690aa | ||
|
|
8267f01ccd | ||
|
|
6f9439e1bc | ||
|
|
06994c0dfc | ||
|
|
dee5d639e2 | ||
|
|
df6730be55 | ||
|
|
6c1ef398bb | ||
|
|
0469e19f54 | ||
|
|
dbcfa7b599 | ||
|
|
df68403b6d | ||
|
|
57bdc2b885 | ||
|
|
f565ff5def | ||
|
|
8ece639987 | ||
|
|
b35f509784 | ||
|
|
f1954df573 | ||
|
|
9e4fa5dcf1 | ||
|
|
0809673ba9 | ||
|
|
b386284180 | ||
|
|
515519bc87 | ||
|
|
5404163be0 | ||
|
|
0b193eee43 | ||
|
|
7333123ba4 | ||
|
|
d99c5ed890 | ||
|
|
04740fbcbb | ||
|
|
6a7440f7d3 | ||
|
|
14299bb2cc | ||
|
|
66cebfc992 | ||
|
|
108b8e6705 | ||
|
|
4eaa6afa4d | ||
|
|
f643a46bbf | ||
|
|
aae63a7ff3 | ||
|
|
582567696e | ||
|
|
2e0c89409d | ||
|
|
7bb7456a8b | ||
|
|
0372e12b81 | ||
|
|
a6873c1520 | ||
|
|
f11220da3a | ||
|
|
bb9793d5b7 | ||
|
|
e99af991ec | ||
|
|
abf3708cc2 | ||
|
|
4395d6156d | ||
|
|
04ba53c870 |
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(®, 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...");
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
67
esphome/components/ble_client/output/__init__.py
Normal file
67
esphome/components/ble_client/output/__init__.py
Normal 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)
|
||||
71
esphome/components/ble_client/output/ble_binary_output.cpp
Normal file
71
esphome/components/ble_client/output/ble_binary_output.cpp
Normal 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
|
||||
39
esphome/components/ble_client/output/ble_binary_output.h
Normal file
39
esphome/components/ble_client/output/ble_binary_output.h
Normal 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
|
||||
@@ -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:
|
||||
|
||||
@@ -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]):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ enum Protocol {
|
||||
PROTOCOL_GREE,
|
||||
PROTOCOL_GREEYAA,
|
||||
PROTOCOL_GREEYAN,
|
||||
PROTOCOL_GREEYAC,
|
||||
PROTOCOL_HISENSE_AUD,
|
||||
PROTOCOL_HITACHI,
|
||||
PROTOCOL_HYUNDAI,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_))
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
193
esphome/components/uart/uart_debugger.cpp
Normal file
193
esphome/components/uart/uart_debugger.cpp
Normal 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
|
||||
101
esphome/components/uart/uart_debugger.h
Normal file
101
esphome/components/uart/uart_debugger.h
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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_();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user