Merge pull request #7278 from esphome/bump-2024.8.0b1

2024.8.0b1
This commit is contained in:
Jesse Hills
2024-08-15 07:50:36 +12:00
committed by GitHub
454 changed files with 17674 additions and 5273 deletions
+2 -2
View File
@@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.3.0 uses: docker/build-push-action@v6.7.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.3.0 uses: docker/build-push-action@v6.7.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
+7
View File
@@ -13,6 +13,13 @@ updates:
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
groups:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/.github/actions/build-image" directory: "/.github/actions/build-image"
schedule: schedule:
+2 -2
View File
@@ -46,9 +46,9 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.1.0 uses: docker/setup-qemu-action@v3.2.0
- name: Set TAG - name: Set TAG
run: | run: |
+3
View File
@@ -9,6 +9,7 @@ on:
paths: paths:
- "**" - "**"
- "!.github/workflows/*.yml" - "!.github/workflows/*.yml"
- "!.github/actions/build-image/*"
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- "!.yamllint" - "!.yamllint"
- "!.github/dependabot.yml" - "!.github/dependabot.yml"
@@ -468,6 +469,8 @@ jobs:
- name: Compile config - name: Compile config
run: | run: |
. venv/bin/activate . venv/bin/activate
mkdir build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
for component in ${{ matrix.components }}; do for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component ./script/test_build_components -e compile -c $component
done done
+7 -7
View File
@@ -90,18 +90,18 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.1.0 uses: docker/setup-qemu-action@v3.2.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -184,17 +184,17 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.4.0 uses: docker/setup-buildx-action@v3.6.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
+2
View File
@@ -138,3 +138,5 @@ sdkconfig.*
.tests/ .tests/
/components /components
/managed_components
+9
View File
@@ -2,6 +2,15 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2 rev: 24.4.2
hooks: hooks:
+12 -1
View File
@@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @OttoWinter esphome/components/api/* @OttoWinter
esphome/components/as5600/* @ammmze esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
@@ -45,6 +46,7 @@ esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669 esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner esphome/components/atm90e26/* @danieltwagner
esphome/components/atm90e32/* @circuitsetup @descipher
esphome/components/b_parasite/* @rbaron esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter esphome/components/bang_bang/* @OttoWinter
@@ -64,6 +66,8 @@ esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme280_base/* @esphome/core esphome/components/bme280_base/* @esphome/core
esphome/components/bme280_spi/* @apbodrov esphome/components/bme280_spi/* @apbodrov
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bme68x_bsec2/* @kbx81 @neffs
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
esphome/components/bmi160/* @flaviut esphome/components/bmi160/* @flaviut
esphome/components/bmp3xx/* @latonita esphome/components/bmp3xx/* @latonita
esphome/components/bmp3xx_base/* @latonita @martgras esphome/components/bmp3xx_base/* @latonita @martgras
@@ -165,7 +169,9 @@ esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff esphome/components/honeywellabp2_i2c/* @jpfaff
@@ -216,6 +222,8 @@ esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/matrix_keypad/* @ssieb esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283 esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger esphome/components/max44009/* @berfenger
@@ -273,6 +281,7 @@ esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @guillempages
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
@@ -424,6 +433,7 @@ esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra esphome/components/web_server_idf/* @dentra
@@ -446,6 +456,7 @@ esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# If /cache is mounted, use that as PIO's coredir # 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) # otherwise use path in /config (so that PIO packages aren't downloaded on each compile)
+15 -81
View File
@@ -1,12 +1,12 @@
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
import argparse import argparse
from datetime import datetime
import functools import functools
import logging import logging
import os import os
import re import re
import sys import sys
import time import time
from datetime import datetime
import argcomplete import argcomplete
@@ -39,14 +39,14 @@ from esphome.const import (
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address from esphome.helpers import indent, is_ip_address
from esphome.log import Fore, color, setup_log
from esphome.util import ( from esphome.util import (
get_serial_ports,
list_yaml_files,
run_external_command, run_external_command,
run_external_process, run_external_process,
safe_print, safe_print,
list_yaml_files,
get_serial_ports,
) )
from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -116,6 +116,7 @@ def get_port_type(port):
def run_miniterm(config, port): def run_miniterm(config, port):
import serial import serial
from esphome import platformio_api from esphome import platformio_api
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
@@ -596,9 +597,10 @@ def command_update_all(args):
def command_idedata(args, config): def command_idedata(args, config):
from esphome import platformio_api
import json import json
from esphome import platformio_api
logging.disable(logging.INFO) logging.disable(logging.INFO)
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
@@ -747,7 +749,14 @@ def parse_args(argv):
) )
parser = argparse.ArgumentParser( 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) mqtt_options = argparse.ArgumentParser(add_help=False)
@@ -948,67 +957,6 @@ def parse_args(argv):
# a deprecation warning). # a deprecation warning).
arguments = argv[1:] 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) argcomplete.autocomplete(parser)
return parser.parse_args(arguments) return parser.parse_args(arguments)
@@ -1023,20 +971,6 @@ def run_esphome(argv):
# Show timestamp for dashboard access logs # Show timestamp for dashboard access logs
args.command == "dashboard", 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: if args.command in PRE_CONFIG_ACTIONS:
try: try:
+1 -1
View File
@@ -7,10 +7,10 @@ from esphome.const import (
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
CONF_TIME,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
+53 -53
View File
@@ -8,55 +8,78 @@
# want to break suddenly due to a rename (this file will get backports for features). # want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
Expression, Expression,
LineComment,
MockObj,
MockObjClass,
Pvariable,
RawExpression, RawExpression,
RawStatement, RawStatement,
TemplateArguments,
StructInitializer,
ArrayInitializer,
safe_exp,
Statement, Statement,
LineComment, StructInitializer,
progmem_array, TemplateArguments,
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,
add, add,
add_global,
add_library,
add_build_flag, add_build_flag,
add_define, add_define,
add_global,
add_library,
add_platformio_option, add_platformio_option,
get_variable, get_variable,
get_variable_with_full_id, get_variable_with_full_id,
process_lambda,
is_template, is_template,
new_Pvariable,
new_variable,
process_lambda,
progmem_array,
safe_exp,
statement,
static_const_array,
templatable, templatable,
MockObj, variable,
MockObjClass, with_local_variable,
) )
from esphome.cpp_helpers import ( # noqa from esphome.cpp_helpers import ( # noqa: F401
gpio_pin_expression,
register_component,
build_registry_entry, build_registry_entry,
build_registry_list, build_registry_list,
extract_registry_entry_config, extract_registry_entry_config,
register_parented, gpio_pin_expression,
past_safe_mode, past_safe_mode,
register_component,
register_parented,
) )
from esphome.cpp_types import ( # noqa from esphome.cpp_types import ( # noqa: F401
global_ns, NAN,
void, App,
nullptr, Application,
float_, Component,
double, ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
GPIOPin,
InternalGPIOPin,
JsonObject,
JsonObjectConst,
Parented,
PollingComponent,
arduino_json_ns,
bool_, bool_,
const_char_ptr,
double,
esphome_ns,
float_,
global_ns,
gpio_Flags,
int16,
int32,
int64,
int_, int_,
nullptr,
optional,
size_t,
std_ns, std_ns,
std_shared_ptr, std_shared_ptr,
std_string, std_string,
@@ -66,28 +89,5 @@ from esphome.cpp_types import ( # noqa
uint16, uint16,
uint32, uint32,
uint64, uint64,
int16, void,
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,
) )
+1 -1
View File
@@ -1,5 +1,5 @@
import esphome.config_validation as cv import esphome.config_validation as cv
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ade7953 sensor component has been renamed to ade7953_i2c." "The ade7953 sensor component has been renamed to ade7953_i2c."
) )
@@ -60,7 +60,7 @@ bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
this->write_byte16(reg); this->write_byte16(reg);
this->transfer_byte(0x80); this->transfer_byte(0x80);
uint8_t recv[2]; uint8_t recv[2];
this->read_array(recv, 4); this->read_array(recv, 2);
*value = encode_uint16(recv[0], recv[1]); *value = encode_uint16(recv[0], recv[1]);
this->disable(); this->disable();
return false; return false;
@@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
ESP_LOGD(TAG, "version = %d", value->version); ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) { if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
if (this->humidity_sensor_ != nullptr) { if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f); 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)) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc); this->tvoc_sensor_->publish_state(value->voc);
} }
if (this->illuminance_sensor_ != nullptr) {
this->illuminance_sensor_->publish_state(value->ambientLight);
}
} else { } else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); 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", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
} }
AirthingsWavePlus::AirthingsWavePlus() { AirthingsWavePlus::AirthingsWavePlus() {
@@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } 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_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_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
protected: protected:
bool is_valid_radon_value_(uint16_t radon); 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_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
struct WavePlusReadings { struct WavePlusReadings {
uint8_t version; uint8_t version;
@@ -12,6 +12,9 @@ from esphome.const import (
CONF_CO2, CONF_CO2,
UNIT_BECQUEREL_PER_CUBIC_METER, UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
CONF_ILLUMINANCE,
UNIT_LUX,
DEVICE_CLASS_ILLUMINANCE,
) )
DEPENDENCIES = airthings_wave_base.DEPENDENCIES DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
device_class=DEVICE_CLASS_CARBON_DIOXIDE, device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT, 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): if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2) sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens)) 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))
@@ -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 import automation
from esphome.automation import maybe_simple_id 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 ( from esphome.const import (
CONF_CODE,
CONF_ID, CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_CODE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
) )
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"] CODEOWNERS = ["@grahambrown11", "@hwstar"]
@@ -77,67 +78,72 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition "AlarmControlPanelCondition", automation.Condition
) )
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ALARM_CONTROL_PANEL_SCHEMA = (
web_server.WEBSERVER_SORTING_SCHEMA cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
).extend( .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
{ .extend(
cv.GenerateID(): cv.declare_id(AlarmControlPanel), {
cv.Optional(CONF_ON_STATE): automation.validate_automation( cv.GenerateID(): cv.declare_id(AlarmControlPanel),
{ cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), mqtt.MQTTAlarmControlPanelComponent
} ),
), cv.Optional(CONF_ON_STATE): automation.validate_automation(
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), }
} ),
), cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
cv.Optional(CONF_ON_ARMING): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger), }
} ),
), cv.Optional(CONF_ON_ARMING): automation.validate_automation(
cv.Optional(CONF_ON_PENDING): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger), }
} ),
), cv.Optional(CONF_ON_PENDING): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger), }
} ),
), cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
cv.Optional(CONF_ON_DISARMED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger), }
} ),
), cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), }
} ),
), cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
cv.Optional(CONF_ON_CHIME): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger), }
} ),
), cv.Optional(CONF_ON_CHIME): automation.validate_automation(
cv.Optional(CONF_ON_READY): automation.validate_automation( {
{ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger), }
} ),
), 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( 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: if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id) web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config) 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): async def register_alarm_control_panel(var, config):
+4
View File
@@ -0,0 +1,4 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
CODEOWNERS = ["@aodrenah"]
+151
View File
@@ -0,0 +1,151 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#include "apds9306.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace apds9306 {
static const char *const TAG = "apds9306";
enum { // APDS9306 registers
APDS9306_MAIN_CTRL = 0x00,
APDS9306_ALS_MEAS_RATE = 0x04,
APDS9306_ALS_GAIN = 0x05,
APDS9306_PART_ID = 0x06,
APDS9306_MAIN_STATUS = 0x07,
APDS9306_CLEAR_DATA_0 = 0x0A, // LSB
APDS9306_CLEAR_DATA_1 = 0x0B,
APDS9306_CLEAR_DATA_2 = 0x0C, // MSB
APDS9306_ALS_DATA_0 = 0x0D, // LSB
APDS9306_ALS_DATA_1 = 0x0E,
APDS9306_ALS_DATA_2 = 0x0F, // MSB
APDS9306_INT_CFG = 0x19,
APDS9306_INT_PERSISTENCE = 0x1A,
APDS9306_ALS_THRES_UP_0 = 0x21, // LSB
APDS9306_ALS_THRES_UP_1 = 0x22,
APDS9306_ALS_THRES_UP_2 = 0x23, // MSB
APDS9306_ALS_THRES_LOW_0 = 0x24, // LSB
APDS9306_ALS_THRES_LOW_1 = 0x25,
APDS9306_ALS_THRES_LOW_2 = 0x26, // MSB
APDS9306_ALS_THRES_VAR = 0x27
};
#define APDS9306_ERROR_CHECK(func, error) \
if (!(func)) { \
ESP_LOGE(TAG, error); \
this->mark_failed(); \
return; \
}
#define APDS9306_WARNING_CHECK(func, warning) \
if (!(func)) { \
ESP_LOGW(TAG, warning); \
this->status_set_warning(); \
return; \
}
#define APDS9306_WRITE_BYTE(reg, value) \
ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \
if (!this->write_byte(reg, value)) { \
ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \
this->mark_failed(); \
return; \
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate);
// ALS gain, see datasheet or init.py for options
uint8_t als_gain = (this->gain_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain);
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Check for data, clear main status
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
}
void APDS9306::dump_config() {
LOG_SENSOR("", "APDS9306", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");
break;
default:
ESP_LOGE(TAG, "Setting up APDS9306 registers failed!");
break;
}
}
ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]);
ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
LOG_UPDATE_INTERVAL(this);
}
void APDS9306::update() {
// Check for new data
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data
return;
}
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Clear MAIN STATUS
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
uint8_t als_data[3];
APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]);
float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) *
(100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux);
this->publish_state(lux);
}
} // namespace apds9306
} // namespace esphome
+66
View File
@@ -0,0 +1,66 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace apds9306 {
enum MeasurementBitWidth : uint8_t {
MEASUREMENT_BIT_WIDTH_20 = 0,
MEASUREMENT_BIT_WIDTH_19 = 1,
MEASUREMENT_BIT_WIDTH_18 = 2,
MEASUREMENT_BIT_WIDTH_17 = 3,
MEASUREMENT_BIT_WIDTH_16 = 4,
MEASUREMENT_BIT_WIDTH_13 = 5,
};
static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13};
enum MeasurementRate : uint8_t {
MEASUREMENT_RATE_25 = 0,
MEASUREMENT_RATE_50 = 1,
MEASUREMENT_RATE_100 = 2,
MEASUREMENT_RATE_200 = 3,
MEASUREMENT_RATE_500 = 4,
MEASUREMENT_RATE_1000 = 5,
MEASUREMENT_RATE_2000 = 6,
};
static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000};
enum AmbientLightGain : uint8_t {
AMBIENT_LIGHT_GAIN_1 = 0,
AMBIENT_LIGHT_GAIN_3 = 1,
AMBIENT_LIGHT_GAIN_6 = 2,
AMBIENT_LIGHT_GAIN_9 = 3,
AMBIENT_LIGHT_GAIN_18 = 4,
};
static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18};
class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void dump_config() override;
void update() override;
void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; }
void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; }
void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; }
protected:
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
WRONG_ID,
} error_code_{NONE};
MeasurementBitWidth bit_width_;
MeasurementRate measurement_rate_;
AmbientLightGain gain_;
};
} // namespace apds9306
} // namespace esphome
+95
View File
@@ -0,0 +1,95 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
DEVICE_CLASS_ILLUMINANCE,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CONF_APDS9306_ID = "apds9306_id"
CONF_BIT_WIDTH = "bit_width"
CONF_MEASUREMENT_RATE = "measurement_rate"
apds9306_ns = cg.esphome_ns.namespace("apds9306")
APDS9306 = apds9306_ns.class_(
"APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth")
MeasurementRate = apds9306_ns.enum("MeasurementRate")
AmbientLightGain = apds9306_ns.enum("AmbientLightGain")
MEASUREMENT_BIT_WIDTHS = {
20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20,
19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19,
18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18,
17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17,
16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16,
13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13,
}
MEASUREMENT_RATES = {
25: MeasurementRate.MEASUREMENT_RATE_25,
50: MeasurementRate.MEASUREMENT_RATE_50,
100: MeasurementRate.MEASUREMENT_RATE_100,
200: MeasurementRate.MEASUREMENT_RATE_200,
500: MeasurementRate.MEASUREMENT_RATE_500,
1000: MeasurementRate.MEASUREMENT_RATE_1000,
2000: MeasurementRate.MEASUREMENT_RATE_2000,
}
AMBIENT_LIGHT_GAINS = {
1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1,
3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3,
6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6,
9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9,
18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18,
}
def _validate_measurement_rate(value):
value = cv.positive_time_period_milliseconds(value)
return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds)
CONFIG_SCHEMA = (
sensor.sensor_schema(
APDS9306,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_LIGHTBULB,
)
.extend(
{
cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True),
cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum(
MEASUREMENT_BIT_WIDTHS, int=True
),
cv.Optional(
CONF_MEASUREMENT_RATE, default="100ms"
): _validate_measurement_rate,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x52))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_bit_width(config[CONF_BIT_WIDTH]))
cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE]))
cg.add(var.set_ambient_light_gain(config[CONF_GAIN]))
+79 -51
View File
@@ -1,25 +1,27 @@
import base64 import base64
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTION,
CONF_ACTIONS,
CONF_DATA, CONF_DATA,
CONF_DATA_TEMPLATE, CONF_DATA_TEMPLATE,
CONF_EVENT,
CONF_ID, CONF_ID,
CONF_KEY, CONF_KEY,
CONF_ON_CLIENT_CONNECTED,
CONF_ON_CLIENT_DISCONNECTED,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PORT, CONF_PORT,
CONF_REBOOT_TIMEOUT, CONF_REBOOT_TIMEOUT,
CONF_SERVICE, CONF_SERVICE,
CONF_VARIABLES,
CONF_SERVICES, CONF_SERVICES,
CONF_TRIGGER_ID,
CONF_EVENT,
CONF_TAG, CONF_TAG,
CONF_ON_CLIENT_CONNECTED, CONF_TRIGGER_ID,
CONF_ON_CLIENT_DISCONNECTED, CONF_VARIABLES,
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
@@ -63,40 +65,51 @@ def validate_encryption_key(value):
return value return value
CONFIG_SCHEMA = cv.Schema( ACTIONS_SCHEMA = automation.validate_automation(
{ {
cv.GenerateID(): cv.declare_id(APIServer), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(UserServiceTrigger),
cv.Optional(CONF_PORT, default=6053): cv.port, cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional(CONF_PASSWORD, default=""): cv.string_strict, cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.valid_name,
cv.Optional( cv.Optional(CONF_VARIABLES, default={}): cv.Schema(
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.validate_id_name: cv.one_of(*SERVICE_ARG_NATIVE_TYPES, lower=True),
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.Optional(CONF_ENCRYPTION): cv.Schema( },
{ cv.All(
cv.Required(CONF_KEY): validate_encryption_key, cv.has_exactly_one_key(CONF_SERVICE, CONF_ACTION),
} cv.rename_key(CONF_SERVICE, CONF_ACTION),
), ),
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( )
single=True
), CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( cv.Schema(
single=True {
), cv.GenerateID(): cv.declare_id(APIServer),
} cv.Optional(CONF_PORT, default=6053): cv.port,
).extend(cv.COMPONENT_SCHEMA) 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) @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_password(config[CONF_PASSWORD]))
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) 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 = [] template_args = []
func_args = [] func_args = []
service_arg_names = [] service_arg_names = []
@@ -119,7 +132,7 @@ async def to_code(config):
service_arg_names.append(name) service_arg_names.append(name)
templ = cg.TemplateArguments(*template_args) templ = cg.TemplateArguments(*template_args)
trigger = cg.new_Pvariable( 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)) cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
@@ -152,28 +165,43 @@ async def to_code(config):
KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)}) KEY_VALUE_SCHEMA = cv.Schema({cv.string: cv.templatable(cv.string_strict)})
HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema(
{ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
cv.GenerateID(): cv.use_id(APIServer), cv.Schema(
cv.Required(CONF_SERVICE): cv.templatable(cv.string), {
cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, cv.GenerateID(): cv.use_id(APIServer),
cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, cv.Exclusive(CONF_SERVICE, group_of_exclusion=CONF_ACTION): cv.templatable(
cv.Optional(CONF_VARIABLES, default={}): cv.Schema( cv.string
{cv.string: cv.returning_lambda} ),
), 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( @automation.register_action(
"homeassistant.service", "homeassistant.service",
HomeAssistantServiceCallAction, HomeAssistantServiceCallAction,
HOMEASSISTANT_SERVICE_ACTION_SCHEMA, HOMEASSISTANT_ACTION_ACTION_SCHEMA,
) )
async def homeassistant_service_to_code(config, action_id, template_arg, args): async def homeassistant_service_to_code(config, action_id, template_arg, args):
serv = await cg.get_variable(config[CONF_ID]) serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False) 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)) cg.add(var.set_service(templ))
for key, value in config[CONF_DATA].items(): for key, value in config[CONF_DATA].items():
templ = await cg.templatable(value, args, None) templ = await cg.templatable(value, args, None)
+7 -1
View File
@@ -686,6 +686,7 @@ message SubscribeHomeAssistantStateResponse {
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
string entity_id = 1; string entity_id = 1;
string attribute = 2; string attribute = 2;
bool once = 3;
} }
message HomeAssistantStateResponse { message HomeAssistantStateResponse {
@@ -1872,6 +1873,11 @@ message UpdateStateResponse {
string release_summary = 9; string release_summary = 9;
string release_url = 10; string release_url = 10;
} }
enum UpdateCommand {
UPDATE_COMMAND_NONE = 0;
UPDATE_COMMAND_UPDATE = 1;
UPDATE_COMMAND_CHECK = 2;
}
message UpdateCommandRequest { message UpdateCommandRequest {
option (id) = 118; option (id) = 118;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
@@ -1879,5 +1885,5 @@ message UpdateCommandRequest {
option (no_delay) = true; option (no_delay) = true;
fixed32 key = 1; fixed32 key = 1;
bool install = 2; UpdateCommand command = 2;
} }
+14 -1
View File
@@ -1328,7 +1328,20 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
if (update == nullptr) if (update == nullptr)
return; 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 #endif
+33 -4
View File
@@ -567,6 +567,20 @@ template<> const char *proto_enum_to_string<enums::ValveOperation>(enums::ValveO
} }
} }
#endif #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) { bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
@@ -3095,6 +3109,16 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}"); out.append("SubscribeHomeAssistantStatesRequest {}");
} }
#endif #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) { bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@@ -3112,6 +3136,7 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id); buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->attribute); buffer.encode_string(2, this->attribute);
buffer.encode_bool(3, this->once);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { 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(" attribute: ");
out.append("'").append(this->attribute).append("'"); out.append("'").append(this->attribute).append("'");
out.append("\n"); out.append("\n");
out.append(" once: ");
out.append(YESNO(this->once));
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@@ -8596,7 +8625,7 @@ void UpdateStateResponse::dump_to(std::string &out) const {
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 2: { case 2: {
this->install = value.as_bool(); this->command = value.as_enum<enums::UpdateCommand>();
return true; return true;
} }
default: default:
@@ -8615,7 +8644,7 @@ bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
} }
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const { void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key); 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 #ifdef HAS_PROTO_MESSAGE_DUMP
void UpdateCommandRequest::dump_to(std::string &out) const { 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(buffer);
out.append("\n"); out.append("\n");
out.append(" install: "); out.append(" command: ");
out.append(YESNO(this->install)); out.append(proto_enum_to_string<enums::UpdateCommand>(this->command));
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
} }
+8 -1
View File
@@ -227,6 +227,11 @@ enum ValveOperation : uint32_t {
VALVE_OPERATION_IS_OPENING = 1, VALVE_OPERATION_IS_OPENING = 1,
VALVE_OPERATION_IS_CLOSING = 2, VALVE_OPERATION_IS_CLOSING = 2,
}; };
enum UpdateCommand : uint32_t {
UPDATE_COMMAND_NONE = 0,
UPDATE_COMMAND_UPDATE = 1,
UPDATE_COMMAND_CHECK = 2,
};
} // namespace enums } // namespace enums
@@ -831,6 +836,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public: public:
std::string entity_id{}; std::string entity_id{};
std::string attribute{}; std::string attribute{};
bool once{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@@ -838,6 +844,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
protected: protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class HomeAssistantStateResponse : public ProtoMessage { class HomeAssistantStateResponse : public ProtoMessage {
public: public:
@@ -2175,7 +2182,7 @@ class UpdateStateResponse : public ProtoMessage {
class UpdateCommandRequest : public ProtoMessage { class UpdateCommandRequest : public ProtoMessage {
public: public:
uint32_t key{0}; uint32_t key{0};
bool install{false}; enums::UpdateCommand command{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
+10
View File
@@ -359,8 +359,18 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.entity_id = std::move(entity_id), .entity_id = std::move(entity_id),
.attribute = std::move(attribute), .attribute = std::move(attribute),
.callback = std::move(f), .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 { const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_; return this->state_subs_;
} }
+3
View File
@@ -112,10 +112,13 @@ class APIServer : public Component, public Controller {
std::string entity_id; std::string entity_id;
optional<std::string> attribute; optional<std::string> attribute;
std::function<void(std::string)> callback; std::function<void(std::string)> callback;
bool once;
}; };
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); 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<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
+7
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"
+68 -16
View File
@@ -132,10 +132,77 @@ void ATM90E32Component::update() {
this->status_clear_warning(); 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() { void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component..."); ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup(); 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 uint16_t mmode0 = 0x87; // 3P4W 50Hz
if (line_freq_ == 60) { if (line_freq_ == 60) {
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz 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_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_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10% 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 // 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_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current 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 // 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_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current 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 // 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_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain
+20 -7
View File
@@ -1,9 +1,12 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "atm90e32_reg.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.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 esphome {
namespace atm90e32 { namespace atm90e32 {
@@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent,
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void update() override; void update() override;
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } 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_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; } 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_line_freq(int freq) { line_freq_ = freq; }
void set_current_phases(int phases) { current_phases_ = phases; } void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; } 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_voltage_offset_phase(uint8_t /*phase*/);
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/); uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
int32_t last_periodic_millis = millis(); int32_t last_periodic_millis = millis();
protected: protected:
@@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent,
float get_chip_temperature_(); float get_chip_temperature_();
bool get_publish_interval_flag_() { return publish_interval_flag_; }; bool get_publish_interval_flag_() { return publish_interval_flag_; };
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; }; void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
void restore_calibrations_();
struct ATM90E32Phase { struct ATM90E32Phase {
uint16_t voltage_gain_{7305}; uint16_t voltage_gain_{0};
uint16_t ct_gain_{27961}; uint16_t ct_gain_{0};
uint16_t voltage_offset_{0}; uint16_t voltage_offset_{0};
uint16_t current_offset_{0}; uint16_t current_offset_{0};
float voltage_{0}; float voltage_{0};
@@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent,
uint32_t cumulative_reverse_active_energy_{0}; uint32_t cumulative_reverse_active_energy_{0};
} phase_[3]; } 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 *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr}; sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15}; uint16_t pga_gain_{0x15};
int line_freq_{60}; int line_freq_{60};
int current_phases_{3}; int current_phases_{3};
bool publish_interval_flag_{true}; bool publish_interval_flag_{false};
bool peak_current_signed_{false}; bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
}; };
} // namespace atm90e32 } // namespace atm90e32
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <cinttypes>
namespace esphome { namespace esphome {
namespace atm90e32 { namespace atm90e32 {
@@ -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)
@@ -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
@@ -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
+15 -11
View File
@@ -1,21 +1,21 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_APPARENT_POWER,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT, CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_PHASE_A, CONF_PHASE_A,
CONF_PHASE_ANGLE,
CONF_PHASE_B, CONF_PHASE_B,
CONF_PHASE_C, CONF_PHASE_C,
CONF_PHASE_ANGLE,
CONF_POWER, CONF_POWER,
CONF_POWER_FACTOR, CONF_POWER_FACTOR,
CONF_APPARENT_POWER, CONF_REACTIVE_POWER,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REVERSE_ACTIVE_ENERGY, CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
@@ -23,13 +23,13 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC, ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_DEGREES,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_DEGREES,
UNIT_HERTZ, UNIT_HERTZ,
UNIT_VOLT, UNIT_VOLT,
UNIT_VOLT_AMPS_REACTIVE, UNIT_VOLT_AMPS_REACTIVE,
@@ -37,6 +37,8 @@ from esphome.const import (
UNIT_WATT_HOURS, UNIT_WATT_HOURS,
) )
from . import atm90e32_ns
CONF_LINE_FREQUENCY = "line_frequency" CONF_LINE_FREQUENCY = "line_frequency"
CONF_CHIP_TEMPERATURE = "chip_temperature" CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga" CONF_GAIN_PGA = "gain_pga"
@@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct"
CONF_HARMONIC_POWER = "harmonic_power" CONF_HARMONIC_POWER = "harmonic_power"
CONF_PEAK_CURRENT = "peak_current" CONF_PEAK_CURRENT = "peak_current"
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
UNIT_DEG = "degrees" UNIT_DEG = "degrees"
LINE_FREQS = { LINE_FREQS = {
"50HZ": 50, "50HZ": 50,
@@ -61,7 +64,6 @@ PGA_GAINS = {
"4X": 0x2A, "4X": 0x2A,
} }
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
ATM90E32Component = atm90e32_ns.class_( ATM90E32Component = atm90e32_ns.class_(
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice "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_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean, 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")) .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_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) 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_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))
+1 -1
View File
@@ -90,7 +90,7 @@ struct BedjetStatusPacket {
int unused_6 : 1; // 0x4 int unused_6 : 1; // 0x4
bool is_dual_zone : 1; /// Is part of a Dual Zone configuration bool is_dual_zone : 1; /// Is part of a Dual Zone configuration
int unused_7 : 1; // 0x1 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_4 : 8; // Unknown 23-24 = 0x1310
uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310 uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310
@@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput {
void write_state(light::LightState *state) override { void write_state(light::LightState *state) override {
bool binary; bool binary;
state->current_values_as_binary(&binary); state->current_values_as_binary(&binary);
if (binary) if (binary) {
this->output_->turn_on(); this->output_->turn_on();
else } else {
this->output_->turn_off(); this->output_->turn_off();
}
} }
protected: protected:
+5 -5
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 import automation, core
from esphome.automation import Condition, maybe_simple_id from esphome.automation import Condition, maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DELAY, CONF_DELAY,
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
@@ -16,6 +14,7 @@ from esphome.const import (
CONF_INVERTED, CONF_INVERTED,
CONF_MAX_LENGTH, CONF_MAX_LENGTH,
CONF_MIN_LENGTH, CONF_MIN_LENGTH,
CONF_MQTT_ID,
CONF_ON_CLICK, CONF_ON_CLICK,
CONF_ON_DOUBLE_CLICK, CONF_ON_DOUBLE_CLICK,
CONF_ON_MULTI_CLICK, CONF_ON_MULTI_CLICK,
@@ -26,7 +25,6 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TIMING, CONF_TIMING,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_BATTERY_CHARGING,
@@ -59,6 +57,8 @@ from esphome.const import (
DEVICE_CLASS_WINDOW, DEVICE_CLASS_WINDOW,
) )
from esphome.core import CORE, coroutine_with_priority 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 from esphome.util import Registry
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
+4 -4
View File
@@ -1,7 +1,8 @@
import esphome.codegen as cg from esphome import automation
import esphome.config_validation as cv
from esphome.automation import maybe_simple_id 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 ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
@@ -13,7 +14,6 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VALUE, CONF_VALUE,
) )
from esphome import automation
AUTO_LOAD = ["esp32_ble_client"] AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"] CODEOWNERS = ["@buxtronix", "@clydebarrow"]
@@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ble_client, esp32_ble_tracker, output 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 esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns from .. import ble_client_ns
@@ -1,17 +1,18 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]
@@ -1,7 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, switch
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import switch, ble_client
from esphome.const import ICON_BLUETOOTH from esphome.const import ICON_BLUETOOTH
from .. import ble_client_ns from .. import ble_client_ns
BLEClientSwitch = ble_client_ns.class_( BLEClientSwitch = ble_client_ns.class_(
@@ -1,13 +1,14 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
CONF_TRIGGER_ID,
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]
@@ -1,13 +1,13 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, esp32_ble_tracker from esphome.components import binary_sensor, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_MAC_ADDRESS,
CONF_MIN_RSSI, CONF_MIN_RSSI,
CONF_SERVICE_UUID,
CONF_TIMEOUT, CONF_TIMEOUT,
) )
+2 -2
View File
@@ -1,12 +1,12 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_SERVICE_UUID,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,
@@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
DEPENDENCIES = ["esp32_ble_tracker"] DEPENDENCIES = ["esp32_ble_tracker"]
@@ -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 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 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"] AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"] DEPENDENCIES = ["api", "esp32"]
+196
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

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