Merge branch 'dev' into nvds-new-espnow

This commit is contained in:
NP v/d Spek 2024-08-24 12:10:36 +02:00 committed by GitHub
commit 1713e28b7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
414 changed files with 11581 additions and 2070 deletions

View file

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.5.0
uses: docker/build-push-action@v6.7.0
with:
context: .
file: ./docker/Dockerfile
@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.5.0
uses: docker/build-push-action@v6.7.0
with:
context: .
file: ./docker/Dockerfile

View file

@ -46,7 +46,7 @@ jobs:
with:
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.5.0
uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0

View file

@ -9,6 +9,7 @@ on:
paths:
- "**"
- "!.github/workflows/*.yml"
- "!.github/actions/build-image/*"
- ".github/workflows/ci.yml"
- "!.yamllint"
- "!.github/dependabot.yml"
@ -396,7 +397,7 @@ jobs:
file: ${{ fromJson(needs.list-components.outputs.components) }}
steps:
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
@ -450,7 +451,7 @@ jobs:
run: echo ${{ matrix.components }}
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7

View file

@ -90,7 +90,7 @@ jobs:
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.5.0
uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU
if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.2.0
@ -184,7 +184,7 @@ jobs:
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.5.0
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'

2
.gitignore vendored
View file

@ -138,3 +138,5 @@ sdkconfig.*
.tests/
/components
/managed_components

View file

