Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2023-07-24 17:12:27 +02:00 committed by GitHub
commit 28236c9db4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 410 additions and 128 deletions

4
.gitignore vendored
View file

@ -129,4 +129,6 @@ tests/.esphome/
sdkconfig.*
!sdkconfig.defaults
.tests/
.tests/
/components

View file

@ -22,16 +22,22 @@ RUN \
python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \
python3-cffi=1.14.5-1 \
&& rm -rf \
python3-cffi=1.14.5-1; \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.9.2-3 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
libjpeg-dev=1:2.0.6-4 \
libcairo2=1.16.0-5; \
fi; \
rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*

View file

@ -365,10 +365,16 @@ def command_wizard(args):
def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not CORE.verbose:
config = strip_default_ids(config)
safe_print(yaml_util.dump(config, args.show_secrets))
output = yaml_util.dump(config, args.show_secrets)
# add the console decoration so the front-end can hide the secrets
if not args.show_secrets:
output = re.sub(
r"(password|key|psk|ssid)\:\s(.*)", r"\1: \\033[5m\2\\033[6m", output
)
safe_print(output)
_LOGGER.info("Configuration is valid!")
return 0

View file

@ -1420,6 +1420,7 @@ message VoiceAssistantRequest {
bool start = 1;
string conversation_id = 2;
bool use_vad = 3;
}
message VoiceAssistantResponse {

View file

@ -907,12 +907,13 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id) {
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) {
if (!this->voice_assistant_subscription_)
return false;
VoiceAssistantRequest msg;
msg.start = start;
msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(msg);
}
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {

View file

@ -124,7 +124,7 @@ class APIConnection : public APIServerConnection {
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
this->voice_assistant_subscription_ = msg.subscribe;
}
bool request_voice_assistant(bool start, const std::string &conversation_id);
bool request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad);
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
#endif

View file

@ -6348,6 +6348,10 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
this->start = value.as_bool();
return true;
}
case 3: {
this->use_vad = value.as_bool();
return true;
}
default:
return false;
}
@ -6365,6 +6369,7 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start);
buffer.encode_string(2, this->conversation_id);
buffer.encode_bool(3, this->use_vad);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const {
@ -6377,6 +6382,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" conversation_id: ");
out.append("'").append(this->conversation_id).append("'");
out.append("\n");
out.append(" use_vad: ");
out.append(YESNO(this->use_vad));
out.append("\n");
out.append("}");
}
#endif

View file

@ -1655,6 +1655,7 @@ class VoiceAssistantRequest : public ProtoMessage {
public:
bool start{false};
std::string conversation_id{};
bool use_vad{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;

View file

@ -323,16 +323,16 @@ void APIServer::on_shutdown() {
}
#ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant(const std::string &conversation_id) {
bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) {
for (auto &c : this->clients_) {
if (c->request_voice_assistant(true, conversation_id))
if (c->request_voice_assistant(true, conversation_id, use_vad))
return true;
}
return false;
}
void APIServer::stop_voice_assistant() {
for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, ""))
if (c->request_voice_assistant(false, "", false))
return;
}
}

View file

@ -81,7 +81,7 @@ class APIServer : public Component, public Controller {
#endif
#ifdef USE_VOICE_ASSISTANT
bool start_voice_assistant(const std::string &conversation_id);
bool start_voice_assistant(const std::string &conversation_id, bool use_vad);
void stop_voice_assistant();
#endif

View file

@ -114,7 +114,7 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
if (!decoded.has_value())
return false;
// Decoded remote state y 3 bytes long code.
uint32_t remote_state = *decoded;
uint32_t remote_state = (*decoded).second;
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
if ((remote_state & 0xFF0000) != 0xB20000)
return false;

View file

@ -51,7 +51,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
if (universe < first_universe_ || universe > get_last_universe())
return false;
int output_offset = (universe - first_universe_) * get_lights_per_universe();
int32_t output_offset = (universe - first_universe_) * get_lights_per_universe();
// limit amount of lights per universe and received
int output_end =
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Union
from typing import Union, Optional
from pathlib import Path
import logging
import os
@ -42,6 +42,7 @@ from .const import ( # noqa
KEY_REFRESH,
KEY_REPO,
KEY_SDKCONFIG_OPTIONS,
KEY_SUBMODULES,
KEY_VARIANT,
VARIANT_ESP32C3,
VARIANT_FRIENDLY,
@ -120,17 +121,28 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component(
name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None
*,
name: str,
repo: str,
ref: str = None,
path: str = None,
refresh: TimePeriod = None,
components: Optional[list[str]] = None,
submodules: Optional[list[str]] = None,
):
"""Add an esp-idf component to the project."""
if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project")
if components is None:
components = []
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
KEY_REPO: repo,
KEY_REF: ref,
KEY_PATH: path,
KEY_REFRESH: refresh,
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
}
@ -163,23 +175,23 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
# The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0)
ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 5)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0)
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0)
def _arduino_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"),
"latest": (cv.Version(2, 0, 7), None),
"latest": (cv.Version(2, 0, 9), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
@ -214,7 +226,7 @@ def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 0, 1), None),
"latest": (cv.Version(5, 1, 0), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
@ -536,20 +548,41 @@ def copy_files():
ref=component[KEY_REF],
refresh=component[KEY_REFRESH],
domain="idf_components",
submodules=component[KEY_SUBMODULES],
)
mkdir_p(CORE.relative_build_path("components"))
component_dir = repo_dir
if component[KEY_PATH] is not None:
component_dir = component_dir / component[KEY_PATH]
shutil.copytree(
component_dir,
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git", ".github"),
symlinks=True,
ignore_dangling_symlinks=True,
)
if component[KEY_COMPONENTS] == ["*"]:
shutil.copytree(
component_dir,
CORE.relative_build_path("components"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
elif len(component[KEY_COMPONENTS]) > 0:
for comp in component[KEY_COMPONENTS]:
shutil.copytree(
component_dir / comp,
CORE.relative_build_path(f"components/{comp}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
else:
shutil.copytree(
component_dir,
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
dir = os.path.dirname(__file__)
post_build_file = os.path.join(dir, "post_build.py.script")

View file

@ -1201,6 +1201,10 @@ BOARDS = {
"name": "BPI-Bit",
"variant": VARIANT_ESP32,
},
"bpi_leaf_s3": {
"name": "BPI-Leaf-S3",
"variant": VARIANT_ESP32S3,
},
"briki_abc_esp32": {
"name": "Briki ABC (MBC-WB) - ESP32",
"variant": VARIANT_ESP32,
@ -1217,6 +1221,10 @@ BOARDS = {
"name": "Connaxio's Espoir",
"variant": VARIANT_ESP32,
},
"cytron_maker_feather_aiot_s3": {
"name": "Cytron Maker Feather AIoT S3",
"variant": VARIANT_ESP32S3,
},
"d-duino-32": {
"name": "D-duino-32",
"variant": VARIANT_ESP32,
@ -1225,6 +1233,10 @@ BOARDS = {
"name": "Deneyap Kart 1A",
"variant": VARIANT_ESP32,
},
"deneyapkart1Av2": {
"name": "Deneyap Kart 1A v2",
"variant": VARIANT_ESP32S3,
},
"deneyapkartg": {
"name": "Deneyap Kart G",
"variant": VARIANT_ESP32C3,
@ -1237,6 +1249,10 @@ BOARDS = {
"name": "Deneyap Mini",
"variant": VARIANT_ESP32S2,
},
"deneyapminiv2": {
"name": "Deneyap Mini v2",
"variant": VARIANT_ESP32S2,
},
"denky32": {
"name": "Denky32 (WROOM32)",
"variant": VARIANT_ESP32,
@ -1265,6 +1281,10 @@ BOARDS = {
"name": "Espressif ESP32-C3-DevKitM-1",
"variant": VARIANT_ESP32C3,
},
"esp32-c3-m1i-kit": {
"name": "Ai-Thinker ESP-C3-M1-I-Kit",
"variant": VARIANT_ESP32C3,
},
"esp32cam": {
"name": "AI Thinker ESP32-CAM",
"variant": VARIANT_ESP32,
@ -1329,6 +1349,10 @@ BOARDS = {
"name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
"variant": VARIANT_ESP32S3,
},
"esp32-s3-korvo-2": {
"name": "Espressif ESP32-S3-Korvo-2",
"variant": VARIANT_ESP32S3,
},
"esp32thing": {
"name": "SparkFun ESP32 Thing",
"variant": VARIANT_ESP32,
@ -1637,6 +1661,10 @@ BOARDS = {
"name": "Noduino Quantum",
"variant": VARIANT_ESP32,
},
"redpill_esp32s3": {
"name": "Munich Labs RedPill ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"seeed_xiao_esp32c3": {
"name": "Seeed Studio XIAO ESP32C3",
"variant": VARIANT_ESP32C3,

View file

@ -9,6 +9,7 @@ KEY_REPO = "repo"
KEY_REF = "ref"
KEY_REFRESH = "refresh"
KEY_PATH = "path"
KEY_SUBMODULES = "submodules"
VARIANT_ESP32 = "ESP32"
VARIANT_ESP32S2 = "ESP32S2"

View file

@ -112,7 +112,6 @@ CONFIG_SCHEMA = cv.All(
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2410",
baud_rate=256000,
require_tx=True,
require_rx=True,
parity="NONE",

View file

@ -86,10 +86,10 @@ async def to_code(config):
5, 0, 0
):
add_idf_component(
"mdns",
"https://github.com/espressif/esp-protocols.git",
"mdns-v1.0.9",
"components/mdns",
name="mdns",
repo="https://github.com/espressif/esp-protocols.git",
ref="mdns-v1.0.9",
path="components/mdns",
)
if config[CONF_DISABLED]:

View file

@ -28,6 +28,7 @@ from esphome.const import (
DEVICE_CLASS_DATA_RATE,
DEVICE_CLASS_DATA_SIZE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_ENERGY_STORAGE,
@ -42,6 +43,7 @@ from esphome.const import (
DEVICE_CLASS_NITROGEN_MONOXIDE,
DEVICE_CLASS_NITROUS_OXIDE,
DEVICE_CLASS_OZONE,
DEVICE_CLASS_PH,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
@ -81,6 +83,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_DATA_RATE,
DEVICE_CLASS_DATA_SIZE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_DURATION,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_ENERGY_STORAGE,
@ -95,6 +98,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_NITROGEN_MONOXIDE,
DEVICE_CLASS_NITROUS_OXIDE,
DEVICE_CLASS_OZONE,
DEVICE_CLASS_PH,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,

View file

@ -17,6 +17,7 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_GROUP,
CONF_DEVICE,
CONF_SECOND,
CONF_STATE,
CONF_CHANNEL,
CONF_FAMILY,
@ -39,6 +40,7 @@ AUTO_LOAD = ["binary_sensor"]
CONF_RECEIVER_ID = "receiver_id"
CONF_TRANSMITTER_ID = "transmitter_id"
CONF_FIRST = "first"
ns = remote_base_ns = cg.esphome_ns.namespace("remote_base")
RemoteProtocol = ns.class_("RemoteProtocol")
@ -349,19 +351,48 @@ async def canalsatld_action(var, config, args):
CoolixAction,
CoolixDumper,
) = declare_protocol("Coolix")
COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA)
COOLIX_BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215),
cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215),
cv.Optional(CONF_DATA): cv.invalid(
"'data' option has been removed in ESPHome 2023.8. "
"Use the 'first' and 'second' options instead."
),
}
)
COOLIX_SENSOR_SCHEMA = cv.Any(cv.hex_int_range(0, 16777215), COOLIX_BASE_SCHEMA)
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SENSOR_SCHEMA)
def coolix_binary_sensor(var, config):
cg.add(
var.set_data(
cg.StructInitializer(
CoolixData,
("data", config[CONF_DATA]),
if isinstance(config, dict):
cg.add(
var.set_data(
cg.StructInitializer(
CoolixData,
("first", config[CONF_FIRST]),
("second", config[CONF_SECOND]),
)
)
)
)
else:
cg.add(
var.set_data(
cg.StructInitializer(CoolixData, ("first", 0), ("second", config))
)
)
@register_action("coolix", CoolixAction, COOLIX_BASE_SCHEMA)
async def coolix_action(var, config, args):
template_ = await cg.templatable(config[CONF_FIRST], args, cg.uint32)
cg.add(var.set_first(template_))
template_ = await cg.templatable(config[CONF_SECOND], args, cg.uint32)
cg.add(var.set_second(template_))
@register_trigger("coolix", CoolixTrigger, CoolixData)
@ -374,12 +405,6 @@ def coolix_dumper(var, config):
pass
@register_action("coolix", CoolixAction, COOLIX_SCHEMA)
async def coolix_action(var, config, args):
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32)
cg.add(var.set_data(template_))
# Dish
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
"Dish"

View file

@ -15,11 +15,21 @@ static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US;
static const int32_t FOOTER_MARK_US = 1 * TICK_US;
static const int32_t FOOTER_SPACE_US = 10 * TICK_US;
static void encode_data(RemoteTransmitData *dst, const CoolixData &src) {
// Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
bool CoolixData::operator==(const CoolixData &other) const {
if (this->first == 0)
return this->second == other.first || this->second == other.second;
if (other.first == 0)
return other.second == this->first || other.second == this->second;
return this->first == other.first && this->second == other.second;
}
static void encode_frame(RemoteTransmitData *dst, const uint32_t &src) {
// Append header
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
// Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted.
for (unsigned shift = 16;; shift -= 8) {
// Grab a bytes worth of data.
// Grab a bytes worth of data
const uint8_t byte = src >> shift;
// Normal
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
@ -27,27 +37,33 @@ static void encode_data(RemoteTransmitData *dst, const CoolixData &src) {
// Inverted
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
// Data end
if (shift == 0)
// End of frame
if (shift == 0) {
// Append footer
dst->mark(FOOTER_MARK_US);
break;
}
}
}
void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
encode_data(dst, data);
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
encode_data(dst, data);
dst->mark(FOOTER_MARK_US);
dst->reserve(100 + 100 * data.has_second());
encode_frame(dst, data.first);
if (data.has_second()) {
dst->space(FOOTER_SPACE_US);
encode_frame(dst, data.second);
}
}
static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
static bool decode_frame(RemoteReceiveData &src, uint32_t &dst) {
// Checking for header
if (!src.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
return false;
// Reading data
uint32_t data = 0;
for (unsigned n = 3;; data <<= 8) {
// Read byte
// Reading byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_mark(BIT_MARK_US))
return false;
@ -57,13 +73,16 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
return false;
}
}
// Check for inverse byte
// Checking for inverted byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
return false;
}
// Checking the end of reading
// End of frame
if (--n == 0) {
// Checking for footer
if (!src.expect_mark(FOOTER_MARK_US))
return false;
dst = data;
return true;
}
@ -71,15 +90,24 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
}
optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) {
CoolixData first, second;
if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) &&
data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) &&
decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second)
return first;
return {};
CoolixData result;
const auto size = data.size();
if ((size != 200 && size != 100) || !decode_frame(data, result.first))
return {};
if (size == 100 || !data.expect_space(FOOTER_SPACE_US) || !decode_frame(data, result.second))
result.second = 0;
return result;
}
void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); }
void CoolixProtocol::dump(const CoolixData &data) {
if (data.is_strict()) {
ESP_LOGD(TAG, "Received Coolix: 0x%06X", data.first);
} else if (data.has_second()) {
ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second);
} else {
ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X]", data.first);
}
}
} // namespace remote_base
} // namespace esphome