@ -46,6 +46,7 @@ esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/atm90e32/* @circuitsetup @descipher
esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter
@ -65,6 +66,8 @@ esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bme68x_bsec2/* @kbx81 @neffs
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @latonita
esphome/components/bmp3xx_base/* @latonita @martgras
@ -166,7 +169,10 @@ esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
@ -276,6 +282,7 @@ esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @guillempages
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931
@ -427,6 +434,7 @@ esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
@ -449,6 +457,7 @@ esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# If /cache is mounted, use that as PIO's coredir
# otherwise use path in /config (so that PIO packages aren't downloaded on each compile)

View file

@ -1,12 +1,12 @@
# PYTHON_ARGCOMPLETE_OK
import argparse
from datetime import datetime
import functools
import logging
import os
import re
import sys
import time
from datetime import datetime
import argcomplete
@ -38,15 +38,15 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address
from esphome.helpers import indent, is_ip_address, get_bool_env
from esphome.log import Fore, color, setup_log
from esphome.util import (
get_serial_ports,
list_yaml_files,
run_external_command,
run_external_process,
safe_print,
list_yaml_files,
get_serial_ports,
)
from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__)
@ -116,6 +116,7 @@ def get_port_type(port):
def run_miniterm(config, port):
import serial
from esphome import platformio_api
if CONF_LOGGER not in config:
@ -596,9 +597,10 @@ def command_update_all(args):
def command_idedata(args, config):
from esphome import platformio_api
import json
from esphome import platformio_api
logging.disable(logging.INFO)
logging.disable(logging.WARNING)
@ -729,7 +731,11 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument(
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
"-v",
"--verbose",
help="Enable verbose ESPHome logs.",
action="store_true",
default=get_bool_env("ESPHOME_VERBOSE"),
)
options_parser.add_argument(
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"
@ -747,7 +753,14 @@ def parse_args(argv):
)
parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser]
description=f"ESPHome {const.__version__}", parents=[options_parser]
)
parser.add_argument(
"--version",
action="version",
version=f"Version: {const.__version__}",
help="Print the ESPHome version and exit.",
)
mqtt_options = argparse.ArgumentParser(add_help=False)
@ -948,67 +961,6 @@ def parse_args(argv):
# a deprecation warning).
arguments = argv[1:]
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
# First, try new-style parsing, but don't exit in case of failure
try:
# duplicate parser so that we can use the original one to raise errors later on
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
current_parser.set_defaults(deprecated_argv_suggestion=None)
current_parser.error = _raise
return current_parser.parse_args(arguments)
except argparse.ArgumentError:
pass
# Second, try compat parsing and rearrange the command-line if it succeeds
# Disable argparse's built-in help option and add it manually to prevent this
# parser from printing the help messagefor the old format when invoked with -h.
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
compat_parser.add_argument("-h", "--help", action="store_true")
compat_parser.add_argument("configuration", nargs="*")
compat_parser.add_argument(
"command",
choices=[
"config",
"compile",
"upload",
"logs",
"run",
"clean-mqtt",
"wizard",
"mqtt-fingerprint",
"version",
"clean",
"dashboard",
"vscode",
"update-all",
],
)
try:
compat_parser.error = _raise
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
arguments = (
arguments[0:last_option]
+ [result.command]
+ result.configuration
+ unparsed
)
deprecated_argv_suggestion = arguments
except argparse.ArgumentError:
# old-style parsing failed, don't suggest any argument
deprecated_argv_suggestion = None
# Finally, run the new-style parser again with the possibly swapped arguments,
# and let it error out if the command is unparsable.
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
argcomplete.autocomplete(parser)
return parser.parse_args(arguments)
@ -1023,20 +975,6 @@ def run_esphome(argv):
# Show timestamp for dashboard access logs
args.command == "dashboard",
)
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
if sys.version_info < (3, 8, 0):
_LOGGER.error(
"You're running ESPHome with Python <3.8. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.8+"
)
return 1
if args.command in PRE_CONFIG_ACTIONS:
try:

View file

@ -7,10 +7,10 @@ from esphome.const import (
CONF_ELSE,
CONF_ID,
CONF_THEN,
CONF_TIME,
CONF_TIMEOUT,
CONF_TRIGGER_ID,
CONF_TYPE_ID,
CONF_TIME,
CONF_UPDATE_INTERVAL,
)
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor

View file

@ -8,55 +8,78 @@
# want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa
from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
Expression,
LineComment,
MockObj,
MockObjClass,
Pvariable,
RawExpression,
RawStatement,
TemplateArguments,
StructInitializer,
ArrayInitializer,
safe_exp,
Statement,
LineComment,
progmem_array,
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,
StructInitializer,
TemplateArguments,
add,
add_global,
add_library,
add_build_flag,
add_define,
add_global,
add_library,
add_platformio_option,
get_variable,
get_variable_with_full_id,
process_lambda,
is_template,
new_Pvariable,
new_variable,
process_lambda,
progmem_array,
safe_exp,
statement,
static_const_array,
templatable,
MockObj,
MockObjClass,
variable,
with_local_variable,
)
from esphome.cpp_helpers import ( # noqa
gpio_pin_expression,
register_component,
from esphome.cpp_helpers import ( # noqa: F401
build_registry_entry,
build_registry_list,
extract_registry_entry_config,
register_parented,
gpio_pin_expression,
past_safe_mode,
register_component,
register_parented,
)
from esphome.cpp_types import ( # noqa
global_ns,
void,
nullptr,
float_,
double,
from esphome.cpp_types import ( # noqa: F401
NAN,
App,
Application,
Component,
ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
GPIOPin,
InternalGPIOPin,
JsonObject,
JsonObjectConst,
Parented,
PollingComponent,
arduino_json_ns,
bool_,
const_char_ptr,
double,
esphome_ns,
float_,
global_ns,
gpio_Flags,
int16,
int32,
int64,
int_,
nullptr,
optional,
size_t,
std_ns,
std_shared_ptr,
std_string,
@ -66,28 +89,5 @@ from esphome.cpp_types import ( # noqa
uint16,
uint32,
uint64,
int16,
int32,
int64,
size_t,
const_char_ptr,
NAN,
esphome_ns,
App,
EntityBase,
Component,
ComponentPtr,
PollingComponent,
Application,
optional,
arduino_json_ns,
JsonObject,
JsonObjectConst,
Controller,
GPIOPin,
InternalGPIOPin,
gpio_Flags,
EntityCategory,
Parented,
ESPTime,
void,
)

View file

@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
this->write_byte16(reg);
this->transfer_byte(0x80);
uint8_t recv[2];
this->read_array(recv, 4);
this->read_array(recv, 2);
*value = encode_uint16(recv[0], recv[1]);
this->disable();
return false;

View file

@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
}
@ -43,6 +41,10 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
if (this->illuminance_sensor_ != nullptr) {
this->illuminance_sensor_->publish_state(value->ambientLight);
}
} else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
}
@ -68,6 +70,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
}
AirthingsWavePlus::AirthingsWavePlus() {

View file

@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
protected:
bool is_valid_radon_value_(uint16_t radon);
@ -32,6 +33,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
struct WavePlusReadings {
uint8_t version;

View file

@ -12,6 +12,9 @@ from esphome.const import (
CONF_CO2,
UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
CONF_ILLUMINANCE,
UNIT_LUX,
DEVICE_CLASS_ILLUMINANCE,
)
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -62,3 +71,6 @@ async def to_code(config):
if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens))
if config_illuminance := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config_illuminance)
cg.add(var.set_illuminance(sens))

View file

@ -1,16 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import web_server
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_CODE,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_CODE,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"]
@ -77,67 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
web_server.WEBSERVER_SORTING_SCHEMA
).extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
}
),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
}
),
cv.Optional(CONF_ON_CHIME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
}
),
cv.Optional(CONF_ON_READY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
}
),
}
ALARM_CONTROL_PANEL_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTAlarmControlPanelComponent
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
}
),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
}
),
cv.Optional(CONF_ON_CHIME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
}
),
cv.Optional(CONF_ON_READY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
}
),
}
)
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
@ -192,6 +198,9 @@ async def setup_alarm_control_panel_core_(var, config):
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_alarm_control_panel(var, config):

View file

@ -1,25 +1,27 @@
import base64
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_DATA,
CONF_DATA_TEMPLATE,
CONF_EVENT,
CONF_ID,
CONF_KEY,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_PASSWORD,
CONF_PORT,
CONF_REBOOT_TIMEOUT,
CONF_SERVICE,
CONF_VARIABLES,
CONF_SERVICES,
CONF_TRIGGER_ID,
CONF_EVENT,
CONF_TAG,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import coroutine_with_priority
@ -63,40 +65,51 @@ def validate_encryption_key(value):
return value
CONFIG_SCHEMA = cv.Schema(
ACTIONS_SCHEMA = automation.validate_automation(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SERVICES): automation.validate_automation(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Required(CONF_SERVICE): cv.valid_name,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{
cv.validate_id_name: cv.one_of(
*SERVICE_ARG_NATIVE_TYPES, lower=True
),
}
),
cv.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
}
),
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
single=True
),
}
).extend(cv.COMPONENT_SCHEMA)
},
cv.All(
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
cv.rename_key(CONF_SERVICE, CONF_ACTION),
),
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(APIServer),
cv.Optional(CONF_PORT, default=6053): cv.port,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict,
cv.Optional(
CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds,
cv.Exclusive(
CONF_SERVICES, group_of_exclusion=CONF_ACTIONS
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): cv.Schema(
{
cv.Required(CONF_KEY): validate_encryption_key,
}
),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
single=True
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.rename_key(CONF_SERVICES, CONF_ACTIONS),
)
@coroutine_with_priority(40.0)
@ -108,7 +121,7 @@ async def to_code(config):
cg.add(var.set_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
for conf in config.get(CONF_SERVICES, []):
for conf in config.get(CONF_ACTIONS, []):
template_args = []
func_args = []
service_arg_names = []
@ -119,7 +132,7 @@ async def to_code(config):
service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable(
conf[CONF_TRIGGER_ID], templ, conf[CONF_SERVICE], service_arg_names
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
)
cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf)
@ -142,7 +155,7 @@ async def to_code(config):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4")
cg.add_library("esphome/noise-c", "0.1.6")
else:
cg.add_define("USE_API_PLAINTEXT")
@ -152,28 +165,43 @@ async def to_code(config):
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(APIServer),
cv.Required(CONF_SERVICE): cv.templatable(cv.string),
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{cv.string: cv.returning_lambda}
),
}
HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.use_id(APIServer),
cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.string
),
cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.string
),
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA,
cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
{cv.string: cv.returning_lambda}
),
}
),
cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
cv.rename_key(CONF_SERVICE, CONF_ACTION),
)
@automation.register_action(
"homeassistant.action",
HomeAssistantServiceCallAction,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
@automation.register_action(
"homeassistant.service",
HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA,
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
)
async def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = await cg.templatable(config[CONF_SERVICE], args, None)
templ = await cg.templatable(config[CONF_ACTION], args, None)
cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items():
templ = await cg.templatable(value, args, None)

View file

@ -686,6 +686,7 @@ message SubscribeHomeAssistantStateResponse {
option (source) = SOURCE_SERVER;
string entity_id = 1;
string attribute = 2;
bool once = 3;
}
message HomeAssistantStateResponse {
@ -1872,6 +1873,11 @@ message UpdateStateResponse {
string release_summary = 9;
string release_url = 10;
}
enum UpdateCommand {
UPDATE_COMMAND_NONE = 0;
UPDATE_COMMAND_UPDATE = 1;
UPDATE_COMMAND_CHECK = 2;
}
message UpdateCommandRequest {
option (id) = 118;
option (source) = SOURCE_CLIENT;
@ -1879,5 +1885,5 @@ message UpdateCommandRequest {
option (no_delay) = true;
fixed32 key = 1;
bool install = 2;
UpdateCommand command = 2;
}

View file

@ -1328,7 +1328,20 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
if (update == nullptr)
return;
update->perform();
switch (msg.command) {
case enums::UPDATE_COMMAND_UPDATE:
update->perform();
break;
case enums::UPDATE_COMMAND_CHECK:
update->check();
break;
case enums::UPDATE_COMMAND_NONE:
ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled. Check client is sending the correct command");
break;
default:
ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
break;
}
}
#endif

View file

@ -567,6 +567,20 @@ template<> const char *proto_enum_to_string<enums::ValveOperation>(enums::ValveO
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateCommand value) {
switch (value) {
case enums::UPDATE_COMMAND_NONE:
return "UPDATE_COMMAND_NONE";
case enums::UPDATE_COMMAND_UPDATE:
return "UPDATE_COMMAND_UPDATE";
case enums::UPDATE_COMMAND_CHECK:
return "UPDATE_COMMAND_CHECK";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@ -3095,6 +3109,16 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
}
#endif
bool SubscribeHomeAssistantStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->once = value.as_bool();
return true;
}
default:
return false;
}
}
bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@ -3112,6 +3136,7 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->attribute);
buffer.encode_bool(3, this->once);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
@ -3124,6 +3149,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
out.append(" attribute: ");
out.append("'").append(this->attribute).append("'");
out.append("\n");
out.append(" once: ");
out.append(YESNO(this->once));
out.append("\n");
out.append("}");
}
#endif
@ -8596,7 +8625,7 @@ void UpdateStateResponse::dump_to(std::string &out) const {
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->install = value.as_bool();
this->command = value.as_enum<enums::UpdateCommand>();
return true;
}
default:
@ -8615,7 +8644,7 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->install);
buffer.encode_enum<enums::UpdateCommand>(2, this->command);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void UpdateCommandRequest::dump_to(std::string &out) const {
@ -8626,8 +8655,8 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
out.append(buffer);
out.append("\n");
out.append(" install: ");
out.append(YESNO(this->install));
out.append(" command: ");
out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
out.append("\n");
out.append("}");
}

View file

@ -227,6 +227,11 @@ enum ValveOperation : uint32_t {
VALVE_OPERATION_IS_OPENING = 1,
VALVE_OPERATION_IS_CLOSING = 2,
};
enum UpdateCommand : uint32_t {
UPDATE_COMMAND_NONE = 0,
UPDATE_COMMAND_UPDATE = 1,
UPDATE_COMMAND_CHECK = 2,
};
} // namespace enums
@ -831,6 +836,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{};
std::string attribute{};
bool once{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -838,6 +844,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class HomeAssistantStateResponse : public ProtoMessage {
public:
@ -2175,7 +2182,7 @@ class UpdateStateResponse : public ProtoMessage {
class UpdateCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
bool install{false};
enums::UpdateCommand command{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;

View file

@ -359,8 +359,18 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}

View file

@ -112,10 +112,13 @@ class APIServer : public Component, public Controller {
std::string entity_id;
optional<std::string> attribute;
std::function<void(std::string)> callback;
bool once;
};
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }

View file

@ -0,0 +1,7 @@
import esphome.codegen as cg
CODEOWNERS = ["@circuitsetup", "@descipher"]
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
CONF_ATM90E32_ID = "atm90e32_id"

View file

@ -132,10 +132,77 @@ void ATM90E32Component::update() {
this->status_clear_warning();
}
void ATM90E32Component::restore_calibrations_() {
if (enable_offset_calibration_) {
this->pref_.load(&this->offset_phase_);
}
};
void ATM90E32Component::run_offset_calibrations() {
// Run the calibrations and
// Setup voltage and current calibration offsets for PHASE A
this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE B
this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE C
this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::clear_offset_calibrations() {
// Clear the calibrations and
this->offset_phase_[PHASEA].voltage_offset_ = 0;
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = 0;
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
this->offset_phase_[PHASEB].voltage_offset_ = 0;
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = 0;
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
this->offset_phase_[PHASEC].voltage_offset_ = 0;
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = 0;
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup();
if (this->enable_offset_calibration_) {
uint32_t hash = fnv1_hash(App.get_friendly_name());
this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
this->restore_calibrations_();
}
uint16_t mmode0 = 0x87; // 3P4W 50Hz
if (line_freq_ == 60) {
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
@ -167,27 +234,12 @@ void ATM90E32Component::setup() {
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
// Setup voltage and current calibration offsets for PHASE A
this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset
this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset
// Setup voltage and current gain for PHASE A
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
// Setup voltage and current calibration offsets for PHASE B
this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset
this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset
// Setup voltage and current gain for PHASE B
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
// Setup voltage and current calibration offsets for PHASE C
this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
// Setup voltage and current gain for PHASE C
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain

View file

@ -1,9 +1,12 @@
#pragma once
#include "esphome/core/component.h"
#include "atm90e32_reg.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
#include "atm90e32_reg.h"
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace atm90e32 {
@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent,
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
@ -48,9 +50,11 @@ class ATM90E32Component : public PollingComponent,
void set_line_freq(int freq) { line_freq_ = freq; }
void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
void run_offset_calibrations();
void clear_offset_calibrations();
void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
int32_t last_periodic_millis = millis();
protected:
@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent,
float get_chip_temperature_();
bool get_publish_interval_flag_() { return publish_interval_flag_; };
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
void restore_calibrations_();
struct ATM90E32Phase {
uint16_t voltage_gain_{7305};
uint16_t ct_gain_{27961};
uint16_t voltage_gain_{0};
uint16_t ct_gain_{0};
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
float voltage_{0};
@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent,
uint32_t cumulative_reverse_active_energy_{0};
} phase_[3];
struct Calibration {
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
} offset_phase_[3];
ESPPreferenceObject pref_;
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15};
int line_freq_{60};
int current_phases_{3};
bool publish_interval_flag_{true};
bool publish_interval_flag_{false};
bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
};
} // namespace atm90e32

View file

@ -1,5 +1,7 @@
#pragma once
#include <cinttypes>
namespace esphome {
namespace atm90e32 {

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
ATM90E32CalibrationButton = atm90e32_ns.class_(
"ATM90E32CalibrationButton",
button.Button,
)
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearCalibrationButton",
button.Button,
)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
ATM90E32CalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_CHIP,
),
}
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
b = await button.new_button(run_offset)
await cg.register_parented(b, parent)
if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
b = await button.new_button(clear_offset)
await cg.register_parented(b, parent)

View file

@ -0,0 +1,20 @@
#include "atm90e32_button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e32 {
static const char *const TAG = "atm90e32.button";
void ATM90E32CalibrationButton::press_action() {
ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
this->parent_->run_offset_calibrations();
}
void ATM90E32ClearCalibrationButton::press_action() {
ESP_LOGI(TAG, "Offset calibrations cleared.");
this->parent_->clear_offset_calibrations();
}
} // namespace atm90e32
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/atm90e32/atm90e32.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace atm90e32 {
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32CalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearCalibrationButton() = default;
protected:
void press_action() override;
};
} // namespace atm90e32
} // namespace esphome

View file

@ -1,21 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_APPARENT_POWER,
CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_PHASE_A,
CONF_PHASE_ANGLE,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_PHASE_ANGLE,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_APPARENT_POWER,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
@ -23,13 +23,13 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_DEGREES,
UNIT_CELSIUS,
UNIT_DEGREES,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_VOLT_AMPS_REACTIVE,
@ -37,6 +37,8 @@ from esphome.const import (
UNIT_WATT_HOURS,
)
from . import atm90e32_ns
CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga"
@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct"
CONF_HARMONIC_POWER = "harmonic_power"
CONF_PEAK_CURRENT = "peak_current"
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
UNIT_DEG = "degrees"
LINE_FREQS = {
"50HZ": 50,
@ -61,7 +64,6 @@ PGA_GAINS = {
"4X": 0x2A,
}
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
ATM90E32Component = atm90e32_ns.class_(
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
)
@ -164,6 +166,7 @@ CONFIG_SCHEMA = (
),
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
}
)
.extend(cv.polling_component_schema("60s"))
@ -227,3 +230,4 @@ async def to_code(config):
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))

View file

@ -90,7 +90,7 @@ struct BedjetStatusPacket {
int unused_6 : 1; // 0x4
bool is_dual_zone : 1; /// Is part of a Dual Zone configuration
int unused_7 : 1; // 0x1
} dual_zone_flags;
} dual_zone_flags; // NOLINT(clang-diagnostic-unaligned-access)
uint8_t unused_4 : 8; // Unknown 23-24 = 0x1310
uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310

View file

@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput {
void write_state(light::LightState *state) override {
bool binary;
state->current_values_as_binary(&binary);
if (binary)
if (binary) {
this->output_->turn_on();
else
} else {
this->output_->turn_off();
}
}
protected:

View file

@ -1,10 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DELAY,
CONF_DEVICE_CLASS,
@ -16,6 +14,7 @@ from esphome.const import (
CONF_INVERTED,
CONF_MAX_LENGTH,
CONF_MIN_LENGTH,
CONF_MQTT_ID,
CONF_ON_CLICK,
CONF_ON_DOUBLE_CLICK,
CONF_ON_MULTI_CLICK,
@ -26,7 +25,6 @@ from esphome.const import (
CONF_STATE,
CONF_TIMING,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
@ -59,6 +57,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome.util import Registry
CODEOWNERS = ["@esphome/core"]

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import esp32_ble_tracker, esp32_ble_client
import esphome.codegen as cg
from esphome.components import esp32_ble_client, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
CONF_ID,
@ -13,7 +14,6 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VALUE,
)
from esphome import automation
AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"]

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ble_client, esp32_ble_tracker, output
import esphome.config_validation as cv
from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns

View file

@ -1,17 +1,18 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, sensor
import esphome.config_validation as cv
from esphome.components import sensor, ble_client, esp32_ble_tracker
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
CONF_LAMBDA,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT,
)
from esphome import automation
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg
from esphome.components import ble_client, switch
import esphome.config_validation as cv
from esphome.components import switch, ble_client
from esphome.const import ICON_BLUETOOTH
from .. import ble_client_ns
BLEClientSwitch = ble_client_ns.class_(

View file

@ -1,13 +1,14 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, text_sensor
import esphome.config_validation as cv
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
CONF_ID,
CONF_TRIGGER_ID,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
)
from esphome import automation
from .. import ble_client_ns
DEPENDENCIES = ["ble_client"]

View file

@ -1,13 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import (
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR,
CONF_IBEACON_UUID,
CONF_MAC_ADDRESS,
CONF_MIN_RSSI,
CONF_SERVICE_UUID,
CONF_TIMEOUT,
)

View file

@ -1,12 +1,12 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import (
CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR,
CONF_IBEACON_UUID,
CONF_SERVICE_UUID,
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT,

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, text_sensor
import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
DEPENDENCIES = ["esp32_ble_tracker"]

View file

@ -1,8 +1,8 @@
from esphome.components import esp32_ble_tracker, esp32_ble_client
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ACTIVE, CONF_ID
from esphome.components import esp32_ble_client, esp32_ble_tracker
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_ID
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"]

View file

@ -0,0 +1,196 @@
import hashlib
from pathlib import Path
from esphome import core, external_files
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODEL,
CONF_RAW_DATA_ID,
CONF_SAMPLE_RATE,
CONF_TEMPERATURE_OFFSET,
)
CODEOWNERS = ["@neffs", "@kbx81"]
DOMAIN = "bme68x_bsec2"
BSEC2_LIBRARY_VERSION = "v1.7.2502"
CONF_ALGORITHM_OUTPUT = "algorithm_output"
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
CONF_IAQ_MODE = "iaq_mode"
CONF_OPERATING_AGE = "operating_age"
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
CONF_SUPPLY_VOLTAGE = "supply_voltage"
bme68x_bsec2_ns = cg.esphome_ns.namespace("bme68x_bsec2")
BME68xBSEC2Component = bme68x_bsec2_ns.class_("BME68xBSEC2Component", cg.Component)
MODEL_OPTIONS = ["bme680", "bme688"]
AlgorithmOutput = bme68x_bsec2_ns.enum("AlgorithmOutput")
ALGORITHM_OUTPUT_OPTIONS = {
"classification": AlgorithmOutput.ALGORITHM_OUTPUT_CLASSIFICATION,
"regression": AlgorithmOutput.ALGORITHM_OUTPUT_REGRESSION,
}
OperatingAge = bme68x_bsec2_ns.enum("OperatingAge")
OPERATING_AGE_OPTIONS = {
"4d": OperatingAge.OPERATING_AGE_4D,
"28d": OperatingAge.OPERATING_AGE_28D,
}
SampleRate = bme68x_bsec2_ns.enum("SampleRate")
SAMPLE_RATE_OPTIONS = {
"LP": SampleRate.SAMPLE_RATE_LP,
"ULP": SampleRate.SAMPLE_RATE_ULP,
}
Voltage = bme68x_bsec2_ns.enum("Voltage")
VOLTAGE_OPTIONS = {
"1.8V": Voltage.VOLTAGE_1_8V,
"3.3V": Voltage.VOLTAGE_3_3V,
}
ALGORITHM_OUTPUT_FILE_NAME = {
"classification": "sel",
"regression": "reg",
}
SAMPLE_RATE_FILE_NAME = {
"LP": "3s",
"ULP": "300s",
}
VOLTAGE_FILE_NAME = {
"1.8V": "18v",
"3.3V": "33v",
}
def _compute_local_file_path(url: str) -> Path:
h = hashlib.new("sha256")
h.update(url.encode())
key = h.hexdigest()[:8]
base_dir = external_files.compute_local_file_dir(DOMAIN)
return base_dir / key
def _compute_url(config: dict) -> str:
model = config.get(CONF_MODEL)
operating_age = config.get(CONF_OPERATING_AGE)
sample_rate = SAMPLE_RATE_FILE_NAME[config.get(CONF_SAMPLE_RATE)]
volts = VOLTAGE_FILE_NAME[config.get(CONF_SUPPLY_VOLTAGE)]
if model == "bme688":
algo = ALGORITHM_OUTPUT_FILE_NAME[
config.get(CONF_ALGORITHM_OUTPUT, "classification")
]
filename = "bsec_selectivity"
else:
algo = "iaq"
filename = "bsec_iaq"
return f"https://raw.githubusercontent.com/boschsensortec/Bosch-BSEC2-Library/{BSEC2_LIBRARY_VERSION}/src/config/{model}/{model}_{algo}_{volts}_{sample_rate}_{operating_age}/{filename}.txt"
def download_bme68x_blob(config):
url = _compute_url(config)
path = _compute_local_file_path(url)
external_files.download_content(url, path)
return config
def validate_bme68x(config):
if CONF_ALGORITHM_OUTPUT not in config:
return config
if config[CONF_MODEL] != "bme688":
raise cv.Invalid(f"{CONF_ALGORITHM_OUTPUT} is only valid for BME688")
if config[CONF_ALGORITHM_OUTPUT] == "regression" and (
config[CONF_OPERATING_AGE] != "4d"
or config[CONF_SAMPLE_RATE] != "ULP"
or config[CONF_SUPPLY_VOLTAGE] != "1.8V"
):
raise cv.Invalid(
f" To use '{CONF_ALGORITHM_OUTPUT}: regression', {CONF_OPERATING_AGE} must be '4d', {CONF_SAMPLE_RATE} must be 'ULP' and {CONF_SUPPLY_VOLTAGE} must be '1.8V'"
)
return config
CONFIG_SCHEMA_BASE = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BME68xBSEC2Component),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
cv.Required(CONF_MODEL): cv.one_of(*MODEL_OPTIONS, lower=True),
cv.Optional(CONF_ALGORITHM_OUTPUT): cv.enum(
ALGORITHM_OUTPUT_OPTIONS, lower=True
),
cv.Optional(CONF_OPERATING_AGE, default="28d"): cv.enum(
OPERATING_AGE_OPTIONS, lower=True
),
cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum(
SAMPLE_RATE_OPTIONS, upper=True
),
cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum(
VOLTAGE_OPTIONS, upper=True
),
cv.Optional(CONF_TEMPERATURE_OFFSET, default=0): cv.temperature,
cv.Optional(
CONF_STATE_SAVE_INTERVAL, default="6hours"
): cv.positive_time_period_minutes,
},
)
.add_extra(cv.only_with_arduino)
.add_extra(validate_bme68x)
.add_extra(download_bme68x_blob)
)
async def to_code_base(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if algo_output := config.get(CONF_ALGORITHM_OUTPUT):
cg.add(var.set_algorithm_output(algo_output))
cg.add(var.set_operating_age(config[CONF_OPERATING_AGE]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_voltage(config[CONF_SUPPLY_VOLTAGE]))
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
cg.add(
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
path = _compute_local_file_path(_compute_url(config))
try:
with open(path, encoding="utf-8") as f:
bsec2_iaq_config = f.read()
except Exception as e:
raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}")
# Convert retrieved BSEC2 config to an array of ints
rhs = [int(x) for x in bsec2_iaq_config.split(",")]
# Create an array which will reside in program memory and configure the sensor instance to use it
bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs)))
# Although this component does not use SPI, the BSEC2 library requires the SPI library
cg.add_library("SPI", None)
cg.add_library(
"BME68x Sensor library",
"1.1.40407",
)
cg.add_library(
"BSEC2 Software Library",
None,
f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}",
)
cg.add_define("USE_BSEC2")
return var

View file

@ -0,0 +1,523 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_BSEC2
#include "bme68x_bsec2.h"
#include <string>
namespace esphome {
namespace bme68x_bsec2 {
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
#define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP"))
#define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V")
static const char *const TAG = "bme68x_bsec2.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
void BME68xBSEC2Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BME68X via BSEC2...");
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_init_m failed: status %d", this->bsec_status_);
return;
}
bsec_get_version_m(&this->bsec_instance_, &this->version_);
this->bme68x_status_ = bme68x_init(&this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bme68x_init failed: status %d", this->bme68x_status_);
return;
}
if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
this->set_config_(this->bsec2_configuration_, this->bsec2_configuration_length_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_set_configuration_m failed: status %d", this->bsec_status_);
return;
}
}
this->update_subscription_();
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
ESP_LOGE(TAG, "bsec_update_subscription_m failed: status %d", this->bsec_status_);
return;
}
this->load_state_();
}
void BME68xBSEC2Component::dump_config() {
ESP_LOGCONFIG(TAG, "BME68X via BSEC2:");
ESP_LOGCONFIG(TAG, " BSEC2 version: %d.%d.%d.%d", this->version_.major, this->version_.minor,
this->version_.major_bugfix, this->version_.minor_bugfix);
ESP_LOGCONFIG(TAG, " BSEC2 configuration blob:");
ESP_LOGCONFIG(TAG, " Configured: %s", YESNO(this->bsec2_blob_configured_));
if (this->bsec2_configuration_ != nullptr && this->bsec2_configuration_length_) {
ESP_LOGCONFIG(TAG, " Size: %" PRIu32, this->bsec2_configuration_length_);
}
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication failed (BSEC2 status: %d, BME68X status: %d)", this->bsec_status_,
this->bme68x_status_);
}
if (this->algorithm_output_ != ALGORITHM_OUTPUT_IAQ) {
ESP_LOGCONFIG(TAG, " Algorithm output: %s", BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(this->algorithm_output_));
}
ESP_LOGCONFIG(TAG, " Operating age: %s", BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_));
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_));
ESP_LOGCONFIG(TAG, " Voltage: %s", BME68X_BSEC2_VOLTAGE_LOG(this->voltage_));
ESP_LOGCONFIG(TAG, " State save interval: %ims", this->state_save_interval_ms_);
ESP_LOGCONFIG(TAG, " Temperature offset: %.2f", this->temperature_offset_);
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->temperature_sample_rate_));
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->pressure_sample_rate_));
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
ESP_LOGCONFIG(TAG, " Sample rate: %s", BME68X_BSEC2_SAMPLE_RATE_LOG(this->humidity_sample_rate_));
LOG_SENSOR(" ", "Gas resistance", this->gas_resistance_sensor_);
LOG_SENSOR(" ", "CO2 equivalent", this->co2_equivalent_sensor_);
LOG_SENSOR(" ", "Breath VOC equivalent", this->breath_voc_equivalent_sensor_);
LOG_SENSOR(" ", "IAQ", this->iaq_sensor_);
LOG_SENSOR(" ", "IAQ static", this->iaq_static_sensor_);
LOG_SENSOR(" ", "Numeric IAQ accuracy", this->iaq_accuracy_sensor_);
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "IAQ accuracy", this->iaq_accuracy_text_sensor_);
#endif
}
float BME68xBSEC2Component::get_setup_priority() const { return setup_priority::DATA; }
void BME68xBSEC2Component::loop() {
this->run_();
if (this->bsec_status_ < BSEC_OK || this->bme68x_status_ < BME68X_OK) {
this->status_set_error();
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
this->status_set_warning();
} else {
this->status_clear_warning();
}
// Process a single action from the queue. These are primarily sensor state publishes
// that in totality take too long to send in a single call.
if (this->queue_.size()) {
auto action = std::move(this->queue_.front());
this->queue_.pop();
action();
}
}
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
this->mark_failed();
return;
}
uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE];
this->bsec_status_ = bsec_set_configuration_m(&this->bsec_instance_, config, len, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ == BSEC_OK) {
this->bsec2_blob_configured_ = true;
}
}
float BME68xBSEC2Component::calc_sensor_sample_rate_(SampleRate sample_rate) {
if (sample_rate == SAMPLE_RATE_DEFAULT) {
sample_rate = this->sample_rate_;
}
return sample_rate == SAMPLE_RATE_ULP ? BSEC_SAMPLE_RATE_ULP : BSEC_SAMPLE_RATE_LP;
}
void BME68xBSEC2Component::update_subscription_() {
bsec_sensor_configuration_t virtual_sensors[BSEC_NUMBER_OUTPUTS];
uint8_t num_virtual_sensors = 0;
#ifdef USE_SENSOR
if (this->iaq_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_IAQ;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->iaq_static_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_STATIC_IAQ;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->co2_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->breath_voc_equivalent_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_BREATH_VOC_EQUIVALENT;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->pressure_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_PRESSURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->pressure_sample_rate_);
num_virtual_sensors++;
}
if (this->gas_resistance_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_RAW_GAS;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(SAMPLE_RATE_DEFAULT);
num_virtual_sensors++;
}
if (this->temperature_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->temperature_sample_rate_);
num_virtual_sensors++;
}
if (this->humidity_sensor_) {
virtual_sensors[num_virtual_sensors].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY;
virtual_sensors[num_virtual_sensors].sample_rate = this->calc_sensor_sample_rate_(this->humidity_sample_rate_);
num_virtual_sensors++;
}
#endif
bsec_sensor_configuration_t sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
this->bsec_status_ = bsec_update_subscription_m(&this->bsec_instance_, virtual_sensors, num_virtual_sensors,
sensor_settings, &num_sensor_settings);
}
void BME68xBSEC2Component::run_() {
int64_t curr_time_ns = this->get_time_ns_();
if (curr_time_ns < this->next_call_ns_) {
return;
}
this->op_mode_ = this->bsec_settings_.op_mode;
uint8_t status;
ESP_LOGV(TAG, "Performing sensor run");
struct bme68x_conf bme68x_conf;
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
return;
}
this->next_call_ns_ = this->bsec_settings_.next_call;
if (this->bsec_settings_.trigger_measurement) {
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
bme68x_conf.os_hum = this->bsec_settings_.humidity_oversampling;
bme68x_conf.os_temp = this->bsec_settings_.temperature_oversampling;
bme68x_conf.os_pres = this->bsec_settings_.pressure_oversampling;
bme68x_set_conf(&bme68x_conf, &this->bme68x_);
switch (this->bsec_settings_.op_mode) {
case BME68X_FORCED_MODE:
this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using forced mode");
break;
case BME68X_PARALLEL_MODE:
if (this->op_mode_ != this->bsec_settings_.op_mode) {
this->bme68x_heatr_conf_.enable = BME68X_ENABLE;
this->bme68x_heatr_conf_.heatr_temp_prof = this->bsec_settings_.heater_temperature_profile;
this->bme68x_heatr_conf_.heatr_dur_prof = this->bsec_settings_.heater_duration_profile;
this->bme68x_heatr_conf_.profile_len = this->bsec_settings_.heater_profile_len;
this->bme68x_heatr_conf_.shared_heatr_dur =
BSEC_TOTAL_HEAT_DUR -
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->op_mode_ = BME68X_PARALLEL_MODE;
this->sleep_mode_ = false;
ESP_LOGV(TAG, "Using parallel mode");
}
break;
case BME68X_SLEEP_MODE:
if (!this->sleep_mode_) {
bme68x_set_op_mode(BME68X_SLEEP_MODE, &this->bme68x_);
this->sleep_mode_ = true;
ESP_LOGV(TAG, "Using sleep mode");
}
break;
}
uint32_t meas_dur = 0;
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
this->set_timeout("read", meas_dur / 1000, [this, curr_time_ns]() { this->read_(curr_time_ns); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns);
}
}
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
ESP_LOGV(TAG, "Reading data");
if (this->bsec_settings_.trigger_measurement) {
uint8_t current_op_mode;
this->bme68x_status_ = bme68x_get_op_mode(&current_op_mode, &this->bme68x_);
if (current_op_mode == BME68X_SLEEP_MODE) {
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
return;
}
}
if (!this->bsec_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
}
struct bme68x_data data[3];
uint8_t nFields = 0;
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
return;
}
if (nFields < 1) {
ESP_LOGD(TAG, "BME68X did not provide new data");
return;
}
for (uint8_t i = 0; i < nFields; i++) {
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temperature, Pressure, Humidity & Gas Resistance
uint8_t num_inputs = 0;
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_TEMPERATURE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE;
inputs[num_inputs].signal = data[i].temperature;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HEATSOURCE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE;
inputs[num_inputs].signal = this->temperature_offset_;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_HUMIDITY)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY;
inputs[num_inputs].signal = data[i].humidity;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PRESSURE)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE;
inputs[num_inputs].signal = data[i].pressure;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_GASRESISTOR)) {
if (data[i].status & BME68X_GASM_VALID_MSK) {
inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR;
inputs[num_inputs].signal = data[i].gas_resistance;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
} else {
ESP_LOGD(TAG, "BME68X did not report gas data");
}
}
if (BSEC_CHECK_INPUT(this->bsec_settings_.process_data, BSEC_INPUT_PROFILE_PART) &&
(data[i].status & BME68X_GASM_VALID_MSK)) {
inputs[num_inputs].sensor_id = BSEC_INPUT_PROFILE_PART;
inputs[num_inputs].signal = (this->op_mode_ == BME68X_FORCED_MODE) ? 0 : data[i].gas_index;
inputs[num_inputs].time_stamp = trigger_time_ns;
num_inputs++;
}
if (num_inputs < 1) {
ESP_LOGD(TAG, "No signal inputs available for BSEC2");
return;
}
bsec_output_t outputs[BSEC_NUMBER_OUTPUTS];
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (num_outputs < 1) {
ESP_LOGD(TAG, "No signal outputs provided by BSEC2");
return;
}
this->publish_(outputs, num_outputs);
}
}
void BME68xBSEC2Component::publish_(const bsec_output_t *outputs, uint8_t num_outputs) {
ESP_LOGV(TAG, "Publishing sensor states");
bool update_accuracy = false;
uint8_t max_accuracy = 0;
for (uint8_t i = 0; i < num_outputs; i++) {
float signal = outputs[i].signal;
switch (outputs[i].sensor_id) {
case BSEC_OUTPUT_IAQ:
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
update_accuracy = true;
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_STATIC_IAQ:
max_accuracy = std::max(outputs[i].accuracy, max_accuracy);
update_accuracy = true;
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_static_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_CO2_EQUIVALENT:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->co2_equivalent_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->breath_voc_equivalent_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_RAW_PRESSURE:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->pressure_sensor_, signal / 100.0f); });
#endif
break;
case BSEC_OUTPUT_RAW_GAS:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->gas_resistance_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->temperature_sensor_, signal); });
#endif
break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
#ifdef USE_SENSOR
this->queue_push_([this, signal]() { this->publish_sensor_(this->humidity_sensor_, signal); });
#endif
break;
}
}
if (update_accuracy) {
#ifdef USE_SENSOR
this->queue_push_(
[this, max_accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, max_accuracy, true); });
#endif
#ifdef USE_TEXT_SENSOR
this->queue_push_([this, max_accuracy]() {
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[max_accuracy]);
});
#endif
// Queue up an opportunity to save state
this->queue_push_([this, max_accuracy]() { this->save_state_(max_accuracy); });
}
}
int64_t BME68xBSEC2Component::get_time_ns_() {
int64_t time_ms = millis();
if (this->last_time_ms_ > time_ms) {
this->millis_overflow_counter_++;
}
this->last_time_ms_ = time_ms;
return (time_ms + ((int64_t) this->millis_overflow_counter_ << 32)) * INT64_C(1000000);
}
#ifdef USE_SENSOR
void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value, bool change_only) {
if (!sensor || (change_only && sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
#endif
#ifdef USE_TEXT_SENSOR
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
sensor->publish_state(value);
}
#endif
void BME68xBSEC2Component::load_state_() {
uint32_t hash = this->get_hash();
this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true);
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
if (this->bsec_state_.load(&state)) {
ESP_LOGV(TAG, "Loading state");
uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE];
this->bsec_status_ =
bsec_set_state_m(&this->bsec_instance_, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer));
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed to load state (BSEC2 error code %d)", this->bsec_status_);
}
ESP_LOGI(TAG, "Loaded state");
}
}
void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) {
return;
}
ESP_LOGV(TAG, "Saving state");
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE];
uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
this->bsec_status_ = bsec_get_state_m(&this->bsec_instance_, 0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer,
BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "Failed fetch state for save (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (!this->bsec_state_.save(&state)) {
ESP_LOGW(TAG, "Failed to save state");
return;
}
this->last_state_save_ms_ = millis();
ESP_LOGI(TAG, "Saved state");
}
} // namespace bme68x_bsec2
} // namespace esphome
#endif

View file

@ -0,0 +1,163 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#ifdef USE_BSEC2
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include <cinttypes>
#include <queue>
#include <bsec2.h>
namespace esphome {
namespace bme68x_bsec2 {
enum AlgorithmOutput {
ALGORITHM_OUTPUT_IAQ,
ALGORITHM_OUTPUT_CLASSIFICATION,
ALGORITHM_OUTPUT_REGRESSION,
};
enum OperatingAge {
OPERATING_AGE_4D,
OPERATING_AGE_28D,
};
enum SampleRate {
SAMPLE_RATE_LP = 0,
SAMPLE_RATE_ULP = 1,
SAMPLE_RATE_DEFAULT = 2,
};
enum Voltage {
VOLTAGE_1_8V,
VOLTAGE_3_3V,
};
class BME68xBSEC2Component : public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void loop() override;
void set_algorithm_output(AlgorithmOutput algorithm_output) { this->algorithm_output_ = algorithm_output; }
void set_operating_age(OperatingAge operating_age) { this->operating_age_ = operating_age; }
void set_temperature_offset(float offset) { this->temperature_offset_ = offset; }
void set_voltage(Voltage voltage) { this->voltage_ = voltage; }
void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; }
void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; }
void set_pressure_sample_rate(SampleRate sample_rate) { this->pressure_sample_rate_ = sample_rate; }
void set_humidity_sample_rate(SampleRate sample_rate) { this->humidity_sample_rate_ = sample_rate; }
void set_bsec2_configuration(const uint8_t *data, const uint32_t len) {
this->bsec2_configuration_ = data;
this->bsec2_configuration_length_ = len;
}
void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; }
#ifdef USE_SENSOR
void set_temperature_sensor(sensor::Sensor *sensor) { this->temperature_sensor_ = sensor; }
void set_pressure_sensor(sensor::Sensor *sensor) { this->pressure_sensor_ = sensor; }
void set_humidity_sensor(sensor::Sensor *sensor) { this->humidity_sensor_ = sensor; }
void set_gas_resistance_sensor(sensor::Sensor *sensor) { this->gas_resistance_sensor_ = sensor; }
void set_iaq_sensor(sensor::Sensor *sensor) { this->iaq_sensor_ = sensor; }
void set_iaq_static_sensor(sensor::Sensor *sensor) { this->iaq_static_sensor_ = sensor; }
void set_iaq_accuracy_sensor(sensor::Sensor *sensor) { this->iaq_accuracy_sensor_ = sensor; }
void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; }
void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; }
#endif
#ifdef USE_TEXT_SENSOR
void set_iaq_accuracy_text_sensor(text_sensor::TextSensor *sensor) { this->iaq_accuracy_text_sensor_ = sensor; }
#endif
virtual uint32_t get_hash() = 0;
protected:
void set_config_(const uint8_t *config, u_int32_t len);
float calc_sensor_sample_rate_(SampleRate sample_rate);
void update_subscription_();
void run_();
void read_(int64_t trigger_time_ns);
void publish_(const bsec_output_t *outputs, uint8_t num_outputs);
int64_t get_time_ns_();
#ifdef USE_SENSOR
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
#endif
#ifdef USE_TEXT_SENSOR
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
#endif
void load_state_();
void save_state_(uint8_t accuracy);
void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); }
struct bme68x_dev bme68x_;
bsec_bme_settings_t bsec_settings_;
bsec_version_t version_;
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
struct bme68x_heatr_conf bme68x_heatr_conf_;
uint8_t op_mode_; // operating mode of sensor
bool sleep_mode_;
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0};
uint32_t millis_overflow_counter_{0};
int64_t next_call_ns_{0};
std::queue<std::function<void()>> queue_;
uint8_t const *bsec2_configuration_{nullptr};
uint32_t bsec2_configuration_length_{0};
bool bsec2_blob_configured_{false};
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
#ifdef USE_SENSOR
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
sensor::Sensor *gas_resistance_sensor_{nullptr};
sensor::Sensor *iaq_sensor_{nullptr};
sensor::Sensor *iaq_static_sensor_{nullptr};
sensor::Sensor *iaq_accuracy_sensor_{nullptr};
sensor::Sensor *co2_equivalent_sensor_{nullptr};
sensor::Sensor *breath_voc_equivalent_sensor_{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
#endif
};
} // namespace bme68x_bsec2
} // namespace esphome
#endif

View file

@ -0,0 +1,130 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_GAS_RESISTANCE,
CONF_HUMIDITY,
CONF_IAQ_ACCURACY,
CONF_PRESSURE,
CONF_SAMPLE_RATE,
CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
ICON_GAS_CYLINDER,
ICON_GAUGE,
ICON_THERMOMETER,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
UNIT_OHM,
UNIT_PARTS_PER_MILLION,
UNIT_PERCENT,
)
from . import CONF_BME68X_BSEC2_ID, SAMPLE_RATE_OPTIONS, BME68xBSEC2Component
DEPENDENCIES = ["bme68x_bsec2"]
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_IAQ = "iaq"
CONF_IAQ_STATIC = "iaq_static"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
ICON_TEST_TUBE = "mdi:test-tube"
UNIT_IAQ = "IAQ"
TYPES = [
CONF_TEMPERATURE,
CONF_PRESSURE,
CONF_HUMIDITY,
CONF_GAS_RESISTANCE,
CONF_IAQ,
CONF_IAQ_STATIC,
CONF_IAQ_ACCURACY,
CONF_CO2_EQUIVALENT,
CONF_BREATH_VOC_EQUIVALENT,
]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
icon=ICON_THERMOMETER,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
icon=ICON_GAUGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)}
),
cv.Optional(CONF_GAS_RESISTANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_OHM,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ): sensor.sensor_schema(
unit_of_measurement=UNIT_IAQ,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ_STATIC): sensor.sensor_schema(
unit_of_measurement=UNIT_IAQ,
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_IAQ_ACCURACY): sensor.sensor_schema(
icon=ICON_ACCURACY,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_TEST_TUBE,
accuracy_decimals=1,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
async def setup_conf(config, key, hub):
if conf := config.get(key):
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if sample_rate := conf.get(CONF_SAMPLE_RATE):
cg.add(getattr(hub, f"set_{key}_sample_rate")(sample_rate))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_IAQ_ACCURACY
from . import CONF_BME68X_BSEC2_ID, BME68xBSEC2Component
DEPENDENCIES = ["bme68x_bsec2"]
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
TYPES = [CONF_IAQ_ACCURACY]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
icon=ICON_ACCURACY
),
}
)
async def setup_conf(config, key, hub):
if conf := config.get(key):
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
async def to_code(config):
hub = await cg.get_variable(config[CONF_BME68X_BSEC2_ID])
for key in TYPES:
await setup_conf(config, key, hub)

View file

@ -0,0 +1,28 @@
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.bme68x_bsec2 import (
CONFIG_SCHEMA_BASE,
BME68xBSEC2Component,
to_code_base,
)
import esphome.config_validation as cv
CODEOWNERS = ["@neffs", "@kbx81"]
AUTO_LOAD = ["bme68x_bsec2"]
DEPENDENCIES = ["i2c"]
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(
"BME68xBSEC2I2CComponent", BME68xBSEC2Component, i2c.I2CDevice
)
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
cv.Schema({cv.GenerateID(): cv.declare_id(BME68xBSEC2I2CComponent)})
).extend(i2c.i2c_device_schema(0x76))
async def to_code(config):
var = await to_code_base(config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,53 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_BSEC2
#include "bme68x_bsec2_i2c.h"
#include "esphome/components/i2c/i2c.h"
#include <cinttypes>
namespace esphome {
namespace bme68x_bsec2_i2c {
static const char *const TAG = "bme68x_bsec2_i2c.sensor";
void BME68xBSEC2I2CComponent::setup() {
// must set up our bme68x_dev instance before calling setup()
this->bme68x_.intf_ptr = (void *) this;
this->bme68x_.intf = BME68X_I2C_INTF;
this->bme68x_.read = BME68xBSEC2I2CComponent::read_bytes_wrapper;
this->bme68x_.write = BME68xBSEC2I2CComponent::write_bytes_wrapper;
this->bme68x_.delay_us = BME68xBSEC2I2CComponent::delay_us;
this->bme68x_.amb_temp = 25;
BME68xBSEC2Component::setup();
}
void BME68xBSEC2I2CComponent::dump_config() {
LOG_I2C_DEVICE(this);
BME68xBSEC2Component::dump_config();
}
uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); }
int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) {
ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register);
return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->read_bytes(a_register, data, len) ? 0 : -1;
}
int8_t BME68xBSEC2I2CComponent::write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len,
void *intfPtr) {
ESP_LOGVV(TAG, "write_bytes_wrapper: reg = %u", a_register);
return static_cast<BME68xBSEC2I2CComponent *>(intfPtr)->write_bytes(a_register, data, len) ? 0 : -1;
}
void BME68xBSEC2I2CComponent::delay_us(uint32_t period, void *intfPtr) {
ESP_LOGVV(TAG, "Delaying for %" PRIu32 "us", period);
delayMicroseconds(period);
}
} // namespace bme68x_bsec2_i2c
} // namespace esphome
#endif

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#ifdef USE_BSEC2
#include "esphome/components/bme68x_bsec2/bme68x_bsec2.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bme68x_bsec2_i2c {
class BME68xBSEC2I2CComponent : public bme68x_bsec2::BME68xBSEC2Component, public i2c::I2CDevice {
void setup() override;
void dump_config() override;
uint32_t get_hash() override;
static int8_t read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr);
static int8_t write_bytes_wrapper(uint8_t a_register, const uint8_t *data, uint32_t len, void *intfPtr);
static void delay_us(uint32_t period, void *intfPtr);
};
} // namespace bme68x_bsec2_i2c
} // namespace esphome
#endif

View file

@ -1,16 +1,16 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
@ -18,8 +18,8 @@ from esphome.const import (
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
IS_PLATFORM_COMPONENT = True

View file

@ -1,4 +1,5 @@
#include "captive_portal.h"
#ifdef USE_CAPTIVE_PORTAL
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/wifi/wifi_component.h"
@ -91,3 +92,4 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_CAPTIVE_PORTAL
#include <memory>
#ifdef USE_ARDUINO
#include <DNSServer.h>
@ -71,3 +72,4 @@ extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-
} // namespace captive_portal
} // namespace esphome
#endif

View file

@ -1,8 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION_STATE_TOPIC,
CONF_AWAY,
@ -21,6 +20,7 @@ from esphome.const import (
CONF_MODE,
CONF_MODE_COMMAND_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_MQTT_ID,
CONF_ON_CONTROL,
CONF_ON_STATE,
CONF_PRESET,
@ -33,20 +33,20 @@ from esphome.const import (
CONF_TARGET_HUMIDITY_STATE_TOPIC,
CONF_TARGET_TEMPERATURE,
CONF_TARGET_TEMPERATURE_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH,
CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_LOW,
CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC,
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC,
CONF_TARGET_TEMPERATURE_STATE_TOPIC,
CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True

View file

@ -141,7 +141,7 @@ struct ClimateDeviceRestoreState {
float target_temperature_low;
float target_temperature_high;
};
};
} __attribute__((packed));
float target_humidity;
/// Convert this struct to a climate call that can be performed.

View file

@ -1,23 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS,
CONF_STATE,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC,
CONF_STATE,
CONF_STOP,
CONF_TILT,
CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC,
CONF_STOP,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND,
DEVICE_CLASS_CURTAIN,

View file

@ -5,13 +5,17 @@ namespace cst226 {
void CST226Touchscreen::setup() {
esph_log_config(TAG, "Setting up CST226 Touchscreen...");
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
this->set_timeout(30, [this] { this->continue_setup_(); });
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
}
}
void CST226Touchscreen::update_touches() {

View file

@ -35,7 +35,7 @@ class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void continue_setup_();
InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{NULL_PIN};
GPIOPin *reset_pin_{};
uint8_t chip_id_{};
bool setup_complete_{};
};

View file

@ -1,32 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt, web_server, time
import esphome.codegen as cg
from esphome.components import mqtt, time, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DATE,
CONF_DATETIME,
CONF_DAY,
CONF_HOUR,
CONF_ID,
CONF_MINUTE,
CONF_MONTH,
CONF_MQTT_ID,
CONF_ON_TIME,
CONF_ON_VALUE,
CONF_SECOND,
CONF_TIME,
CONF_TIME_ID,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_DATE,
CONF_DATETIME,
CONF_TIME,
CONF_YEAR,
CONF_MONTH,
CONF_DAY,
CONF_SECOND,
CONF_HOUR,
CONF_MINUTE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter", "@jesserockz"]
DEPENDENCIES = ["time"]

View file

@ -16,10 +16,11 @@ class DemoSensor : public sensor::Sensor, public PollingComponent {
float base = std::isnan(this->state) ? 0.0f : this->state;
this->publish_state(base + val * 10);
} else {
if (val < 0.1)
if (val < 0.1) {
this->publish_state(NAN);
else
} else {
this->publish_state(val * 100);
}
}
}
};

View file

@ -675,5 +675,36 @@ void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
const LogString *text_align_to_string(TextAlign textalign) {
switch (textalign) {
case TextAlign::TOP_LEFT:
return LOG_STR("TOP_LEFT");
case TextAlign::TOP_CENTER:
return LOG_STR("TOP_CENTER");
case TextAlign::TOP_RIGHT:
return LOG_STR("TOP_RIGHT");
case TextAlign::CENTER_LEFT:
return LOG_STR("CENTER_LEFT");
case TextAlign::CENTER:
return LOG_STR("CENTER");
case TextAlign::CENTER_RIGHT:
return LOG_STR("CENTER_RIGHT");
case TextAlign::BASELINE_LEFT:
return LOG_STR("BASELINE_LEFT");
case TextAlign::BASELINE_CENTER:
return LOG_STR("BASELINE_CENTER");
case TextAlign::BASELINE_RIGHT:
return LOG_STR("BASELINE_RIGHT");
case TextAlign::BOTTOM_LEFT:
return LOG_STR("BOTTOM_LEFT");
case TextAlign::BOTTOM_CENTER:
return LOG_STR("BOTTOM_CENTER");
case TextAlign::BOTTOM_RIGHT:
return LOG_STR("BOTTOM_RIGHT");
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace display
} // namespace esphome

View file

@ -8,6 +8,7 @@
#include "esphome/core/color.h"
#include "esphome/core/automation.h"
#include "esphome/core/time.h"
#include "esphome/core/log.h"
#include "display_color_utils.h"
#ifdef USE_GRAPH
@ -737,5 +738,7 @@ class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *>
DisplayPage *to_{nullptr};
};
const LogString *text_align_to_string(TextAlign textalign);
} // namespace display
} // namespace esphome

View file

@ -1,23 +1,26 @@
import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation, core
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components.number import Number
from esphome.components.select import Select
from esphome.components.switch import Switch
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_TYPE,
CONF_TRIGGER_ID,
CONF_ON_VALUE,
CONF_ACTIVE,
CONF_COMMAND,
CONF_CUSTOM,
CONF_NUMBER,
CONF_FORMAT,
CONF_ID,
CONF_ITEMS,
CONF_MODE,
CONF_ACTIVE,
CONF_NUMBER,
CONF_ON_VALUE,
CONF_TEXT,
CONF_TRIGGER_ID,
CONF_TYPE,
)
from esphome.automation import maybe_simple_id
from esphome.components.select import Select
from esphome.components.number import Number
from esphome.components.switch import Switch
CODEOWNERS = ["@numo68"]
@ -29,10 +32,8 @@ CONF_JOYSTICK = "joystick"
CONF_LABEL = "label"
CONF_MENU = "menu"
CONF_BACK = "back"
CONF_TEXT = "text"
CONF_SELECT = "select"
CONF_SWITCH = "switch"
CONF_ITEMS = "items"
CONF_ON_TEXT = "on_text"
CONF_OFF_TEXT = "off_text"
CONF_VALUE_LAMBDA = "value_lambda"

View file

@ -1,4 +1,5 @@
#include "e131.h"
#ifdef USE_NETWORK
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"
@ -118,3 +119,4 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_NETWORK
#include "esphome/components/socket/socket.h"
#include "esphome/core/component.h"
@ -53,3 +54,4 @@ class E131Component : public esphome::Component {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#include "e131_addressable_light_effect.h"
#include "e131.h"
#ifdef USE_NETWORK
#include "esphome/core/log.h"
namespace esphome {
@ -90,3 +91,4 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
} // namespace e131
} // namespace esphome
#endif

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/light/addressable_light_effect.h"
#ifdef USE_NETWORK
namespace esphome {
namespace e131 {
@ -42,3 +42,4 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#include <cstring>
#include "e131.h"
#ifdef USE_NETWORK
#include "esphome/components/network/ip_address.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@ -137,3 +138,4 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,11 +1,12 @@
from dataclasses import dataclass
from typing import Union, Optional
from pathlib import Path
import logging
import os
import esphome.final_validate as fv
from pathlib import Path
from typing import Optional, Union
from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p
from esphome import git
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
CONF_BOARD,
@ -15,6 +16,7 @@ from esphome.const import (
CONF_IGNORE_EFUSE_MAC_CRC,
CONF_NAME,
CONF_PATH,
CONF_PLATFORM_VERSION,
CONF_PLATFORMIO_OPTIONS,
CONF_REF,
CONF_REFRESH,
@ -32,13 +34,12 @@ from esphome.const import (
TYPE_GIT,
TYPE_LOCAL,
__version__,
CONF_PLATFORM_VERSION,
)
from esphome.core import CORE, HexInt, TimePeriod
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome import git
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from .boards import BOARDS
from .const import ( # noqa
KEY_BOARD,
KEY_COMPONENTS,
@ -54,12 +55,10 @@ from .const import ( # noqa
VARIANT_FRIENDLY,
VARIANTS,
)
from .boards import BOARDS
# force import gpio to register pin schema
from .gpio import esp32_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"]
@ -173,6 +172,19 @@ def add_idf_component(
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
}
else:
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
if components is not None:
component_config[KEY_COMPONENTS] = list(
set(component_config[KEY_COMPONENTS] + components)
)
if submodules is not None:
if component_config[KEY_SUBMODULES] is None:
component_config[KEY_SUBMODULES] = submodules
else:
component_config[KEY_SUBMODULES] = list(
set(component_config[KEY_SUBMODULES] + submodules)
)
def add_extra_script(stage: str, filename: str, path: str):

View file

@ -1,4 +1,4 @@
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3
from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3
ESP32_BASE_PINS = {
"TX": 1,

View file

@ -1,22 +1,22 @@
from dataclasses import dataclass
from typing import Any
import logging
from typing import Any
from esphome import pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OPEN_DRAIN,
CONF_OUTPUT,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
PLATFORM_ESP32,
)
from esphome import pins
from esphome.core import CORE
import esphome.config_validation as cv
import esphome.codegen as cg
from . import boards
from .const import (
@ -24,22 +24,21 @@ from .const import (
KEY_ESP32,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
esp32_ns,
)
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)

View file

@ -1,5 +1,6 @@
import logging
import esphome.config_validation as cv
from esphome.const import (
CONF_INPUT,
CONF_MODE,
@ -8,10 +9,8 @@ from esphome.const import (
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP_SDIO_PINS = {
6: "Flash Clock",
7: "Flash Data 0",

View file

@ -1,10 +1,9 @@
import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
import esphome.config_validation as cv
_ESP32C2_STRAPPING_PINS = {8, 9}
_LOGGER = logging.getLogger(__name__)

View file

@ -1,11 +1,7 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
_ESP32C3_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
_ESP32C6_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
_ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}

View file

@ -1,5 +1,6 @@
import logging
import esphome.config_validation as cv
from esphome.const import (
CONF_INPUT,
CONF_MODE,
@ -8,8 +9,6 @@ from esphome.const import (
CONF_PULLDOWN,
CONF_PULLUP,
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP32S2_SPI_PSRAM_PINS = {

View file

@ -1,12 +1,7 @@
import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
_ESP_32S3_SPI_PSRAM_PINS = {

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"]

View file

@ -1,10 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components.esp32_ble import CONF_BLE_ID
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
from esphome.core import CORE, TimePeriod
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import CONF_BLE_ID
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID
from esphome.core import CORE, TimePeriod
AUTO_LOAD = ["esp32_ble"]
DEPENDENCIES = ["esp32"]

View file

@ -1,5 +1,4 @@
import esphome.codegen as cg
from esphome.components import esp32_ble_tracker
AUTO_LOAD = ["esp32_ble_tracker"]

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg
from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL
from esphome.components import esp32_ble
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
AUTO_LOAD = ["esp32_ble"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]

View file

@ -1,10 +1,10 @@
import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTIVE,
CONF_DURATION,

View file

@ -1,9 +1,8 @@
import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble_server, output
import esphome.config_validation as cv
from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID
AUTO_LOAD = ["esp32_ble_server"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["wifi", "esp32"]
@ -50,7 +49,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.2.3")
cg.add_library("improv/Improv", "1.2.4")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View file

@ -1,5 +1,5 @@
#include "ota_esphome.h"
#ifdef USE_OTA
#include "esphome/components/md5/md5.h"
#include "esphome/components/network/util.h"
#include "esphome/components/ota/ota_backend.h"
@ -410,3 +410,4 @@ float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::A
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
} // namespace esphome
#endif

View file

@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_OTA
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/components/ota/ota_backend.h"
@ -41,3 +42,4 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
};
} // namespace esphome
#endif

View file

@ -1,6 +1,4 @@
from esphome import pins
import esphome.config_validation as cv
import esphome.final_validate as fv
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import (
@ -8,31 +6,33 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from esphome.components.network import IPAddress
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
import esphome.config_validation as cv
from esphome.const import (
CONF_DOMAIN,
CONF_ID,
CONF_VALUE,
CONF_MANUAL_IP,
CONF_STATIC_IP,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_GATEWAY,
CONF_SUBNET,
CONF_ADDRESS,
CONF_CLK_PIN,
CONF_CS_PIN,
CONF_DNS1,
CONF_DNS2,
CONF_CLK_PIN,
CONF_DOMAIN,
CONF_GATEWAY,
CONF_ID,
CONF_INTERRUPT_PIN,
CONF_MANUAL_IP,
CONF_MISO_PIN,
CONF_MOSI_PIN,
CONF_CS_PIN,
CONF_INTERRUPT_PIN,
CONF_PAGE_ID,
CONF_RESET_PIN,
CONF_SPI,
CONF_PAGE_ID,
CONF_ADDRESS,
CONF_STATIC_IP,
CONF_SUBNET,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_VALUE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.components.network import IPAddress
from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX
import esphome.final_validate as fv
CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"]

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_IP_ADDRESS,
CONF_DNS_ADDRESS,
CONF_IP_ADDRESS,
CONF_MAC_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC,
)

View file

@ -1,24 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_EVENT_TYPE,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_EVENT,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_EVENT_TYPE,
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True

View file

@ -1,31 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_OSCILLATING,
CONF_OSCILLATION_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
CONF_SPEED,
CONF_SPEED_LEVEL_COMMAND_TOPIC,
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_OFF_SPEED_CYCLE,
CONF_ON_DIRECTION_SET,
CONF_ON_OSCILLATING_SET,
CONF_ON_PRESET_SET,
CONF_ON_SPEED_SET,
CONF_ON_STATE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_ON_PRESET_SET,
CONF_TRIGGER_ID,
CONF_DIRECTION,
CONF_OSCILLATING,
CONF_OSCILLATION_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
CONF_RESTORE_MODE,
CONF_SPEED,
CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_LEVEL_COMMAND_TOPIC,
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity

View file

@ -307,7 +307,7 @@ void FingerprintGrowComponent::delete_fingerprint(uint16_t finger_id) {
void FingerprintGrowComponent::delete_all_fingerprints() {
ESP_LOGI(TAG, "Deleting all stored fingerprints");
this->data_ = {EMPTY};
this->data_ = {DELETE_ALL};
switch (this->send_command_()) {
case OK:
ESP_LOGI(TAG, "Deleted all fingerprints");

View file

@ -36,7 +36,7 @@ enum GrowCommand {
LOAD = 0x07,
UPLOAD = 0x08,
DELETE = 0x0C,
EMPTY = 0x0D,
DELETE_ALL = 0x0D, // aka EMPTY
READ_SYS_PARAM = 0x0F,
SET_PASSWORD = 0x12,
VERIFY_PASSWORD = 0x13,

View file

@ -1,19 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display, font, color
from esphome.const import CONF_DISPLAY, CONF_ID, CONF_TRIGGER_ID
from esphome import automation, core
import esphome.codegen as cg
from esphome.components import color, display, font
from esphome.components.display_menu_base import (
DISPLAY_MENU_BASE_SCHEMA,
DisplayMenuComponent,
display_menu_to_code,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_BACKGROUND_COLOR,
CONF_DISPLAY,
CONF_FONT,
CONF_FOREGROUND_COLOR,
CONF_ID,
CONF_TRIGGER_ID,
)
CONF_FONT = "font"
CONF_MENU_ITEM_VALUE = "menu_item_value"
CONF_FOREGROUND_COLOR = "foreground_color"
CONF_BACKGROUND_COLOR = "background_color"
CONF_ON_REDRAW = "on_redraw"
graphical_display_menu_ns = cg.esphome_ns.namespace("graphical_display_menu")

View file

@ -80,8 +80,8 @@ class HaierClimateBase : public esphome::Component,
const char *phase_to_string_(ProtocolPhases phase);
virtual void set_handlers() = 0;
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0;
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming)
virtual void initialization(){};
virtual bool prepare_pending_action();
virtual void process_protocol_reset();

View file

@ -0,0 +1,2 @@
AUTO_LOAD = ["md5"]
CODEOWNERS = ["@dwmw2"]

View file

@ -0,0 +1,56 @@
#include <cstdio>
#include <cstring>
#include "hmac_md5.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace hmac_md5 {
void HmacMD5::init(const uint8_t *key, size_t len) {
uint8_t ipad[64], opad[64];
memset(ipad, 0, sizeof(ipad));
if (len > 64) {
md5::MD5Digest keymd5;
keymd5.init();
keymd5.add(key, len);
keymd5.calculate();
keymd5.get_bytes(ipad);
} else {
memcpy(ipad, key, len);
}
memcpy(opad, ipad, sizeof(opad));
for (int i = 0; i < 64; i++) {
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
this->ihash_.init();
this->ihash_.add(ipad, sizeof(ipad));
this->ohash_.init();
this->ohash_.add(opad, sizeof(opad));
}
void HmacMD5::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); }
void HmacMD5::calculate() {
uint8_t ibytes[16];
this->ihash_.calculate();
this->ihash_.get_bytes(ibytes);
this->ohash_.add(ibytes, sizeof(ibytes));
this->ohash_.calculate();
}
void HmacMD5::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); }
void HmacMD5::get_hex(char *output) { this->ohash_.get_hex(output); }
bool HmacMD5::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); }
bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); }
} // namespace hmac_md5
} // namespace esphome

View file

@ -0,0 +1,48 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/components/md5/md5.h"
#include <string>
namespace esphome {
namespace hmac_md5 {
class HmacMD5 {
public:
HmacMD5() = default;
~HmacMD5() = default;
/// Initialize a new MD5 digest computation.
void init(const uint8_t *key, size_t len);
void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); }
void init(const std::string &key) { this->init(key.c_str(), key.length()); }
/// Add bytes of data for the digest.
void add(const uint8_t *data, size_t len);
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
/// Compute the digest, based on the provided data.
void calculate();
/// Retrieve the HMAC-MD5 digest as bytes.
/// The output must be able to hold 16 bytes or more.
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-MD5 digest as hex characters.
/// The output must be able to hold 32 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes).
bool equals_bytes(const uint8_t *expected);
/// Compare the digest against a provided hex-encoded digest (32 bytes).
bool equals_hex(const char *expected);
protected:
md5::MD5Digest ihash_;
md5::MD5Digest ohash_;
};
} // namespace hmac_md5
} // namespace esphome

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
CODEOWNERS = ["@OttoWinter"]
CODEOWNERS = ["@OttoWinter", "@esphome/core"]
homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
@ -13,6 +13,13 @@ HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
}
)
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA = cv.Schema(
{
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_INTERNAL, default=True): cv.boolean,
}
)
def setup_home_assistant_entity(var, config):
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from .. import (
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
CODEOWNERS = ["@landonr"]
DEPENDENCIES = ["api"]
HomeassistantNumber = homeassistant_ns.class_(
"HomeassistantNumber", number.Number, cg.Component
)
CONFIG_SCHEMA = (
number.number_schema(HomeassistantNumber)
.extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await number.new_number(
config,
min_value=0,
max_value=0,
step=0,
)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)

View file

@ -0,0 +1,100 @@
#include "homeassistant_number.h"
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
namespace esphome {
namespace homeassistant {
static const char *const TAG = "homeassistant.number";
void HomeassistantNumber::state_changed_(const std::string &state) {
auto number_value = parse_number<float>(state);
if (!number_value.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str());
this->publish_state(NAN);
return;
}
if (this->state == number_value.value()) {
return;
}
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), state.c_str());
this->publish_state(number_value.value());
}
void HomeassistantNumber::min_retrieved_(const std::string &min) {
auto min_value = parse_number<float>(min);
if (!min_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str());
}
ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
this->traits.set_min_value(min_value.value());
}
void HomeassistantNumber::max_retrieved_(const std::string &max) {
auto max_value = parse_number<float>(max);
if (!max_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str());
}
ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
this->traits.set_max_value(max_value.value());
}
void HomeassistantNumber::step_retrieved_(const std::string &step) {
auto step_value = parse_number<float>(step);
if (!step_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str());
}
ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
this->traits.set_step(step_value.value());
}
void HomeassistantNumber::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, nullopt, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("min"),
std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("max"),
std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("step"),
std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
}
void HomeassistantNumber::dump_config() {
LOG_NUMBER("", "Homeassistant Number", this);
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
}
float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
void HomeassistantNumber::control(float value) {
if (!api::global_api_server->is_connected()) {
ESP_LOGE(TAG, "No clients connected to API server");
return;
}
this->publish_state(value);
api::HomeassistantServiceResponse resp;
resp.service = "number.set_value";
api::HomeassistantServiceMap entity_id;
entity_id.key = "entity_id";
entity_id.value = this->entity_id_;
resp.data.push_back(entity_id);
api::HomeassistantServiceMap entity_value;
entity_value.key = "value";
entity_value.value = to_string(value);
resp.data.push_back(entity_value);
api::global_api_server->send_homeassistant_service_call(resp);
}
} // namespace homeassistant
} // namespace esphome

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