View file

@ -7,7 +7,16 @@
namespace esphome {
namespace remote_base {
using CoolixData = uint32_t;
struct CoolixData {
CoolixData() {}
CoolixData(uint32_t a) : first(a), second(a) {}
CoolixData(uint32_t a, uint32_t b) : first(a), second(b) {}
bool operator==(const CoolixData &other) const;
bool is_strict() const { return this->first == this->second; }
bool has_second() const { return this->second != 0; }
uint32_t first;
uint32_t second;
};
class CoolixProtocol : public RemoteProtocol<CoolixData> {
public:
@ -19,10 +28,10 @@ class CoolixProtocol : public RemoteProtocol<CoolixData> {
DECLARE_REMOTE_PROTOCOL(Coolix)
template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> {
TEMPLATABLE_VALUE(CoolixData, data)
TEMPLATABLE_VALUE(uint32_t, first)
TEMPLATABLE_VALUE(uint32_t, second)
void encode(RemoteTransmitData *dst, Ts... x) override {
CoolixData data = this->data_.value(x...);
CoolixProtocol().encode(dst, data);
CoolixProtocol().encode(dst, {this->first_.value(x...), this->second_.value(x...)});
}
};

View file

@ -57,6 +57,7 @@ from esphome.const import (
DEVICE_CLASS_NITROGEN_MONOXIDE,
DEVICE_CLASS_NITROUS_OXIDE,
DEVICE_CLASS_OZONE,
DEVICE_CLASS_PH,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
@ -114,6 +115,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_NITROGEN_MONOXIDE,
DEVICE_CLASS_NITROUS_OXIDE,
DEVICE_CLASS_OZONE,
DEVICE_CLASS_PH,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,

View file

@ -0,0 +1,57 @@
#include "sigma_delta_output.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sigma_delta_output {
static const char *const TAG = "output.sigma_delta";
void SigmaDeltaOutput::setup() {
if (this->pin_)
this->pin_->setup();
}
void SigmaDeltaOutput::dump_config() {
ESP_LOGCONFIG(TAG, "Sigma Delta Output:");
LOG_PIN(" Pin: ", this->pin_);
if (this->state_change_trigger_) {
ESP_LOGCONFIG(TAG, " State change automation configured");
}
if (this->turn_on_trigger_) {
ESP_LOGCONFIG(TAG, " Turn on automation configured");
}
if (this->turn_off_trigger_) {
ESP_LOGCONFIG(TAG, " Turn off automation configured");
}
LOG_UPDATE_INTERVAL(this);
LOG_FLOAT_OUTPUT(this);
}
void SigmaDeltaOutput::update() {
this->accum_ += this->state_;
const bool next_value = this->accum_ > 0;
if (next_value) {
this->accum_ -= 1.;
}
if (next_value != this->value_) {
this->value_ = next_value;
if (this->pin_) {
this->pin_->digital_write(next_value);
}
if (this->state_change_trigger_) {
this->state_change_trigger_->trigger(next_value);
}
if (next_value && this->turn_on_trigger_) {
this->turn_on_trigger_->trigger();
} else if (!next_value && this->turn_off_trigger_) {
this->turn_off_trigger_->trigger();
}
}
}
} // namespace sigma_delta_output
} // namespace esphome

View file

@ -1,9 +1,12 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
namespace sigma_delta_output {
class SigmaDeltaOutput : public PollingComponent, public output::FloatOutput {
public:
Trigger<> *get_turn_on_trigger() {
@ -25,31 +28,9 @@ class SigmaDeltaOutput : public PollingComponent, public output::FloatOutput {
void set_pin(GPIOPin *pin) { this->pin_ = pin; };
void write_state(float state) override { this->state_ = state; }
void update() override {
this->accum_ += this->state_;
const bool next_value = this->accum_ > 0;
if (next_value) {
this->accum_ -= 1.;
}
if (next_value != this->value_) {
this->value_ = next_value;
if (this->pin_) {
this->pin_->digital_write(next_value);
}
if (this->state_change_trigger_) {
this->state_change_trigger_->trigger(next_value);
}
if (next_value && this->turn_on_trigger_) {
this->turn_on_trigger_->trigger();
} else if (!next_value && this->turn_off_trigger_) {
this->turn_off_trigger_->trigger();
}
}
}
void setup() override;
void dump_config() override;
void update() override;
protected:
GPIOPin *pin_{nullptr};

View file

@ -130,7 +130,7 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
void VoiceAssistant::request_start(bool continuous) {
ESP_LOGD(TAG, "Requesting start...");
if (!api::global_api_server->start_voice_assistant(this->conversation_id_)) {
if (!api::global_api_server->start_voice_assistant(this->conversation_id_, this->silence_detection_)) {
ESP_LOGW(TAG, "Could not request start.");
this->error_trigger_->trigger("not-connected", "Could not request start.");
this->continuous_ = false;

View file

@ -25,10 +25,9 @@ namespace voice_assistant {
// Version 1: Initial version
// Version 2: Adds raw speaker support
// Version 3: Adds continuous support
// Version 3: Unused/skip
static const uint32_t INITIAL_VERSION = 1;
static const uint32_t SPEAKER_SUPPORT = 2;
static const uint32_t SILENCE_DETECTION_SUPPORT = 3;
class VoiceAssistant : public Component {
public:
@ -48,9 +47,6 @@ class VoiceAssistant : public Component {
uint32_t get_version() const {
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
if (this->silence_detection_) {
return SILENCE_DETECTION_SUPPORT;
}
return SPEAKER_SUPPORT;
}
#endif

View file

@ -988,6 +988,7 @@ DEVICE_CLASS_OCCUPANCY = "occupancy"
DEVICE_CLASS_OPENING = "opening"
DEVICE_CLASS_OUTLET = "outlet"
DEVICE_CLASS_OZONE = "ozone"
DEVICE_CLASS_PH = "ph"
DEVICE_CLASS_PLUG = "plug"
DEVICE_CLASS_PM1 = "pm1"
DEVICE_CLASS_PM10 = "pm10"

View file

@ -475,6 +475,7 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
for (auto &cb : this->callbacks_)
cb(args...);
}
size_t size() const { return this->callbacks_.size(); }
/// Call all callbacks in this manager.
void operator()(Ts... args) { call(args...); }

View file

@ -25,6 +25,7 @@ import tornado.ioloop
import tornado.iostream
import tornado.netutil
import tornado.process
import tornado.queues
import tornado.web
import tornado.websocket
import yaml
@ -92,6 +93,10 @@ class DashboardSettings:
def using_auth(self):
return self.using_password or self.using_ha_addon_auth
@property
def streamer_mode(self):
return get_bool_env("ESPHOME_STREAMER_MODE")
def check_password(self, username, password):
if not self.using_auth:
return True
@ -130,7 +135,7 @@ def template_args():
"docs_link": docs_link,
"get_static_file_url": get_static_file_url,
"relative_url": settings.relative_url,
"streamer_mode": get_bool_env("ESPHOME_STREAMER_MODE"),
"streamer_mode": settings.streamer_mode,
"config_dir": settings.config_dir,
}
@ -202,7 +207,11 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs):
super().__init__(application, request, **kwargs)
self._proc = None
self._queue = None
self._is_closed = False
# Windows doesn't support non-blocking pipes,
# use Popen() with a reading thread instead
self._use_popen = os.name == "nt"
@authenticated
def on_message(self, message):
@ -224,13 +233,28 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
return
command = self.build_command(json_message)
_LOGGER.info("Running command '%s'", " ".join(shlex_quote(x) for x in command))
self._proc = tornado.process.Subprocess(
command,
stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT,
stdin=tornado.process.Subprocess.STREAM,
)
self._proc.set_exit_callback(self._proc_on_exit)
if self._use_popen:
self._queue = tornado.queues.Queue()
# pylint: disable=consider-using-with
self._proc = subprocess.Popen(
command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
stdout_thread = threading.Thread(target=self._stdout_thread)
stdout_thread.daemon = True
stdout_thread.start()
else:
self._proc = tornado.process.Subprocess(
command,
stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT,
stdin=tornado.process.Subprocess.STREAM,
)
self._proc.set_exit_callback(self._proc_on_exit)
tornado.ioloop.IOLoop.current().spawn_callback(self._redirect_stdout)
@property
@ -252,7 +276,13 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
while True:
try:
data = yield self._proc.stdout.read_until_regex(reg)
if self._use_popen:
data = yield self._queue.get()
if data is None:
self._proc_on_exit(self._proc.poll())
break
else:
data = yield self._proc.stdout.read_until_regex(reg)
except tornado.iostream.StreamClosedError:
break
data = codecs.decode(data, "utf8", "replace")
@ -260,6 +290,19 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
_LOGGER.debug("> stdout: %s", data)
self.write_message({"event": "line", "data": data})
def _stdout_thread(self):
if not self._use_popen:
return
while True:
data = self._proc.stdout.readline()
if data:
data = data.replace(b"\r", b"")
self._queue.put_nowait(data)
if self._proc.poll() is not None:
break
self._proc.wait(1.0)
self._queue.put_nowait(None)
def _proc_on_exit(self, returncode):
if not self._is_closed:
# Check if the proc was not forcibly closed
@ -270,7 +313,10 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
# Check if proc exists (if 'start' has been run)
if self.is_process_active:
_LOGGER.debug("Terminating process")
self._proc.proc.terminate()
if self._use_popen:
self._proc.terminate()
else:
self._proc.proc.terminate()
# Shutdown proc on WS close
self._is_closed = True
@ -354,7 +400,10 @@ class EsphomeCompileHandler(EsphomeCommandWebSocket):
class EsphomeValidateHandler(EsphomeCommandWebSocket):
def build_command(self, json_message):
config_file = settings.rel_path(json_message["configuration"])
return ["esphome", "--dashboard", "config", config_file]
command = ["esphome", "--dashboard", "config", config_file]
if not settings.streamer_mode:
command.append("--show-secrets")
return command
class EsphomeCleanMqttHandler(EsphomeCommandWebSocket):
@ -1105,7 +1154,7 @@ class JsonConfigRequestHandler(BaseHandler):
self.send_error(404)
return
args = ["esphome", "config", settings.rel_path(configuration), "--show-secrets"]
args = ["esphome", "config", filename, "--show-secrets"]
rc, stdout, _ = run_system_command(*args)

View file

@ -15,6 +15,7 @@ _LOGGER = logging.getLogger(__name__)
def run_git_command(cmd, cwd=None) -> str:
_LOGGER.debug("Running git command: %s", " ".join(cmd))
try:
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
except FileNotFoundError as err:
@ -48,6 +49,7 @@ def clone_or_update(
domain: str,
username: str = None,
password: str = None,
submodules: Optional[list[str]] = None,
) -> tuple[Path, Optional[Callable[[], None]]]:
key = f"{url}@{ref}"
@ -74,6 +76,14 @@ def clone_or_update(
run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir))
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
if submodules is not None:
_LOGGER.info(
"Initialising submodules (%s) for %s", ", ".join(submodules), key
)
run_git_command(
["git", "submodule", "update", "--init"] + submodules, str(repo_dir)
)
else:
# Check refresh needed
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
@ -97,6 +107,14 @@ def clone_or_update(
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
if submodules is not None:
_LOGGER.info(
"Updating submodules (%s) for %s", ", ".join(submodules), key
)
run_git_command(
["git", "submodule", "update", "--init"] + submodules, str(repo_dir)
)
def revert():
_LOGGER.info("Reverting changes to %s -> %s", key, old_sha)
run_git_command(["git", "reset", "--hard", old_sha], str(repo_dir))

View file

@ -71,6 +71,8 @@ def setup_log(
) -> None:
import colorama
colorama.init()
if debug:
log_level = logging.DEBUG
CORE.verbose = True
@ -82,7 +84,6 @@ def setup_log(
logging.getLogger("urllib3").setLevel(logging.WARNING)
colorama.init()
logging.getLogger().handlers[0].setFormatter(
ESPHomeLogFormatter(include_timestamp=include_timestamp)
)

View file

@ -105,7 +105,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script
; This are common settings for the ESP32 (all variants) using Arduino.
[common:esp32-arduino]
extends = common:arduino
platform = platformio/espressif32@5.3.0
platform = platformio/espressif32@5.4.0
platform_packages =
platformio/framework-arduinoespressif32@~3.20005.0
@ -134,9 +134,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf]
extends = common:idf
platform = platformio/espressif32@5.3.0
platform = platformio/espressif32@5.4.0
platform_packages =
platformio/framework-espidf@~3.40404.0
platformio/framework-espidf@~3.40405.0
framework = espidf
lib_deps =

View file

@ -1,5 +1,5 @@
voluptuous==0.13.1
PyYAML==6.0
PyYAML==6.0.1
paho-mqtt==1.6.1
colorama==0.4.6
tornado==6.3.2
@ -8,7 +8,7 @@ tzdata>=2021.1 # from time
pyserial==3.5
platformio==6.1.7 # When updating platformio, also update Dockerfile
esptool==4.6.2
click==8.1.3
click==8.1.5
esphome-dashboard==20230711.0
aioesphomeapi==15.0.0
zeroconf==0.69.0

View file

@ -1609,6 +1609,18 @@ binary_sensor:
-2267,
1709,
]
- platform: remote_receiver
name: Coolix Test 1
coolix: 0xB21F98
- platform: remote_receiver
name: Coolix Test 2
coolix:
first: 0xB2E003
- platform: remote_receiver
name: Coolix Test 3
coolix:
first: 0xB2E003
second: 0xB21F98
- platform: as3935
name: Storm Alert
- platform: analog_threshold
@ -2283,8 +2295,16 @@ switch:
- platform: template
name: MIDEA_RAW
turn_on_action:
remote_transmitter.transmit_midea:
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
- remote_transmitter.transmit_coolix:
first: 0xB21F98
- remote_transmitter.transmit_coolix:
first: 0xB21F98
second: 0xB21F98
- remote_transmitter.transmit_coolix:
first: !lambda "return 0xB21F98;"
second: !lambda "return 0xB21F98;"
- remote_transmitter.transmit_midea:
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
- platform: gpio
name: "MCP23S08 Pin #0"
pin:
@ -2868,6 +2888,9 @@ tm1651:
remote_receiver:
pin: GPIO32
dump: all
on_coolix:
then:
delay: !lambda "return x.first + x.second;"
status_led:
pin: GPIO2