Merge branch 'dev' into add-cse-7766-reactive-power

This commit is contained in:
nkinnan 2024-09-18 09:27:52 -07:00 committed by GitHub
commit 1e9c478f71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
217 changed files with 5747 additions and 785 deletions

View file

@ -47,6 +47,9 @@ 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.7.0 uses: docker/build-push-action@v6.7.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -70,6 +73,9 @@ 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.7.0 uses: docker/build-push-action@v6.7.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile

91
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,91 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
workflow_dispatch:
schedule:
- cron: "30 18 * * 4"
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# - language: c-cpp
# build-mode: autobuild
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View file

@ -65,7 +65,7 @@ jobs:
pip3 install build pip3 install build
python3 -m build python3 -m build
- name: Publish - name: Publish
uses: pypa/gh-action-pypi-publish@v1.9.0 uses: pypa/gh-action-pypi-publish@v1.10.1
deploy-docker: deploy-docker:
name: Build ESPHome ${{ matrix.platform }} name: Build ESPHome ${{ matrix.platform }}
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.3.4 uses: actions/upload-artifact@v4.4.0
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests

View file

@ -36,7 +36,7 @@ jobs:
python ./script/sync-device_class.py python ./script/sync-device_class.py
- name: Commit changes - name: Commit changes
uses: peter-evans/create-pull-request@v6.1.0 uses: peter-evans/create-pull-request@v7.0.3
with: with:
commit-message: "Synchronise Device Classes from Home Assistant" commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com> committer: esphomebot <esphome@nabucasa.com>

View file

@ -61,7 +61,7 @@ esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias- esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas esphome/components/bl0942/* @dbuezas @dwmw2
esphome/components/ble_client/* @buxtronix @clydebarrow esphome/components/ble_client/* @buxtronix @clydebarrow
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme280_base/* @esphome/core esphome/components/bme280_base/* @esphome/core
@ -70,6 +70,9 @@ esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bme68x_bsec2/* @kbx81 @neffs esphome/components/bme68x_bsec2/* @kbx81 @neffs
esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs esphome/components/bme68x_bsec2_i2c/* @kbx81 @neffs
esphome/components/bmi160/* @flaviut esphome/components/bmi160/* @flaviut
esphome/components/bmp280_base/* @ademuri
esphome/components/bmp280_i2c/* @ademuri
esphome/components/bmp280_spi/* @ademuri
esphome/components/bmp3xx/* @latonita esphome/components/bmp3xx/* @latonita
esphome/components/bmp3xx_base/* @latonita @martgras esphome/components/bmp3xx_base/* @latonita @martgras
esphome/components/bmp3xx_i2c/* @latonita esphome/components/bmp3xx_i2c/* @latonita
@ -83,6 +86,7 @@ esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @jesterret
esphome/components/climate/* @esphome/core esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz esphome/components/color_temperature/* @jesserockz
@ -223,6 +227,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core 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/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber esphome/components/m5stack_8angle/* @rnauber
@ -284,6 +289,7 @@ 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/online_image/* @guillempages
esphome/components/opentherm/* @olegtarasov
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
@ -384,6 +390,7 @@ esphome/components/st7701s/* @clydebarrow
esphome/components/st7735/* @SenexCrenshaw esphome/components/st7735/* @SenexCrenshaw
esphome/components/st7789v/* @kbx81 esphome/components/st7789v/* @kbx81
esphome/components/st7920/* @marsjan155 esphome/components/st7920/* @marsjan155
esphome/components/statsd/* @Links2004
esphome/components/substitutions/* @esphome/core esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931 esphome/components/sun_gtil2/* @Mat931

View file

@ -33,9 +33,9 @@ RUN \
python3-venv=3.11.2-1+b1 \ python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \ python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \ iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \ git=1:2.39.5-0+deb12u1 \
curl=7.88.1-10+deb12u6 \ curl=7.88.1-10+deb12u7 \
openssh-client=1:9.2p1-2+deb12u2 \ openssh-client=1:9.2p1-2+deb12u3 \
python3-cffi=1.15.1-5 \ python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \ libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \ libmagic1=1:5.44-3 \
@ -49,7 +49,7 @@ RUN \
zlib1g-dev=1:1.2.13.dfsg-1 \ zlib1g-dev=1:1.2.13.dfsg-1 \
libjpeg-dev=1:2.1.5-2 \ libjpeg-dev=1:2.1.5-2 \
libfreetype-dev=2.12.1+dfsg-5+deb12u3 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
libssl-dev=3.0.13-1~deb12u1 \ libssl-dev=3.0.14-1~deb12u2 \
libffi-dev=3.4.4-1 \ libffi-dev=3.4.4-1 \
libopenjp2-7=2.5.0-2 \ libopenjp2-7=2.5.0-2 \
libtiff6=4.5.0-6+deb12u1 \ libtiff6=4.5.0-6+deb12u1 \
@ -96,14 +96,19 @@ RUN \
# First install requirements to leverage caching when requirements don't change # First install requirements to leverage caching when requirements don't change
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719 # tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / COPY requirements.txt requirements_optional.txt /
RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \ curl -L https://www.piwheels.org/cp311/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl -o /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& pip3 install --break-system-packages --no-cache-dir /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& rm /tmp/cryptography-43.0.0-cp37-abi3-linux_armv7l.whl \
&& export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \ fi; \
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \ CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ --break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt
&& /platformio_install_deps.py /platformio.ini --libraries
COPY script/platformio_install_deps.py platformio.ini /
RUN /platformio_install_deps.py /platformio.ini --libraries
# Avoid unsafe git error when container user and file config volume permissions don't match # Avoid unsafe git error when container user and file config volume permissions don't match
RUN git config --system --add safe.directory '*' RUN git config --system --add safe.directory '*'

View file

@ -1,26 +1,26 @@
import logging import logging
from esphome import automation, core from esphome import automation, core
import esphome.codegen as cg
from esphome.components import font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import ( from esphome.components.image import (
CONF_USE_TRANSPARENCY, CONF_USE_TRANSPARENCY,
LOCAL_SCHEMA, LOCAL_SCHEMA,
WEB_SCHEMA,
SOURCE_WEB,
SOURCE_LOCAL, SOURCE_LOCAL,
SOURCE_WEB,
WEB_SCHEMA,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
CONF_FILE, CONF_FILE,
CONF_ID, CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID, CONF_RAW_DATA_ID,
CONF_REPEAT, CONF_REPEAT,
CONF_RESIZE, CONF_RESIZE,
CONF_TYPE,
CONF_SOURCE, CONF_SOURCE,
CONF_PATH, CONF_TYPE,
CONF_URL, CONF_URL,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
@ -172,6 +172,9 @@ async def to_code(config):
path = CORE.relative_config_path(conf_file[CONF_PATH]) path = CORE.relative_config_path(conf_file[CONF_PATH])
elif conf_file[CONF_SOURCE] == SOURCE_WEB: elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = espImage.compute_local_image_path(conf_file).as_posix() path = espImage.compute_local_image_path(conf_file).as_posix()
else:
raise core.EsphomeError(f"Unknown animation source: {conf_file[CONF_SOURCE]}")
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:
@ -183,13 +186,12 @@ async def to_code(config):
new_width_max, new_height_max = config[CONF_RESIZE] new_width_max, new_height_max = config[CONF_RESIZE]
ratio = min(new_width_max / width, new_height_max / height) ratio = min(new_width_max / width, new_height_max / height)
width, height = int(width * ratio), int(height * ratio) width, height = int(width * ratio), int(height * ratio)
else: elif width > 500 or height > 500:
if width > 500 or height > 500: _LOGGER.warning(
_LOGGER.warning( 'The image "%s" you requested is very big. Please consider'
'The image "%s" you requested is very big. Please consider' " using the resize parameter.",
" using the resize parameter.", path,
path, )
)
transparent = config[CONF_USE_TRANSPARENCY] transparent = config[CONF_USE_TRANSPARENCY]
@ -306,6 +308,8 @@ async def to_code(config):
if transparent: if transparent:
alpha = image.split()[-1] alpha = image.split()[-1]
has_alpha = alpha.getextrema()[0] < 0xFF has_alpha = alpha.getextrema()[0] < 0xFF
else:
has_alpha = False
frame = image.convert("1", dither=Image.Dither.NONE) frame = image.convert("1", dither=Image.Dither.NONE)
if CONF_RESIZE in config: if CONF_RESIZE in config:
frame = frame.resize([width, height]) frame = frame.resize([width, height])

View file

@ -62,6 +62,8 @@ service APIConnection {
rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {}
rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {}
rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {}
rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {}
} }
@ -1112,13 +1114,13 @@ enum MediaPlayerFormatPurpose {
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1; MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1;
} }
message MediaPlayerSupportedFormat { message MediaPlayerSupportedFormat {
option (id) = 119;
option (ifdef) = "USE_MEDIA_PLAYER"; option (ifdef) = "USE_MEDIA_PLAYER";
string format = 1; string format = 1;
uint32 sample_rate = 2; uint32 sample_rate = 2;
uint32 num_channels = 3; uint32 num_channels = 3;
MediaPlayerFormatPurpose purpose = 4; MediaPlayerFormatPurpose purpose = 4;
uint32 sample_bytes = 5;
} }
message ListEntitiesMediaPlayerResponse { message ListEntitiesMediaPlayerResponse {
option (id) = 63; option (id) = 63;
@ -1554,6 +1556,53 @@ message VoiceAssistantTimerEventResponse {
bool is_active = 6; bool is_active = 6;
} }
message VoiceAssistantAnnounceRequest {
option (id) = 119;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
string media_id = 1;
string text = 2;
}
message VoiceAssistantAnnounceFinished {
option (id) = 120;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VOICE_ASSISTANT";
bool success = 1;
}
message VoiceAssistantWakeWord {
string id = 1;
string wake_word = 2;
repeated string trained_languages = 3;
}
message VoiceAssistantConfigurationRequest {
option (id) = 121;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
}
message VoiceAssistantConfigurationResponse {
option (id) = 122;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2;
uint32 max_active_wake_words = 3;
}
message VoiceAssistantSetConfiguration {
option (id) = 123;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
repeated string active_wake_words = 1;
}
// ==================== ALARM CONTROL PANEL ==================== // ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState { enum AlarmControlPanelState {
ALARM_STATE_DISARMED = 0; ALARM_STATE_DISARMED = 0;

View file

@ -1032,6 +1032,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
media_format.sample_rate = supported_format.sample_rate; media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels; media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose); media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
media_format.sample_bytes = supported_format.sample_bytes;
msg.supported_formats.push_back(media_format); msg.supported_formats.push_back(media_format);
} }
@ -1213,6 +1214,49 @@ void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistant
} }
}; };
void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_announce(msg);
}
}
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp;
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return resp;
}
auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word;
resp_wake_word.id = wake_word.id;
resp_wake_word.wake_word = wake_word.wake_word;
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
resp.available_wake_words.push_back(std::move(resp_wake_word));
}
resp.max_active_wake_words = config.max_active_wake_words;
}
return resp;
}
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
}
}
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL

View file

@ -151,6 +151,10 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) override;
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL

View file

@ -5149,6 +5149,10 @@ bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt va
this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>(); this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>();
return true; return true;
} }
case 5: {
this->sample_bytes = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
@ -5168,6 +5172,7 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(2, this->sample_rate);
buffer.encode_uint32(3, this->num_channels); buffer.encode_uint32(3, this->num_channels);
buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose); buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose);
buffer.encode_uint32(5, this->sample_bytes);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void MediaPlayerSupportedFormat::dump_to(std::string &out) const { void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
@ -5190,6 +5195,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
out.append(" purpose: "); out.append(" purpose: ");
out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose)); out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose));
out.append("\n"); out.append("\n");
out.append(" sample_bytes: ");
sprintf(buffer, "%" PRIu32, this->sample_bytes);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -7061,6 +7071,193 @@ void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->media_id = value.as_string();
return true;
}
case 2: {
this->text = value.as_string();
return true;
}
default:
return false;
}
}
void VoiceAssistantAnnounceRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->media_id);
buffer.encode_string(2, this->text);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantAnnounceRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantAnnounceRequest {\n");
out.append(" media_id: ");
out.append("'").append(this->media_id).append("'");
out.append("\n");
out.append(" text: ");
out.append("'").append(this->text).append("'");
out.append("\n");
out.append("}");
}
#endif
bool VoiceAssistantAnnounceFinished::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->success = value.as_bool();
return true;
}
default:
return false;
}
}
void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); }
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantAnnounceFinished {\n");
out.append(" success: ");
out.append(YESNO(this->success));
out.append("\n");
out.append("}");
}
#endif
bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->id = value.as_string();
return true;
}
case 2: {
this->wake_word = value.as_string();
return true;
}
case 3: {
this->trained_languages.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->id);
buffer.encode_string(2, this->wake_word);
for (auto &it : this->trained_languages) {
buffer.encode_string(3, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantWakeWord::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantWakeWord {\n");
out.append(" id: ");
out.append("'").append(this->id).append("'");
out.append("\n");
out.append(" wake_word: ");
out.append("'").append(this->wake_word).append("'");
out.append("\n");
for (const auto &it : this->trained_languages) {
out.append(" trained_languages: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const {
out.append("VoiceAssistantConfigurationRequest {}");
}
#endif
bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->max_active_wake_words = value.as_uint32();
return true;
}
default:
return false;
}
}
bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->available_wake_words.push_back(value.as_message<VoiceAssistantWakeWord>());
return true;
}
case 2: {
this->active_wake_words.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->available_wake_words) {
buffer.encode_message<VoiceAssistantWakeWord>(1, it, true);
}
for (auto &it : this->active_wake_words) {
buffer.encode_string(2, it, true);
}
buffer.encode_uint32(3, this->max_active_wake_words);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantConfigurationResponse {\n");
for (const auto &it : this->available_wake_words) {
out.append(" available_wake_words: ");
it.dump_to(out);
out.append("\n");
}
for (const auto &it : this->active_wake_words) {
out.append(" active_wake_words: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append(" max_active_wake_words: ");
sprintf(buffer, "%" PRIu32, this->max_active_wake_words);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->active_wake_words.push_back(value.as_string());
return true;
}
default:
return false;
}
}
void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const {
for (auto &it : this->active_wake_words) {
buffer.encode_string(1, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantSetConfiguration::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantSetConfiguration {\n");
for (const auto &it : this->active_wake_words) {
out.append(" active_wake_words: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 6: { case 6: {

View file

@ -1277,6 +1277,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
uint32_t sample_rate{0}; uint32_t sample_rate{0};
uint32_t num_channels{0}; uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{}; enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0};
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;
@ -1825,6 +1826,76 @@ class VoiceAssistantTimerEventResponse : public ProtoMessage {
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; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantAnnounceRequest : public ProtoMessage {
public:
std::string media_id{};
std::string text{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class VoiceAssistantAnnounceFinished : public ProtoMessage {
public:
bool success{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantWakeWord : public ProtoMessage {
public:
std::string id{};
std::string wake_word{};
std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class VoiceAssistantConfigurationRequest : public ProtoMessage {
public:
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
};
class VoiceAssistantConfigurationResponse : public ProtoMessage {
public:
std::vector<VoiceAssistantWakeWord> available_wake_words{};
std::vector<std::string> active_wake_words{};
uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantSetConfiguration : public ProtoMessage {
public:
std::vector<std::string> active_wake_words{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};

View file

@ -311,14 +311,6 @@ bool APIServerConnectionBase::send_list_entities_button_response(const ListEntit
#ifdef USE_BUTTON #ifdef USE_BUTTON
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool APIServerConnectionBase::send_media_player_supported_format(const MediaPlayerSupportedFormat &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_media_player_supported_format: %s", msg.dump().c_str());
#endif
return this->send_message_<MediaPlayerSupportedFormat>(msg, 119);
}
#endif
#ifdef USE_MEDIA_PLAYER
bool APIServerConnectionBase::send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg) { bool APIServerConnectionBase::send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_media_player_response: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "send_list_entities_media_player_response: %s", msg.dump().c_str());
@ -494,6 +486,29 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
#endif #endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_announce_finished: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantAnnounceFinished>(msg, 120);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_configuration_response(
const VoiceAssistantConfigurationResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantConfigurationResponse>(msg, 122);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) { const ListEntitiesAlarmControlPanelResponse &msg) {
@ -1147,13 +1162,35 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break; break;
} }
case 119: { case 119: {
#ifdef USE_MEDIA_PLAYER #ifdef USE_VOICE_ASSISTANT
MediaPlayerSupportedFormat msg; VoiceAssistantAnnounceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_media_player_supported_format: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
#endif #endif
this->on_media_player_supported_format(msg); this->on_voice_assistant_announce_request(msg);
#endif
break;
}
case 121: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_configuration_request(msg);
#endif
break;
}
case 123: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantSetConfiguration msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_set_configuration(msg);
#endif #endif
break; break;
} }
@ -1644,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
this->subscribe_voice_assistant(msg); this->subscribe_voice_assistant(msg);
} }
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg);
if (!this->send_voice_assistant_configuration_response(ret)) {
this->on_fatal_error();
}
}
#endif
#ifdef USE_VOICE_ASSISTANT
void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->voice_assistant_set_configuration(msg);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) {
if (!this->is_connection_setup()) { if (!this->is_connection_setup()) {

View file

@ -145,10 +145,6 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_BUTTON #ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){}; virtual void on_button_command_request(const ButtonCommandRequest &value){};
#endif #endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_supported_format(const MediaPlayerSupportedFormat &msg);
virtual void on_media_player_supported_format(const MediaPlayerSupportedFormat &value){};
#endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg); bool send_list_entities_media_player_response(const ListEntitiesMediaPlayerResponse &msg);
#endif #endif
@ -251,6 +247,21 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){}; virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg);
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif #endif
@ -423,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) = 0;
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0;
#endif #endif
@ -524,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
#endif #endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
#endif
#ifdef USE_VOICE_ASSISTANT
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
#endif #endif

View file

@ -1,13 +1,13 @@
# Dummy integration to allow relying on AsyncTCP # Dummy integration to allow relying on AsyncTCP
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority
from esphome.const import ( from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/esphome/AsyncTCP/blob/master/library.json # https://github.com/esphome/AsyncTCP/blob/master/library.json
cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") cg.add_library("esphome/AsyncTCP-esphome", "2.1.4")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/esphome/ESPAsyncTCP # https://github.com/esphome/ESPAsyncTCP
cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")

View file

@ -1 +1 @@
CODEOWNERS = ["@dbuezas"] CODEOWNERS = ["@dbuezas", "@dwmw2"]

View file

@ -41,20 +41,33 @@ static const uint32_t BL0942_REG_MODE_DEFAULT =
static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a; static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a;
static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55; static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55;
// 23-byte packet, 11 bits per byte, 2400 baud: about 105ms
static const uint32_t PKT_TIMEOUT_MS = 200;
void BL0942::loop() { void BL0942::loop() {
DataPacket buffer; DataPacket buffer;
if (!this->available()) { int avail = this->available();
if (!avail) {
return; return;
} }
if (avail < sizeof(buffer)) {
if (!this->rx_start_) {
this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
this->read_array((uint8_t *) &buffer, avail);
this->rx_start_ = 0;
}
return;
}
if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) { if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (this->validate_checksum_(&buffer)) { if (this->validate_checksum_(&buffer)) {
this->received_package_(&buffer); this->received_package_(&buffer);
} }
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
} }
this->rx_start_ = 0;
} }
bool BL0942::validate_checksum_(DataPacket *data) { bool BL0942::validate_checksum_(DataPacket *data) {
@ -109,8 +122,23 @@ void BL0942::update() {
} }
void BL0942::setup() { void BL0942::setup() {
// If either current or voltage references are set explicitly by the user,
// calculate the power reference from it unless that is also explicitly set.
if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
this->power_reference_set_ = true;
}
// Similarly for energy reference, if the power reference was set by the user
// either implicitly or explicitly.
if (this->power_reference_set_ && !this->energy_reference_set_) {
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
this->energy_reference_set_ = true;
}
this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC); this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC); if (this->reset_)
this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
uint32_t mode = BL0942_REG_MODE_DEFAULT; uint32_t mode = BL0942_REG_MODE_DEFAULT;
mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */ mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */
@ -133,10 +161,17 @@ void BL0942::received_package_(DataPacket *data) {
return; return;
} }
// cf_cnt is only 24 bits, so track overflows
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
if (cf_cnt < this->prev_cf_cnt_) {
cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float v_rms = (uint24_t) data->v_rms / voltage_reference_; float v_rms = (uint24_t) data->v_rms / voltage_reference_;
float i_rms = (uint24_t) data->i_rms / current_reference_; float i_rms = (uint24_t) data->i_rms / current_reference_;
float watt = (int24_t) data->watt / power_reference_; float watt = (int24_t) data->watt / power_reference_;
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
float total_energy_consumption = cf_cnt / energy_reference_; float total_energy_consumption = cf_cnt / energy_reference_;
float frequency = 1000000.0f / data->frequency; float frequency = 1000000.0f / data->frequency;
@ -162,13 +197,18 @@ void BL0942::received_package_(DataPacket *data) {
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0942:"); ESP_LOGCONFIG(TAG, "BL0942:");
ESP_LOGCONFIG(TAG, " Reset: %s", TRUEFALSE(this->reset_));
ESP_LOGCONFIG(TAG, " Address: %d", this->address_); ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_); ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_);
ESP_LOGCONFIG(TAG, " Energy reference: %f", this->energy_reference_);
ESP_LOGCONFIG(TAG, " Power reference: %f", this->power_reference_);
ESP_LOGCONFIG(TAG, " Voltage reference: %f", this->voltage_reference_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_); LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_); LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_); LOG_SENSOR("", "Power", this->power_sensor_);
LOG_SENSOR("", "Energy", this->energy_sensor_); LOG_SENSOR("", "Energy", this->energy_sensor_);
LOG_SENSOR("", "frequency", this->frequency_sensor_); LOG_SENSOR("", "Frequency", this->frequency_sensor_);
} }
} // namespace bl0942 } // namespace bl0942

View file

@ -8,6 +8,57 @@
namespace esphome { namespace esphome {
namespace bl0942 { namespace bl0942 {
// The BL0942 IC is "calibration-free", which means that it doesn't care
// at all about calibration, and that's left to software. It measures a
// voltage differential on its IP/IN pins which linearly proportional to
// the current flow, and another on its VP pin which is proportional to
// the line voltage. It never knows the actual calibration; the values
// it reports are solely in terms of those inputs.
//
// The datasheet refers to the input voltages as I(A) and V(V), both
// in millivolts. It measures them against a reference voltage Vref,
// which is typically 1.218V (but that absolute value is meaningless
// without the actual calibration anyway).
//
// The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS
// value is 73989 V(V)/Vref. So we can calibrate those by applying a
// simple meter with a resistive load.
//
// The chip also measures the phase difference between voltage and
// current, and uses it to calculate the power factor (cos φ). It
// reports the WATT value of 3537 * I_RMS * V_RMS * cos φ).
//
// It also integrates total energy based on the WATT value. The time for
// one CF_CNT pulse is 1638.4*256 / WATT.
//
// So... how do we calibrate that?
//
// Using a simple resistive load and an external meter, we can measure
// the true voltage and current for a given V_RMS and I_RMS reading,
// to calculate BL0942_UREF and BL0942_IREF. Those are in units of
// "305978 counts per amp" or "73989 counts per volt" respectively.
//
// We can derive BL0942_PREF from those. Let's eliminate the weird
// factors and express the calibration in plain counts per volt/amp:
// UREF1 = UREF/73989, IREF1 = IREF/305978.
//
// Next... the true power in Watts is V * I * cos φ, so that's equal
// to WATT/3537 * IREF1 * UREF1. Which means
// BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989.
//
// Finally the accumulated energy. The period of a CF_CNT count is
// 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means
// the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds.
// Factoring in the calibration, that's 419230.4 / BL0942_PREF actual
// Watt-seconds (or Joules, as the physicists like to call them).
//
// But we're not being physicists today; we we're being engineers, so
// we want to convert to kWh instead. Which we do by dividing by 1000
// and then by 3600, so the energy in kWh is
// CF_CNT * 419230.4 / BL0942_PREF / 3600000
//
// Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4
static const float BL0942_PREF = 596; // taken from tasmota static const float BL0942_PREF = 596; // taken from tasmota
static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218 static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
static const float BL0942_IREF = 251213.46469622; // 305978/1.218 static const float BL0942_IREF = 251213.46469622; // 305978/1.218
@ -42,6 +93,23 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; } void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; }
void set_address(uint8_t address) { this->address_ = address; } void set_address(uint8_t address) { this->address_ = address; }
void set_reset(bool reset) { this->reset_ = reset; }
void set_current_reference(float current_ref) {
this->current_reference_ = current_ref;
this->current_reference_set_ = true;
}
void set_energy_reference(float energy_ref) {
this->energy_reference_ = energy_ref;
this->energy_reference_set_ = true;
}
void set_power_reference(float power_ref) {
this->power_reference_ = power_ref;
this->power_reference_set_ = true;
}
void set_voltage_reference(float voltage_ref) {
this->voltage_reference_ = voltage_ref;
this->voltage_reference_set_ = true;
}
void loop() override; void loop() override;
void update() override; void update() override;
@ -59,14 +127,21 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
// Divide by this to turn into Watt // Divide by this to turn into Watt
float power_reference_ = BL0942_PREF; float power_reference_ = BL0942_PREF;
bool power_reference_set_ = false;
// Divide by this to turn into Volt // Divide by this to turn into Volt
float voltage_reference_ = BL0942_UREF; float voltage_reference_ = BL0942_UREF;
bool voltage_reference_set_ = false;
// Divide by this to turn into Ampere // Divide by this to turn into Ampere
float current_reference_ = BL0942_IREF; float current_reference_ = BL0942_IREF;
bool current_reference_set_ = false;
// Divide by this to turn into kWh // Divide by this to turn into kWh
float energy_reference_ = BL0942_EREF; float energy_reference_ = BL0942_EREF;
bool energy_reference_set_ = false;
uint8_t address_ = 0; uint8_t address_ = 0;
bool reset_ = false;
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ; LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
uint32_t rx_start_ = 0;
uint32_t prev_cf_cnt_ = 0;
bool validate_checksum_(DataPacket *data); bool validate_checksum_(DataPacket *data);
int read_reg_(uint8_t reg); int read_reg_(uint8_t reg);

View file

@ -24,6 +24,12 @@ from esphome.const import (
UNIT_WATT, UNIT_WATT,
) )
CONF_CURRENT_REFERENCE = "current_reference"
CONF_ENERGY_REFERENCE = "energy_reference"
CONF_POWER_REFERENCE = "power_reference"
CONF_RESET = "reset"
CONF_VOLTAGE_REFERENCE = "voltage_reference"
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
bl0942_ns = cg.esphome_ns.namespace("bl0942") bl0942_ns = cg.esphome_ns.namespace("bl0942")
@ -53,19 +59,19 @@ CONFIG_SCHEMA = (
), ),
cv.Optional(CONF_POWER): sensor.sensor_schema( cv.Optional(CONF_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT, unit_of_measurement=UNIT_WATT,
accuracy_decimals=0, accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_ENERGY): sensor.sensor_schema( cv.Optional(CONF_ENERGY): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOWATT_HOURS, unit_of_measurement=UNIT_KILOWATT_HOURS,
accuracy_decimals=0, accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY, device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING, state_class=STATE_CLASS_TOTAL_INCREASING,
), ),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ, unit_of_measurement=UNIT_HERTZ,
accuracy_decimals=0, accuracy_decimals=2,
device_class=DEVICE_CLASS_FREQUENCY, device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
@ -77,6 +83,11 @@ CONFIG_SCHEMA = (
), ),
), ),
cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3), cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3),
cv.Optional(CONF_RESET, default=True): cv.boolean,
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -106,3 +117,12 @@ async def to_code(config):
cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_frequency_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_address(config[CONF_ADDRESS])) cg.add(var.set_address(config[CONF_ADDRESS]))
cg.add(var.set_reset(config[CONF_RESET]))
if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None:
cg.add(var.set_current_reference(current_reference))
if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None:
cg.add(var.set_voltage_reference(voltage_reference))
if (power_reference := config.get(CONF_POWER_REFERENCE, None)) is not None:
cg.add(var.set_power_reference(power_reference))
if (energy_reference := config.get(CONF_ENERGY_REFERENCE, None)) is not None:
cg.add(var.set_energy_reference(energy_reference))

View file

@ -65,9 +65,7 @@ CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
CONF_AUTO_CONNECT = "auto_connect" CONF_AUTO_CONNECT = "auto_connect"
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so MULTI_CONF = True
# enforce this in yaml checks.
MULTI_CONF = 3
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(

View file

@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t,
cv.Optional(CONF_IBEACON_UUID): cv.uuid, cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period,
cv.Optional(CONF_MIN_RSSI): cv.All( cv.Optional(CONF_MIN_RSSI): cv.All(
cv.decibel, cv.int_range(min=-100, max=-30) cv.decibel, cv.int_range(min=-100, max=-30)
@ -83,7 +83,7 @@ async def to_code(config):
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if ibeacon_uuid := config.get(CONF_IBEACON_UUID): if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid)
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:

View file

@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
} }
resp.advertisements.push_back(std::move(adv)); resp.advertisements.push_back(std::move(adv));
ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
} }
ESP_LOGV(TAG, "Proxying %d packets", count); ESP_LOGV(TAG, "Proxying %d packets", count);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
void BluetoothProxy::dump_config() { void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_)); ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size());
ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_));
} }
int BluetoothProxy::get_bluetooth_connections_free() { int BluetoothProxy::get_bluetooth_connections_free() {

View file

@ -1,96 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import ( CONFIG_SCHEMA = cv.invalid(
CONF_ID, "The bmp280 sensor component has been renamed to bmp280_i2c."
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
) )
DEPENDENCIES = ["i2c"]
bmp280_ns = cg.esphome_ns.namespace("bmp280")
BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
"1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X,
"2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X,
"4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X,
"8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X,
"16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X,
}
BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
"2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X,
"4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X,
"8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X,
"16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X,
}
BMP280Component = bmp280_ns.class_(
"BMP280Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BMP280Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x77))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View file

@ -0,0 +1,88 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_HECTOPASCAL,
)
CODEOWNERS = ["@ademuri"]
bmp280_ns = cg.esphome_ns.namespace("bmp280_base")
BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
"1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X,
"2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X,
"4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X,
"8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X,
"16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X,
}
BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
"2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X,
"4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X,
"8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X,
"16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X,
}
CONFIG_SCHEMA_BASE = cv.Schema(
{
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_HECTOPASCAL,
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
).extend(cv.polling_component_schema("60s"))
async def to_code_base(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
return var

View file

@ -1,9 +1,9 @@
#include "bmp280.h" #include "bmp280_base.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace bmp280 { namespace bmp280_base {
static const char *const TAG = "bmp280.sensor"; static const char *const TAG = "bmp280.sensor";
@ -59,6 +59,14 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) {
void BMP280Component::setup() { void BMP280Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up BMP280..."); ESP_LOGCONFIG(TAG, "Setting up BMP280...");
uint8_t chip_id = 0; uint8_t chip_id = 0;
// Read the chip id twice, to work around a bug where the first read is 0.
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
if (!this->read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (!this->read_byte(0xD0, &chip_id)) { if (!this->read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED; this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed(); this->mark_failed();
@ -122,7 +130,6 @@ void BMP280Component::setup() {
} }
void BMP280Component::dump_config() { void BMP280Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMP280:"); ESP_LOGCONFIG(TAG, "BMP280:");
LOG_I2C_DEVICE(this);
switch (this->error_code_) { switch (this->error_code_) {
case COMMUNICATION_FAILED: case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with BMP280 failed!"); ESP_LOGE(TAG, "Communication with BMP280 failed!");
@ -262,5 +269,5 @@ uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
} }
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); } int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
} // namespace bmp280 } // namespace bmp280_base
} // namespace esphome } // namespace esphome

View file

@ -2,10 +2,9 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome { namespace esphome {
namespace bmp280 { namespace bmp280_base {
/// Internal struct storing the calibration values of an BMP280. /// Internal struct storing the calibration values of an BMP280.
struct BMP280CalibrationData { struct BMP280CalibrationData {
@ -50,8 +49,8 @@ enum BMP280IIRFilter {
BMP280_IIR_FILTER_16X = 0b100, BMP280_IIR_FILTER_16X = 0b100,
}; };
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor. /// This class implements support for the BMP280 Temperature+Pressure sensor.
class BMP280Component : public PollingComponent, public i2c::I2CDevice { class BMP280Component : public PollingComponent {
public: public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
@ -68,6 +67,11 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice {
float get_setup_priority() const override; float get_setup_priority() const override;
void update() override; void update() override;
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
protected: protected:
/// Read the temperature value and store the calculated ambient temperature in t_fine. /// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine); float read_temperature_(int32_t *t_fine);
@ -90,5 +94,5 @@ class BMP280Component : public PollingComponent, public i2c::I2CDevice {
} error_code_{NONE}; } error_code_{NONE};
}; };
} // namespace bmp280 } // namespace bmp280_base
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,27 @@
#include "bmp280_i2c.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bmp280_i2c {
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
return I2CDevice::read_byte(a_register, data);
};
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
return I2CDevice::write_byte(a_register, data);
};
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
return I2CDevice::read_bytes(a_register, data, len);
};
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
return I2CDevice::read_byte_16(a_register, data);
};
void BMP280I2CComponent::dump_config() {
LOG_I2C_DEVICE(this);
BMP280Component::dump_config();
}
} // namespace bmp280_i2c
} // namespace esphome

View file

@ -0,0 +1,22 @@
#pragma once
#include "esphome/components/bmp280_base/bmp280_base.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace bmp280_i2c {
static const char *const TAG = "bmp280_i2c.sensor";
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
public:
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
void dump_config() override;
};
} // namespace bmp280_i2c
} // namespace esphome

View file

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE
AUTO_LOAD = ["bmp280_base"]
CODEOWNERS = ["@ademuri"]
DEPENDENCIES = ["i2c"]
bmp280_ns = cg.esphome_ns.namespace("bmp280_i2c")
BMP280I2CComponent = bmp280_ns.class_(
"BMP280I2CComponent", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
i2c.i2c_device_schema(default_address=0x77)
).extend({cv.GenerateID(): cv.declare_id(BMP280I2CComponent)})
async def to_code(config):
var = await to_code_base(config)
await i2c.register_i2c_device(var, config)

View file

@ -0,0 +1,65 @@
#include <cstdint>
#include <cstddef>
#include "bmp280_spi.h"
#include <esphome/components/bmp280_base/bmp280_base.h>
namespace esphome {
namespace bmp280_spi {
uint8_t set_bit(uint8_t num, uint8_t position) {
uint8_t mask = 1 << position;
return num | mask;
}
uint8_t clear_bit(uint8_t num, uint8_t position) {
uint8_t mask = 1 << position;
return num & ~mask;
}
void BMP280SPIComponent::setup() {
this->spi_setup();
BMP280Component::setup();
};
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used
// and replaced by a read/write bit (RW = 0 for write and RW = 1 for read).
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
*data = this->transfer_byte(0);
this->disable();
return true;
}
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
this->enable();
this->transfer_byte(clear_bit(a_register, 7));
this->transfer_byte(data);
this->disable();
return true;
}
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
this->read_array(data, len);
this->disable();
return true;
}
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0);
((uint8_t *) data)[0] = this->transfer_byte(0);
this->disable();
return true;
}
} // namespace bmp280_spi
} // namespace esphome

View file

@ -0,0 +1,20 @@
#pragma once
#include "esphome/components/bmp280_base/bmp280_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace bmp280_spi {
class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
void setup() override;
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
};
} // namespace bmp280_spi
} // namespace esphome

View file

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi
from ..bmp280_base import to_code_base, CONFIG_SCHEMA_BASE
AUTO_LOAD = ["bmp280_base"]
CODEOWNERS = ["@ademuri"]
DEPENDENCIES = ["spi"]
bmp280_ns = cg.esphome_ns.namespace("bmp280_spi")
BMP280SPIComponent = bmp280_ns.class_(
"BMP280SPIComponent", cg.PollingComponent, spi.SPIDevice
)
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
spi.spi_device_schema(default_mode="mode3")
).extend({cv.GenerateID(): cv.declare_id(BMP280SPIComponent)})
async def to_code(config):
var = await to_code_base(config)
await spi.register_spi_device(var, config)

View file

@ -0,0 +1,67 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_RESTORE_VALUE,
)
CODEOWNERS = ["@jesterret"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
ch422g_ns = cg.esphome_ns.namespace("ch422g")
CH422GComponent = ch422g_ns.class_("CH422GComponent", cg.Component, i2c.I2CDevice)
CH422GGPIOPin = ch422g_ns.class_(
"CH422GGPIOPin", cg.GPIOPin, cg.Parented.template(CH422GComponent)
)
CONF_CH422G = "ch422g"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(CH422GComponent),
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x24))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
CH422G_PIN_SCHEMA = pins.gpio_base_schema(
CH422GGPIOPin,
cv.int_range(min=0, max=7),
modes=[CONF_INPUT, CONF_OUTPUT],
).extend(
{
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
}
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA)
async def ch422g_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_CH422G])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,122 @@
#include "ch422g.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ch422g {
const uint8_t CH422G_REG_IN = 0x26;
const uint8_t CH422G_REG_OUT = 0x38;
const uint8_t OUT_REG_DEFAULT_VAL = 0xdf;
static const char *const TAG = "ch422g";
void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CH422G...");
// Test to see if device exists
if (!this->read_inputs_()) {
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
this->mark_failed();
return;
}
// restore defaults over whatever got saved on last boot
if (!this->restore_value_) {
this->write_output_(OUT_REG_DEFAULT_VAL);
}
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
}
void CH422GComponent::loop() {
// Clear all the previously read flags.
this->pin_read_cache_ = 0x00;
}
void CH422GComponent::dump_config() {
ESP_LOGCONFIG(TAG, "CH422G:");
LOG_I2C_DEVICE(this)
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CH422G failed!");
}
}
// ch422g doesn't have any flag support (needs docs?)
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {}
bool CH422GComponent::digital_read(uint8_t pin) {
if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) {
// Read values on first access or in case it's being read again in the same loop
this->read_inputs_();
}
this->pin_read_cache_ |= (1 << pin);
return this->state_mask_ & (1 << pin);
}
void CH422GComponent::digital_write(uint8_t pin, bool value) {
if (value) {
this->write_output_(this->state_mask_ | (1 << pin));
} else {
this->write_output_(this->state_mask_ & ~(1 << pin));
}
}
bool CH422GComponent::read_inputs_() {
if (this->is_failed()) {
return false;
}
uint8_t temp = 0;
if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
uint8_t output = 0;
if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
this->state_mask_ = output;
this->status_clear_warning();
return true;
}
bool CH422GComponent::write_output_(uint8_t value) {
const uint8_t temp = 1;
if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
uint8_t write_mask = value;
if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(
str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask)
.c_str());
return false;
}
this->state_mask_ = value;
this->status_clear_warning();
return true;
}
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
// Run our loop() method very early in the loop, so that we cache read values
// before other components call our digital_read() method.
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
void CH422GGPIOPin::setup() { pin_mode(flags_); }
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
} // namespace ch422g
} // namespace esphome

View file

@ -0,0 +1,70 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ch422g {
class CH422GComponent : public Component, public i2c::I2CDevice {
public:
CH422GComponent() = default;
/// Check i2c availability and setup masks
void setup() override;
/// Poll for input changes periodically
void loop() override;
/// Helper function to read the value of a pin.
bool digital_read(uint8_t pin);
/// Helper function to write the value of a pin.
void digital_write(uint8_t pin, bool value);
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
float get_loop_priority() const override;
void dump_config() override;
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
bool read_inputs_();
bool write_output_(uint8_t value);
/// The mask to write as output state - 1 means HIGH, 0 means LOW
uint8_t state_mask_{0x00};
/// Flags to check if read previously during this loop
uint8_t pin_read_cache_ = {0x00};
/// Storage for last I2C error seen
esphome::i2c::ErrorCode last_error_;
/// Whether we want to override stored values on expander
bool restore_value_{false};
};
/// Helper class to expose a CH422G pin as an internal input GPIO pin.
class CH422GGPIOPin : public GPIOPin {
public:
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
protected:
CH422GComponent *parent_;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
} // namespace ch422g
} // namespace esphome

View file

@ -16,6 +16,8 @@
#include <esp32s2/rom/rtc.h> #include <esp32s2/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32S3)
#include <esp32s3/rom/rtc.h> #include <esp32s3/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32H2)
#include <esp32h2/rom/rtc.h>
#endif #endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <Esp.h> #include <Esp.h>
@ -61,7 +63,7 @@ std::string DebugComponent::get_reset_reason_() {
case RTCWDT_SYS_RESET: case RTCWDT_SYS_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core"; reset_reason = "RTC Watch Dog Reset Digital Core";
break; break;
#if !defined(USE_ESP32_VARIANT_ESP32C6) #if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
case INTRUSION_RESET: case INTRUSION_RESET:
reset_reason = "Intrusion Reset CPU"; reset_reason = "Intrusion Reset CPU";
break; break;

View file

@ -1,15 +1,15 @@
from esphome import automation, core
from esphome.automation import maybe_simple_id
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core, automation
from esphome.automation import maybe_simple_id
from esphome.const import ( from esphome.const import (
CONF_AUTO_CLEAR_ENABLED, CONF_AUTO_CLEAR_ENABLED,
CONF_FROM,
CONF_ID, CONF_ID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_PAGES,
CONF_PAGE_ID, CONF_PAGE_ID,
CONF_PAGES,
CONF_ROTATION, CONF_ROTATION,
CONF_FROM,
CONF_TO, CONF_TO,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
) )
@ -195,3 +195,4 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
@coroutine_with_priority(100.0) @coroutine_with_priority(100.0)
async def to_code(config): async def to_code(config):
cg.add_global(display_ns.using) cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View file

@ -256,6 +256,7 @@ bool Dsmr::parse_telegram() {
MyData data; MyData data;
ESP_LOGV(TAG, "Trying to parse telegram"); ESP_LOGV(TAG, "Trying to parse telegram");
this->stop_requesting_data_(); this->stop_requesting_data_();
::dsmr::ParseResult<void> res = ::dsmr::ParseResult<void> res =
::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values. this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
@ -267,6 +268,11 @@ bool Dsmr::parse_telegram() {
} else { } else {
this->status_clear_warning(); this->status_clear_warning();
this->publish_sensors(data); this->publish_sensors(data);
// publish the telegram, after publishing the sensors so it can also trigger action based on latest values
if (this->s_telegram_ != nullptr) {
this->s_telegram_->publish_state(std::string(this->telegram_, this->bytes_read_));
}
return true; return true;
} }
} }

View file

@ -85,6 +85,9 @@ class Dsmr : public Component, public uart::UARTDevice {
void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; } void set_##s(text_sensor::TextSensor *sensor) { s_##s##_ = sensor; }
DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, ) DSMR_TEXT_SENSOR_LIST(DSMR_SET_TEXT_SENSOR, )
// handled outside dsmr
void set_telegram(text_sensor::TextSensor *sensor) { s_telegram_ = sensor; }
protected: protected:
void receive_telegram_(); void receive_telegram_();
void receive_encrypted_telegram_(); void receive_encrypted_telegram_();
@ -124,6 +127,9 @@ class Dsmr : public Component, public uart::UARTDevice {
bool header_found_{false}; bool header_found_{false};
bool footer_found_{false}; bool footer_found_{false};
// handled outside dsmr
text_sensor::TextSensor *s_telegram_{nullptr};
// Sensor member pointers // Sensor member pointers
#define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr}; #define DSMR_DECLARE_SENSOR(s) sensor::Sensor *s_##s##_{nullptr};
DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, ) DSMR_SENSOR_LIST(DSMR_DECLARE_SENSOR, )

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor from esphome.components import text_sensor
from esphome.const import CONF_INTERNAL
from . import Dsmr, CONF_DSMR_ID from . import Dsmr, CONF_DSMR_ID
AUTO_LOAD = ["dsmr"] AUTO_LOAD = ["dsmr"]
@ -22,6 +22,9 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
cv.Optional("telegram"): text_sensor.text_sensor_schema().extend(
{cv.Optional(CONF_INTERNAL, default=True): cv.boolean}
),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -37,7 +40,9 @@ async def to_code(config):
if id and id.type == text_sensor.TextSensor: if id and id.type == text_sensor.TextSensor:
var = await text_sensor.new_text_sensor(conf) var = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}")(var)) cg.add(getattr(hub, f"set_{key}")(var))
text_sensors.append(f"F({key})") if key != "telegram":
# telegram is not handled by dsmr
text_sensors.append(f"F({key})")
if text_sensors: if text_sensors:
cg.add_define( cg.add_define(

View file

@ -239,7 +239,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 7) RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
# The platformio/espressif32 version to use for esp-idf frameworks # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32

View file

@ -31,6 +31,13 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128);
return ret; return ret;
} }
ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
ESPBTUUID ret;
ret.uuid_.len = ESP_UUID_LEN_128;
for (int i = 0; i < ESP_UUID_LEN_128; i++)
ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i];
return ret;
}
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
ESPBTUUID ret; ESPBTUUID ret;
if (data.length() == 4) { if (data.length() == 4) {

View file

@ -20,6 +20,7 @@ class ESPBTUUID {
static ESPBTUUID from_uint32(uint32_t uuid); static ESPBTUUID from_uint32(uint32_t uuid);
static ESPBTUUID from_raw(const uint8_t *data); static ESPBTUUID from_raw(const uint8_t *data);
static ESPBTUUID from_raw_reversed(const uint8_t *data);
static ESPBTUUID from_raw(const std::string &data); static ESPBTUUID from_raw(const std::string &data);

View file

@ -462,14 +462,16 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
} }
for (auto &data : this->manufacturer_datas_) { for (auto &data : this->manufacturer_datas_) {
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data);
if (this->get_ibeacon().has_value()) { if (ibeacon.has_value()) {
auto ibeacon = this->get_ibeacon().value(); ESP_LOGVV(TAG, " Manufacturer iBeacon:");
ESP_LOGVV(TAG, " iBeacon data:"); ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str());
ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str()); ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major());
ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major()); ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor());
ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor()); ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power());
ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power()); } else {
ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(),
format_hex_pretty(data.data).c_str());
} }
} }
for (auto &data : this->service_datas_) { for (auto &data : this->service_datas_) {
@ -478,7 +480,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
} }
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); ESP_LOGVV(TAG, " Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
#endif #endif
} }
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) { void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {

View file

@ -44,10 +44,10 @@ class ESPBLEiBeacon {
ESPBLEiBeacon(const uint8_t *data); ESPBLEiBeacon(const uint8_t *data);
static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data); static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data);
uint16_t get_major() { return ((this->beacon_data_.major & 0xFF) << 8) | (this->beacon_data_.major >> 8); } uint16_t get_major() { return byteswap(this->beacon_data_.major); }
uint16_t get_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); } uint16_t get_minor() { return byteswap(this->beacon_data_.minor); }
int8_t get_signal_power() { return this->beacon_data_.signal_power; } int8_t get_signal_power() { return this->beacon_data_.signal_power; }
ESPBTUUID get_uuid() { return ESPBTUUID::from_raw(this->beacon_data_.proximity_uuid); } ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); }
protected: protected:
struct { struct {

View file

@ -140,6 +140,8 @@ CONF_TEST_PATTERN = "test_pattern"
# framerates # framerates
CONF_MAX_FRAMERATE = "max_framerate" CONF_MAX_FRAMERATE = "max_framerate"
CONF_IDLE_FRAMERATE = "idle_framerate" CONF_IDLE_FRAMERATE = "idle_framerate"
# frame buffer
CONF_FRAME_BUFFER_COUNT = "frame_buffer_count"
# stream trigger # stream trigger
CONF_ON_STREAM_START = "on_stream_start" CONF_ON_STREAM_START = "on_stream_start"
@ -213,6 +215,7 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All(
cv.framerate, cv.Range(min=0, max=1) cv.framerate, cv.Range(min=0, max=1)
), ),
cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2),
cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( cv.Optional(CONF_ON_STREAM_START): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@ -285,6 +288,7 @@ async def to_code(config):
cg.add(var.set_idle_update_interval(0)) cg.add(var.set_idle_update_interval(0))
else: else:
cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE])) cg.add(var.set_idle_update_interval(1000 / config[CONF_IDLE_FRAMERATE]))
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
cg.add(var.set_frame_size(config[CONF_RESOLUTION])) cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
cg.add_define("USE_ESP32_CAMERA") cg.add_define("USE_ESP32_CAMERA")

View file

@ -127,7 +127,7 @@ void ESP32Camera::dump_config() {
sensor_t *s = esp_camera_sensor_get(); sensor_t *s = esp_camera_sensor_get();
auto st = s->status; auto st = s->status;
ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality); ESP_LOGCONFIG(TAG, " JPEG Quality: %u", st.quality);
// ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count); ESP_LOGCONFIG(TAG, " Framebuffer Count: %u", conf.fb_count);
ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast); ESP_LOGCONFIG(TAG, " Contrast: %d", st.contrast);
ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness); ESP_LOGCONFIG(TAG, " Brightness: %d", st.brightness);
ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation);
@ -212,6 +212,8 @@ ESP32Camera::ESP32Camera() {
this->config_.frame_size = FRAMESIZE_VGA; // 640x480 this->config_.frame_size = FRAMESIZE_VGA; // 640x480
this->config_.jpeg_quality = 10; this->config_.jpeg_quality = 10;
this->config_.fb_count = 1; this->config_.fb_count = 1;
this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
this->config_.fb_location = CAMERA_FB_IN_PSRAM;
global_esp32_camera = this; global_esp32_camera = this;
} }
@ -333,6 +335,12 @@ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) {
void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) {
this->idle_update_interval_ = idle_update_interval; this->idle_update_interval_ = idle_update_interval;
} }
/* set frame buffer parameters */
void ESP32Camera::set_frame_buffer_mode(camera_grab_mode_t mode) { this->config_.grab_mode = mode; }
void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) {
this->config_.fb_count = fb_count;
this->set_frame_buffer_mode(fb_count > 1 ? CAMERA_GRAB_LATEST : CAMERA_GRAB_WHEN_EMPTY);
}
/* ---------------- public API (specific) ---------------- */ /* ---------------- public API (specific) ---------------- */
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) { void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {

View file

@ -145,6 +145,9 @@ class ESP32Camera : public Component, public EntityBase {
/* -- framerates */ /* -- framerates */
void set_max_update_interval(uint32_t max_update_interval); void set_max_update_interval(uint32_t max_update_interval);
void set_idle_update_interval(uint32_t idle_update_interval); void set_idle_update_interval(uint32_t idle_update_interval);
/* -- frame buffer */
void set_frame_buffer_mode(camera_grab_mode_t mode);
void set_frame_buffer_count(uint8_t fb_count);
/* public API (derivated) */ /* public API (derivated) */
void setup() override; void setup() override;

View file

@ -472,13 +472,13 @@ void EthernetComponent::start_connect_() {
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error"); ESPHL_ERROR_CHECK(err, "DHCPC start error");
} }
#if USE_NETWORK_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
}
#endif /* USE_NETWORK_IPV6 */
} }
#if USE_NETWORK_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
}
#endif /* USE_NETWORK_IPV6 */
this->connect_begin_ = millis(); this->connect_begin_ = millis();
this->status_set_warning(); this->status_set_warning();

View file

@ -1,43 +1,35 @@
import functools
import hashlib import hashlib
import logging import logging
import functools
from pathlib import Path
import os import os
from pathlib import Path
import re import re
from packaging import version from packaging import version
import requests import requests
from esphome import core from esphome import core, external_files
from esphome import external_files
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.helpers import ( import esphome.config_validation as cv
copy_file_if_changed,
cpp_string_escape,
)
from esphome.const import ( from esphome.const import (
CONF_FAMILY, CONF_FAMILY,
CONF_FILE, CONF_FILE,
CONF_GLYPHS, CONF_GLYPHS,
CONF_ID, CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID, CONF_RAW_DATA_ID,
CONF_TYPE,
CONF_REFRESH, CONF_REFRESH,
CONF_SIZE, CONF_SIZE,
CONF_PATH, CONF_TYPE,
CONF_WEIGHT,
CONF_URL, CONF_URL,
CONF_WEIGHT,
) )
from esphome.core import ( from esphome.core import CORE, HexInt
CORE, from esphome.helpers import copy_file_if_changed, cpp_string_escape
HexInt,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "font" DOMAIN = "font"
DEPENDENCIES = ["display"]
MULTI_CONF = True MULTI_CONF = True
CODEOWNERS = ["@esphome/core", "@clydebarrow"] CODEOWNERS = ["@esphome/core", "@clydebarrow"]
@ -400,10 +392,7 @@ class EFont:
def convert_bitmap_to_pillow_font(filepath): def convert_bitmap_to_pillow_font(filepath):
from PIL import ( from PIL import BdfFontFile, PcfFontFile
PcfFontFile,
BdfFontFile,
)
local_bitmap_font_file = external_files.compute_local_file_dir( local_bitmap_font_file = external_files.compute_local_file_dir(
DOMAIN, DOMAIN,

View file

@ -1,9 +1,8 @@
#include "font.h" #include "font.h"
#include "esphome/core/color.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome { namespace esphome {
namespace font { namespace font {
@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) {
return -1; return -1;
return lo; return lo;
} }
#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
*baseline = this->baseline_; *baseline = this->baseline_;
*height = this->height_; *height = this->height_;
@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
i += match_length; i += match_length;
} }
} }
#endif
} // namespace font } // namespace font
} // namespace esphome } // namespace esphome

View file

@ -1,8 +1,11 @@
#pragma once #pragma once
#include "esphome/core/datatypes.h"
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_DISPLAY
#include "esphome/components/display/display.h"
#endif
namespace esphome { namespace esphome {
namespace font { namespace font {
@ -38,7 +41,11 @@ class Glyph {
const GlyphData *glyph_data_; const GlyphData *glyph_data_;
}; };
class Font : public display::BaseFont { class Font
#ifdef USE_DISPLAY
: public display::BaseFont
#endif
{
public: public:
/** Construct the font with the given glyphs. /** Construct the font with the given glyphs.
* *
@ -50,9 +57,11 @@ class Font : public display::BaseFont {
int match_next_glyph(const uint8_t *str, int *match_length); int match_next_glyph(const uint8_t *str, int *match_length);
#ifdef USE_DISPLAY
void print(int x_start, int y_start, display::Display *display, Color color, const char *text, void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
Color background) override; Color background) override;
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
#endif
inline int get_baseline() { return this->baseline_; } inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; } inline int get_height() { return this->height_; }
inline int get_bpp() { return this->bpp_; } inline int get_bpp() { return this->bpp_; }

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL from esphome.const import CONF_ID, CONF_MODEL
CODEOWNERS = ["@orestismers"] CODEOWNERS = ["@orestismers"]
@ -17,6 +17,7 @@ MODELS = {
"yaa": Model.GREE_YAA, "yaa": Model.GREE_YAA,
"yac": Model.GREE_YAC, "yac": Model.GREE_YAC,
"yac1fb9": Model.GREE_YAC1FB9, "yac1fb9": Model.GREE_YAC1FB9,
"yx1ff": Model.GREE_YX1FF,
} }
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(

View file

@ -6,7 +6,15 @@ namespace gree {
static const char *const TAG = "gree.climate"; static const char *const TAG = "gree.climate";
void GreeClimate::set_model(Model model) { this->model_ = model; } void GreeClimate::set_model(Model model) {
if (model == GREE_YX1FF) {
this->fan_modes_.insert(climate::CLIMATE_FAN_QUIET); // YX1FF 4 speed
this->presets_.insert(climate::CLIMATE_PRESET_NONE); // YX1FF sleep mode
this->presets_.insert(climate::CLIMATE_PRESET_SLEEP); // YX1FF sleep mode
}
this->model_ = model;
}
void GreeClimate::transmit_state() { void GreeClimate::transmit_state() {
uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00};
@ -14,7 +22,7 @@ void GreeClimate::transmit_state() {
remote_state[0] = this->fan_speed_() | this->operation_mode_(); remote_state[0] = this->fan_speed_() | this->operation_mode_();
remote_state[1] = this->temperature_(); remote_state[1] = this->temperature_();
if (this->model_ == GREE_YAN) { if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
remote_state[2] = 0x60; remote_state[2] = 0x60;
remote_state[3] = 0x50; remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_(); remote_state[4] = this->vertical_swing_();
@ -36,8 +44,18 @@ void GreeClimate::transmit_state() {
} }
} }
if (this->model_ == GREE_YX1FF) {
if (this->fan_speed_() == GREE_FAN_TURBO) {
remote_state[2] |= GREE_FAN_TURBO_BIT;
}
if (this->preset_() == GREE_PRESET_SLEEP) {
remote_state[0] |= GREE_PRESET_SLEEP_BIT;
}
}
// Calculate the checksum // Calculate the checksum
if (this->model_ == GREE_YAN) { if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0); remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
} else { } else {
remote_state[7] = remote_state[7] =
@ -124,6 +142,23 @@ uint8_t GreeClimate::operation_mode_() {
} }
uint8_t GreeClimate::fan_speed_() { uint8_t GreeClimate::fan_speed_() {
// YX1FF has 4 fan speeds -- we treat low as quiet and turbo as high
if (this->model_ == GREE_YX1FF) {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_QUIET:
return GREE_FAN_1;
case climate::CLIMATE_FAN_LOW:
return GREE_FAN_2;
case climate::CLIMATE_FAN_MEDIUM:
return GREE_FAN_3;
case climate::CLIMATE_FAN_HIGH:
return GREE_FAN_TURBO;
case climate::CLIMATE_FAN_AUTO:
default:
return GREE_FAN_AUTO;
}
}
switch (this->fan_mode.value()) { switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW: case climate::CLIMATE_FAN_LOW:
return GREE_FAN_1; return GREE_FAN_1;
@ -161,5 +196,21 @@ uint8_t GreeClimate::temperature_() {
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX)); return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX));
} }
uint8_t GreeClimate::preset_() {
// YX1FF has sleep preset
if (this->model_ == GREE_YX1FF) {
switch (this->preset.value()) {
case climate::CLIMATE_PRESET_NONE:
return GREE_PRESET_NONE;
case climate::CLIMATE_PRESET_SLEEP:
return GREE_PRESET_SLEEP;
default:
return GREE_PRESET_NONE;
}
}
return GREE_PRESET_NONE;
}
} // namespace gree } // namespace gree
} // namespace esphome } // namespace esphome

View file

@ -25,7 +25,6 @@ const uint8_t GREE_FAN_AUTO = 0x00;
const uint8_t GREE_FAN_1 = 0x10; const uint8_t GREE_FAN_1 = 0x10;
const uint8_t GREE_FAN_2 = 0x20; const uint8_t GREE_FAN_2 = 0x20;
const uint8_t GREE_FAN_3 = 0x30; const uint8_t GREE_FAN_3 = 0x30;
const uint8_t GREE_FAN_TURBO = 0x80;
// IR Transmission // IR Transmission
const uint32_t GREE_IR_FREQUENCY = 38000; const uint32_t GREE_IR_FREQUENCY = 38000;
@ -70,8 +69,16 @@ const uint8_t GREE_HDIR_MIDDLE = 0x04;
const uint8_t GREE_HDIR_MRIGHT = 0x05; const uint8_t GREE_HDIR_MRIGHT = 0x05;
const uint8_t GREE_HDIR_RIGHT = 0x06; const uint8_t GREE_HDIR_RIGHT = 0x06;
// Only available on YX1FF
// Turbo (high) fan mode + sleep preset mode
const uint8_t GREE_FAN_TURBO = 0x80;
const uint8_t GREE_FAN_TURBO_BIT = 0x10;
const uint8_t GREE_PRESET_NONE = 0x00;
const uint8_t GREE_PRESET_SLEEP = 0x01;
const uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
// Model codes // Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 }; enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF };
class GreeClimate : public climate_ir::ClimateIR { class GreeClimate : public climate_ir::ClimateIR {
public: public:
@ -93,6 +100,7 @@ class GreeClimate : public climate_ir::ClimateIR {
uint8_t horizontal_swing_(); uint8_t horizontal_swing_();
uint8_t vertical_swing_(); uint8_t vertical_swing_();
uint8_t temperature_(); uint8_t temperature_();
uint8_t preset_();
Model model_{}; Model model_{};
}; };

View file

@ -5,6 +5,19 @@ from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
CODEOWNERS = ["@OttoWinter", "@esphome/core"] CODEOWNERS = ["@OttoWinter", "@esphome/core"]
homeassistant_ns = cg.esphome_ns.namespace("homeassistant") homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
def validate_entity_domain(platform, supported_domains):
def validator(config):
domain = config[CONF_ENTITY_ID].split(".", 1)[0]
if domain not in supported_domains:
raise cv.Invalid(
f"Entity ID {config[CONF_ENTITY_ID]} is not supported by the {platform} platform."
)
return config
return validator
HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ENTITY_ID): cv.entity_id, cv.Required(CONF_ENTITY_ID): cv.entity_id,

View file

@ -7,19 +7,32 @@ from .. import (
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA,
homeassistant_ns, homeassistant_ns,
setup_home_assistant_entity, setup_home_assistant_entity,
validate_entity_domain,
) )
CODEOWNERS = ["@Links2004"] CODEOWNERS = ["@Links2004"]
DEPENDENCIES = ["api"] DEPENDENCIES = ["api"]
SUPPORTED_DOMAINS = [
"automation",
"fan",
"humidifier",
"input_boolean",
"light",
"remote",
"siren",
"switch",
]
HomeassistantSwitch = homeassistant_ns.class_( HomeassistantSwitch = homeassistant_ns.class_(
"HomeassistantSwitch", switch.Switch, cg.Component "HomeassistantSwitch", switch.Switch, cg.Component
) )
CONFIG_SCHEMA = ( CONFIG_SCHEMA = cv.All(
switch.switch_schema(HomeassistantSwitch) switch.switch_schema(HomeassistantSwitch)
.extend(cv.COMPONENT_SCHEMA)
.extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
validate_entity_domain("switch", SUPPORTED_DOMAINS),
) )

View file

@ -42,9 +42,9 @@ void HomeassistantSwitch::write_state(bool state) {
api::HomeassistantServiceResponse resp; api::HomeassistantServiceResponse resp;
if (state) { if (state) {
resp.service = "switch.turn_on"; resp.service = "homeassistant.turn_on";
} else { } else {
resp.service = "switch.turn_off"; resp.service = "homeassistant.turn_off";
} }
api::HomeassistantServiceMap entity_id_kv; api::HomeassistantServiceMap entity_id_kv;

View file

@ -1,16 +1,16 @@
import esphome.config_validation as cv
import esphome.final_validate as fv
import esphome.codegen as cg
from esphome import pins from esphome import pins
from esphome.const import CONF_ID import esphome.codegen as cg
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
VARIANT_ESP32C3,
) )
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
from esphome.cpp_generator import MockObjClass
import esphome.final_validate as fv
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
@ -25,16 +25,26 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin"
CONF_I2S_AUDIO = "i2s_audio" CONF_I2S_AUDIO = "i2s_audio"
CONF_I2S_AUDIO_ID = "i2s_audio_id" CONF_I2S_AUDIO_ID = "i2s_audio_id"
CONF_BITS_PER_SAMPLE = "bits_per_sample"
CONF_I2S_MODE = "i2s_mode" CONF_I2S_MODE = "i2s_mode"
CONF_PRIMARY = "primary" CONF_PRIMARY = "primary"
CONF_SECONDARY = "secondary" CONF_SECONDARY = "secondary"
CONF_USE_APLL = "use_apll"
CONF_BITS_PER_SAMPLE = "bits_per_sample"
CONF_BITS_PER_CHANNEL = "bits_per_channel"
CONF_MONO = "mono"
CONF_LEFT = "left"
CONF_RIGHT = "right"
CONF_STEREO = "stereo"
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) I2SAudioBase = i2s_audio_ns.class_(
I2SAudioOut = i2s_audio_ns.class_( "I2SAudioBase", cg.Parented.template(I2SAudioComponent)
"I2SAudioOut", cg.Parented.template(I2SAudioComponent)
) )
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", I2SAudioBase)
I2SAudioOut = i2s_audio_ns.class_("I2SAudioOut", I2SAudioBase)
i2s_mode_t = cg.global_ns.enum("i2s_mode_t") i2s_mode_t = cg.global_ns.enum("i2s_mode_t")
I2S_MODE_OPTIONS = { I2S_MODE_OPTIONS = {
@ -50,6 +60,75 @@ I2S_PORTS = {
VARIANT_ESP32C3: 1, VARIANT_ESP32C3: 1,
} }
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
I2S_CHANNELS = {
CONF_MONO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ALL_LEFT,
CONF_LEFT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
CONF_RIGHT: i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
CONF_STEREO: i2s_channel_fmt_t.I2S_CHANNEL_FMT_RIGHT_LEFT,
}
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
I2S_BITS_PER_SAMPLE = {
8: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_8BIT,
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
24: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_24BIT,
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
}
i2s_bits_per_chan_t = cg.global_ns.enum("i2s_bits_per_chan_t")
I2S_BITS_PER_CHANNEL = {
"default": i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_DEFAULT,
8: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_8BIT,
16: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_16BIT,
24: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_24BIT,
32: i2s_bits_per_chan_t.I2S_BITS_PER_CHAN_32BIT,
}
_validate_bits = cv.float_with_unit("bits", "bit")
def i2s_audio_component_schema(
class_: MockObjClass,
*,
default_sample_rate: int,
default_channel: str,
default_bits_per_sample: str,
):
return cv.Schema(
{
cv.GenerateID(): cv.declare_id(class_),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Optional(CONF_CHANNEL, default=default_channel): cv.enum(I2S_CHANNELS),
cv.Optional(CONF_SAMPLE_RATE, default=default_sample_rate): cv.int_range(
min=1
),
cv.Optional(CONF_BITS_PER_SAMPLE, default=default_bits_per_sample): cv.All(
_validate_bits, cv.enum(I2S_BITS_PER_SAMPLE)
),
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
I2S_MODE_OPTIONS, lower=True
),
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
cv.Optional(CONF_BITS_PER_CHANNEL, default="default"): cv.All(
cv.Any(cv.float_with_unit("bits", "bit"), "default"),
cv.enum(I2S_BITS_PER_CHANNEL),
),
}
)
async def register_i2s_audio_component(var, config):
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
cg.add(var.set_bits_per_channel(config[CONF_BITS_PER_CHANNEL]))
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(I2SAudioComponent), cv.GenerateID(): cv.declare_id(I2SAudioComponent),

View file

@ -11,9 +11,27 @@ namespace i2s_audio {
class I2SAudioComponent; class I2SAudioComponent;
class I2SAudioIn : public Parented<I2SAudioComponent> {}; class I2SAudioBase : public Parented<I2SAudioComponent> {
public:
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
void set_bits_per_channel(i2s_bits_per_chan_t bits_per_channel) { this->bits_per_channel_ = bits_per_channel; }
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
class I2SAudioOut : public Parented<I2SAudioComponent> {}; protected:
i2s_mode_t i2s_mode_{};
i2s_channel_fmt_t channel_;
uint32_t sample_rate_;
i2s_bits_per_sample_t bits_per_sample_;
i2s_bits_per_chan_t bits_per_channel_;
bool use_apll_;
};
class I2SAudioIn : public I2SAudioBase {};
class I2SAudioOut : public I2SAudioBase {};
class I2SAudioComponent : public Component { class I2SAudioComponent : public Component {
public: public:

View file

@ -12,6 +12,10 @@ from .. import (
I2SAudioOut, I2SAudioOut,
CONF_I2S_AUDIO_ID, CONF_I2S_AUDIO_ID,
CONF_I2S_DOUT_PIN, CONF_I2S_DOUT_PIN,
CONF_LEFT,
CONF_RIGHT,
CONF_MONO,
CONF_STEREO,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -30,12 +34,12 @@ CONF_DAC_TYPE = "dac_type"
CONF_I2S_COMM_FMT = "i2s_comm_fmt" CONF_I2S_COMM_FMT = "i2s_comm_fmt"
INTERNAL_DAC_OPTIONS = { INTERNAL_DAC_OPTIONS = {
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
} }
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"] EXTERNAL_DAC_OPTIONS = [CONF_MONO, CONF_STEREO]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]

View file

@ -23,7 +23,7 @@ enum I2SState : uint8_t {
I2S_STATE_STOPPING, I2S_STATE_STOPPING,
}; };
class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer, public I2SAudioOut { class I2SAudioMediaPlayer : public Component, public Parented<I2SAudioComponent>, public media_player::MediaPlayer {
public: public:
void setup() override; void setup() override;
float get_setup_priority() const override { return esphome::setup_priority::LATE; } float get_setup_priority() const override { return esphome::setup_priority::LATE; }

View file

@ -1,20 +1,17 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome import pins from esphome import pins
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE import esphome.codegen as cg
from esphome.components import microphone, esp32 from esphome.components import esp32, microphone
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NUMBER
from .. import ( from .. import (
CONF_I2S_MODE,
CONF_PRIMARY,
I2S_MODE_OPTIONS,
i2s_audio_ns,
I2SAudioComponent,
I2SAudioIn,
CONF_I2S_AUDIO_ID,
CONF_I2S_DIN_PIN, CONF_I2S_DIN_PIN,
CONF_RIGHT,
I2SAudioIn,
i2s_audio_component_schema,
i2s_audio_ns,
register_i2s_audio_component,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -23,29 +20,14 @@ DEPENDENCIES = ["i2s_audio"]
CONF_ADC_PIN = "adc_pin" CONF_ADC_PIN = "adc_pin"
CONF_ADC_TYPE = "adc_type" CONF_ADC_TYPE = "adc_type"
CONF_PDM = "pdm" CONF_PDM = "pdm"
CONF_BITS_PER_SAMPLE = "bits_per_sample"
CONF_USE_APLL = "use_apll"
I2SAudioMicrophone = i2s_audio_ns.class_( I2SAudioMicrophone = i2s_audio_ns.class_(
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
) )
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
CHANNELS = {
"left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT,
"right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT,
}
i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t")
BITS_PER_SAMPLE = {
16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT,
32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT,
}
INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32]
PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3]
_validate_bits = cv.float_with_unit("bits", "bit")
def validate_esp32_variant(config): def validate_esp32_variant(config):
variant = esp32.get_esp32_variant() variant = esp32.get_esp32_variant()
@ -62,21 +44,15 @@ def validate_esp32_variant(config):
BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
{ i2s_audio_component_schema(
cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), I2SAudioMicrophone,
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), default_sample_rate=16000,
cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), default_channel=CONF_RIGHT,
cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1), default_bits_per_sample="32bit",
cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All( )
_validate_bits, cv.enum(BITS_PER_SAMPLE)
),
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
I2S_MODE_OPTIONS, lower=True
),
}
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.typed_schema( cv.typed_schema(
{ {
@ -88,7 +64,7 @@ CONFIG_SCHEMA = cv.All(
"external": BASE_SCHEMA.extend( "external": BASE_SCHEMA.extend(
{ {
cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number, cv.Required(CONF_I2S_DIN_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_PDM): cv.boolean, cv.Optional(CONF_PDM, default=False): cv.boolean,
} }
), ),
}, },
@ -101,8 +77,8 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await register_i2s_audio_component(var, config)
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) await microphone.register_microphone(var, config)
if config[CONF_ADC_TYPE] == "internal": if config[CONF_ADC_TYPE] == "internal":
variant = esp32.get_esp32_variant() variant = esp32.get_esp32_variant()
@ -112,11 +88,3 @@ async def to_code(config):
else: else:
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
cg.add(var.set_pdm(config[CONF_PDM])) cg.add(var.set_pdm(config[CONF_PDM]))
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
await microphone.register_microphone(var, config)

View file

@ -58,7 +58,7 @@ void I2SAudioMicrophone::start_() {
.tx_desc_auto_clear = false, .tx_desc_auto_clear = false,
.fixed_mclk = 0, .fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256, .mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, .bits_per_chan = this->bits_per_channel_,
}; };
esp_err_t err; esp_err_t err;
@ -167,21 +167,24 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
return 0; return 0;
} }
this->status_clear_warning(); this->status_clear_warning();
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { // ESP-IDF I2S implementation right-extends 8-bit data to 16 bits,
return bytes_read; // and 24-bit data to 32 bits.
} else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { switch (this->bits_per_sample_) {
std::vector<int16_t> samples; case I2S_BITS_PER_SAMPLE_8BIT:
size_t samples_read = bytes_read / sizeof(int32_t); case I2S_BITS_PER_SAMPLE_16BIT:
samples.resize(samples_read); return bytes_read;
for (size_t i = 0; i < samples_read; i++) { case I2S_BITS_PER_SAMPLE_24BIT:
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; case I2S_BITS_PER_SAMPLE_32BIT: {
samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); size_t samples_read = bytes_read / sizeof(int32_t);
for (size_t i = 0; i < samples_read; i++) {
int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14;
buf[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX);
}
return samples_read * sizeof(int16_t);
} }
memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); default:
return samples_read * sizeof(int16_t); ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
} else { return 0;
ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_);
return 0;
} }
} }

View file

@ -30,13 +30,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
} }
#endif #endif
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
protected: protected:
void start_(); void start_();
void stop_(); void stop_();
@ -48,11 +41,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
bool adc_{false}; bool adc_{false};
#endif #endif
bool pdm_{false}; bool pdm_{false};
i2s_mode_t i2s_mode_{};
i2s_channel_fmt_t channel_;
uint32_t sample_rate_;
i2s_bits_per_sample_t bits_per_sample_;
bool use_apll_;
HighFrequencyLoopRequester high_freq_; HighFrequencyLoopRequester high_freq_;
}; };

View file

@ -1,15 +1,19 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import CONF_ID, CONF_MODE import esphome.codegen as cg
from esphome.components import esp32, speaker from esphome.components import esp32, speaker
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_MODE, CONF_TIMEOUT
from .. import ( from .. import (
CONF_I2S_AUDIO_ID,
CONF_I2S_DOUT_PIN, CONF_I2S_DOUT_PIN,
I2SAudioComponent, CONF_LEFT,
CONF_MONO,
CONF_RIGHT,
CONF_STEREO,
I2SAudioOut, I2SAudioOut,
i2s_audio_component_schema,
i2s_audio_ns, i2s_audio_ns,
register_i2s_audio_component,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -19,19 +23,16 @@ I2SAudioSpeaker = i2s_audio_ns.class_(
"I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut "I2SAudioSpeaker", cg.Component, speaker.Speaker, I2SAudioOut
) )
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
CONF_MUTE_PIN = "mute_pin"
CONF_DAC_TYPE = "dac_type" CONF_DAC_TYPE = "dac_type"
i2s_dac_mode_t = cg.global_ns.enum("i2s_dac_mode_t")
INTERNAL_DAC_OPTIONS = { INTERNAL_DAC_OPTIONS = {
"left": i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN, CONF_LEFT: i2s_dac_mode_t.I2S_DAC_CHANNEL_LEFT_EN,
"right": i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN, CONF_RIGHT: i2s_dac_mode_t.I2S_DAC_CHANNEL_RIGHT_EN,
"stereo": i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN, CONF_STEREO: i2s_dac_mode_t.I2S_DAC_CHANNEL_BOTH_EN,
} }
EXTERNAL_DAC_OPTIONS = ["mono", "stereo"]
NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2] NO_INTERNAL_DAC_VARIANTS = [esp32.const.VARIANT_ESP32S2]
@ -44,28 +45,40 @@ def validate_esp32_variant(config):
return config return config
BASE_SCHEMA = (
speaker.SPEAKER_SCHEMA.extend(
i2s_audio_component_schema(
I2SAudioSpeaker,
default_sample_rate=16000,
default_channel=CONF_MONO,
default_bits_per_sample="16bit",
)
)
.extend(
{
cv.Optional(
CONF_TIMEOUT, default="100ms"
): cv.positive_time_period_milliseconds,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.typed_schema( cv.typed_schema(
{ {
"internal": speaker.SPEAKER_SCHEMA.extend( "internal": BASE_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
} }
).extend(cv.COMPONENT_SCHEMA), ),
"external": speaker.SPEAKER_SCHEMA.extend( "external": BASE_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(I2SAudioSpeaker),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Required( cv.Required(
CONF_I2S_DOUT_PIN CONF_I2S_DOUT_PIN
): pins.internal_gpio_output_pin_number, ): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_MODE, default="mono"): cv.one_of(
*EXTERNAL_DAC_OPTIONS, lower=True
),
} }
).extend(cv.COMPONENT_SCHEMA), ),
}, },
key=CONF_DAC_TYPE, key=CONF_DAC_TYPE,
), ),
@ -76,12 +89,11 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await register_i2s_audio_component(var, config)
await speaker.register_speaker(var, config) await speaker.register_speaker(var, config)
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])
if config[CONF_DAC_TYPE] == "internal": if config[CONF_DAC_TYPE] == "internal":
cg.add(var.set_internal_dac_mode(config[CONF_MODE])) cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
else: else:
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) cg.add(var.set_timeout(config[CONF_TIMEOUT]))

View file

@ -56,6 +56,21 @@ void I2SAudioSpeaker::start_() {
this->task_created_ = true; this->task_created_ = true;
} }
template<typename a, typename b> const uint8_t *convert_data_format(const a *from, b *to, size_t &bytes, bool repeat) {
if (sizeof(a) == sizeof(b) && !repeat) {
return reinterpret_cast<const uint8_t *>(from);
}
const b *result = to;
for (size_t i = 0; i < bytes; i += sizeof(a)) {
b value = static_cast<b>(*from++) << (sizeof(b) - sizeof(a)) * 8;
*to++ = value;
if (repeat)
*to++ = value;
}
bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1); // NOLINT
return reinterpret_cast<const uint8_t *>(result);
}
void I2SAudioSpeaker::player_task(void *params) { void I2SAudioSpeaker::player_task(void *params) {
I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params; I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
@ -64,19 +79,19 @@ void I2SAudioSpeaker::player_task(void *params) {
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
i2s_driver_config_t config = { i2s_driver_config_t config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX), .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX),
.sample_rate = 16000, .sample_rate = this_speaker->sample_rate_,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .bits_per_sample = this_speaker->bits_per_sample_,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .channel_format = this_speaker->channel_,
.communication_format = I2S_COMM_FORMAT_STAND_I2S, .communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8, .dma_buf_count = 8,
.dma_buf_len = 128, .dma_buf_len = 256,
.use_apll = false, .use_apll = this_speaker->use_apll_,
.tx_desc_auto_clear = true, .tx_desc_auto_clear = true,
.fixed_mclk = I2S_PIN_NO_CHANGE, .fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256, .mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT, .bits_per_chan = this_speaker->bits_per_channel_,
}; };
#if SOC_I2S_SUPPORTS_DAC #if SOC_I2S_SUPPORTS_DAC
if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) { if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
@ -114,10 +129,11 @@ void I2SAudioSpeaker::player_task(void *params) {
event.type = TaskEventType::STARTED; event.type = TaskEventType::STARTED;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
int16_t buffer[BUFFER_SIZE / 2]; int32_t buffer[BUFFER_SIZE];
while (true) { while (true) {
if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) { if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) !=
pdTRUE) {
break; // End of audio from main thread break; // End of audio from main thread
} }
if (data_event.stop) { if (data_event.stop) {
@ -125,17 +141,28 @@ void I2SAudioSpeaker::player_task(void *params) {
xQueueReset(this_speaker->buffer_queue_); // Flush queue xQueueReset(this_speaker->buffer_queue_); // Flush queue
break; break;
} }
size_t bytes_written;
memmove(buffer, data_event.data, data_event.len); const uint8_t *data = data_event.data;
size_t remaining = data_event.len / 2; size_t remaining = data_event.len;
size_t current = 0; switch (this_speaker->bits_per_sample_) {
case I2S_BITS_PER_SAMPLE_8BIT:
case I2S_BITS_PER_SAMPLE_16BIT: {
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int16_t *>(buffer),
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
break;
}
case I2S_BITS_PER_SAMPLE_24BIT:
case I2S_BITS_PER_SAMPLE_32BIT: {
data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int32_t *>(buffer),
remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
break;
}
}
while (remaining > 0) { while (remaining != 0) {
uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); size_t bytes_written;
esp_err_t err =
esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS));
(10 / portTICK_PERIOD_MS));
if (err != ESP_OK) { if (err != ESP_OK) {
event = {.type = TaskEventType::WARNING, .err = err}; event = {.type = TaskEventType::WARNING, .err = err};
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) { if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
@ -143,21 +170,8 @@ void I2SAudioSpeaker::player_task(void *params) {
} }
continue; continue;
} }
if (bytes_written != sizeof(sample)) { data += bytes_written;
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL}; remaining -= bytes_written;
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
ESP_LOGW(TAG, "Failed to send WARNING event");
}
continue;
}
remaining--;
current++;
}
event.type = TaskEventType::PLAYING;
event.err = current;
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
ESP_LOGW(TAG, "Failed to send PLAYING event");
} }
} }
@ -213,13 +227,11 @@ void I2SAudioSpeaker::watch_() {
case TaskEventType::STARTED: case TaskEventType::STARTED:
ESP_LOGD(TAG, "Started I2S Audio Speaker"); ESP_LOGD(TAG, "Started I2S Audio Speaker");
this->state_ = speaker::STATE_RUNNING; this->state_ = speaker::STATE_RUNNING;
this->status_clear_warning();
break; break;
case TaskEventType::STOPPING: case TaskEventType::STOPPING:
ESP_LOGD(TAG, "Stopping I2S Audio Speaker"); ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
break; break;
case TaskEventType::PLAYING:
this->status_clear_warning();
break;
case TaskEventType::STOPPED: case TaskEventType::STOPPED:
this->state_ = speaker::STATE_STOPPED; this->state_ = speaker::STATE_STOPPED;
vTaskDelete(this->player_task_handle_); vTaskDelete(this->player_task_handle_);

View file

@ -21,7 +21,6 @@ static const size_t BUFFER_SIZE = 1024;
enum class TaskEventType : uint8_t { enum class TaskEventType : uint8_t {
STARTING = 0, STARTING = 0,
STARTED, STARTED,
PLAYING,
STOPPING, STOPPING,
STOPPED, STOPPED,
WARNING = 255, WARNING = 255,
@ -38,18 +37,18 @@ struct DataEvent {
uint8_t data[BUFFER_SIZE]; uint8_t data[BUFFER_SIZE];
}; };
class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAudioOut { class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Component {
public: public:
float get_setup_priority() const override { return esphome::setup_priority::LATE; } float get_setup_priority() const override { return esphome::setup_priority::LATE; }
void setup() override; void setup() override;
void loop() override; void loop() override;
void set_timeout(uint32_t ms) { this->timeout_ = ms; }
void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; } void set_dout_pin(uint8_t pin) { this->dout_pin_ = pin; }
#if SOC_I2S_SUPPORTS_DAC #if SOC_I2S_SUPPORTS_DAC
void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; } void set_internal_dac_mode(i2s_dac_mode_t mode) { this->internal_dac_mode_ = mode; }
#endif #endif
void set_external_dac_channels(uint8_t channels) { this->external_dac_channels_ = channels; }
void start() override; void start() override;
void stop() override; void stop() override;
@ -70,13 +69,13 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
QueueHandle_t buffer_queue_; QueueHandle_t buffer_queue_;
QueueHandle_t event_queue_; QueueHandle_t event_queue_;
uint32_t timeout_{0};
uint8_t dout_pin_{0}; uint8_t dout_pin_{0};
bool task_created_{false}; bool task_created_{false};
#if SOC_I2S_SUPPORTS_DAC #if SOC_I2S_SUPPORTS_DAC
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE}; i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
#endif #endif
uint8_t external_dac_channels_;
}; };
} // namespace i2s_audio } // namespace i2s_audio

View file

@ -1,18 +1,17 @@
from __future__ import annotations from __future__ import annotations
import logging
import hashlib import hashlib
import io import io
import logging
from pathlib import Path from pathlib import Path
import re import re
from magic import Magic from magic import Magic
from esphome import core from esphome import core, external_files
from esphome.components import font
from esphome import external_files
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import font
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DITHER, CONF_DITHER,
CONF_FILE, CONF_FILE,
@ -239,12 +238,11 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
def load_svg_image(file: bytes, resize: tuple[int, int]): def load_svg_image(file: bytes, resize: tuple[int, int]):
# Local import only to allow "validate_pillow_installed" to run *before* importing it # Local import only to allow "validate_pillow_installed" to run *before* importing it
from PIL import Image
# This import is only needed in case of SVG images; adding it # This import is only needed in case of SVG images; adding it
# to the top would force configurations not using SVG to also have it # to the top would force configurations not using SVG to also have it
# installed for no reason. # installed for no reason.
from cairosvg import svg2png from cairosvg import svg2png
from PIL import Image
if resize: if resize:
req_width, req_height = resize req_width, req_height = resize
@ -274,6 +272,9 @@ async def to_code(config):
elif conf_file[CONF_SOURCE] == SOURCE_WEB: elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = compute_local_image_path(conf_file).as_posix() path = compute_local_image_path(conf_file).as_posix()
else:
raise core.EsphomeError(f"Unknown image source: {conf_file[CONF_SOURCE]}")
try: try:
with open(path, "rb") as f: with open(path, "rb") as f:
file_contents = f.read() file_contents = f.read()

View file

@ -1,10 +1,6 @@
import json import json
import logging import logging
from os.path import ( from os.path import dirname, isfile, join
dirname,
isfile,
join,
)
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
@ -174,12 +170,11 @@ def _notify_old_style(config):
return config return config
# NOTE: Keep this in mind when updating the recommended version: # The dev and latest branches will be at *least* this version, which is what matters.
# * For all constants below, update platformio.ini (in this repo)
ARDUINO_VERSIONS = { ARDUINO_VERSIONS = {
"dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"), "dev": (cv.Version(1, 7, 0), "https://github.com/libretiny-eu/libretiny.git"),
"latest": (cv.Version(0, 0, 0), None), "latest": (cv.Version(1, 7, 0), "libretiny"),
"recommended": (cv.Version(1, 5, 1), None), "recommended": (cv.Version(1, 7, 0), None),
} }
@ -282,10 +277,10 @@ async def component_to_code(config):
# if platform version is a valid version constraint, prefix the default package # if platform version is a valid version constraint, prefix the default package
framework = config[CONF_FRAMEWORK] framework = config[CONF_FRAMEWORK]
cv.platformio_version_constraint(framework[CONF_VERSION]) cv.platformio_version_constraint(framework[CONF_VERSION])
if str(framework[CONF_VERSION]) != "0.0.0": if framework[CONF_SOURCE]:
cg.add_platformio_option("platform", f"libretiny @ {framework[CONF_VERSION]}")
elif framework[CONF_SOURCE]:
cg.add_platformio_option("platform", framework[CONF_SOURCE]) cg.add_platformio_option("platform", framework[CONF_SOURCE])
elif str(framework[CONF_VERSION]) != "0.0.0":
cg.add_platformio_option("platform", f"libretiny @ {framework[CONF_VERSION]}")
else: else:
cg.add_platformio_option("platform", "libretiny") cg.add_platformio_option("platform", "libretiny")

View file

@ -0,0 +1 @@
CODEOWNERS = ["@latonita"]

View file

@ -0,0 +1,542 @@
#include "ltr501.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
using esphome::i2c::ErrorCode;
namespace esphome {
namespace ltr501 {
static const char *const TAG = "ltr501";
static const uint8_t MAX_TRIES = 5;
static const uint8_t MAX_SENSITIVITY_ADJUSTMENTS = 10;
struct GainTimePair {
AlsGain501 gain;
IntegrationTime501 time;
};
bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) {
return lhs.gain == rhs.gain && lhs.time == rhs.time;
}
bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
return !(lhs.gain == rhs.gain && lhs.time == rhs.time);
}
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
size_t i = 0;
size_t idx = -1;
while (idx == -1 && i < size) {
if (array[i] == val) {
idx = i;
break;
}
i++;
}
if (idx == -1 || i + 1 >= size)
return val;
return array[i + 1];
}
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
size_t i = size - 1;
size_t idx = -1;
while (idx == -1 && i > 0) {
if (array[i] == val) {
idx = i;
break;
}
i--;
}
if (idx == -1 || i == 0)
return val;
return array[i - 1];
}
static uint16_t get_itime_ms(IntegrationTime501 time) {
static const uint16_t ALS_INT_TIME[4] = {100, 50, 200, 400};
return ALS_INT_TIME[time & 0b11];
}
static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) {
static const uint16_t ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000};
return ALS_MEAS_RATE[rate & 0b111];
}
static float get_gain_coeff(AlsGain501 gain) { return gain == AlsGain501::GAIN_1 ? 1.0f : 150.0f; }
static float get_ps_gain_coeff(PsGain501 gain) {
static const float PS_GAIN[4] = {1, 4, 8, 16};
return PS_GAIN[gain & 0b11];
}
void LTRAlsPs501Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up LTR-501/301/558");
// As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive
this->set_timeout(100, [this]() { this->state_ = State::DELAYED_SETUP; });
}
void LTRAlsPs501Component::dump_config() {
auto get_device_type = [](LtrType typ) {
switch (typ) {
case LtrType::LTR_TYPE_ALS_ONLY:
return "ALS only";
case LtrType::LTR_TYPE_PS_ONLY:
return "PS only";
case LtrType::LTR_TYPE_ALS_AND_PS:
return "Als + PS";
default:
return "Unknown";
}
};
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Device type: %s", get_device_type(this->ltr_type_));
ESP_LOGCONFIG(TAG, " Automatic mode: %s", ONOFF(this->automatic_mode_enabled_));
ESP_LOGCONFIG(TAG, " Gain: %.0fx", get_gain_coeff(this->gain_));
ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_));
ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", get_meas_time_ms(this->repeat_rate_));
ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
ESP_LOGCONFIG(TAG, " Proximity gain: %.0fx", get_ps_gain_coeff(this->ps_gain_));
ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->ps_cooldown_time_s_);
ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->ps_threshold_high_);
ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->ps_threshold_low_);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_);
LOG_SENSOR(" ", "CH1 Infrared counts", this->infrared_counts_sensor_);
LOG_SENSOR(" ", "CH0 Visible+IR counts", this->full_spectrum_counts_sensor_);
LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with I2C LTR-501/301/558 failed!");
}
}
void LTRAlsPs501Component::update() {
if (!this->is_als_()) {
ESP_LOGW(TAG, "Update. ALS data not available. Change configuration to ALS or ALS_PS.");
return;
}
if (this->is_ready() && this->is_als_() && this->state_ == State::IDLE) {
ESP_LOGV(TAG, "Update. Initiating new ALS data collection.");
this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA;
this->als_readings_.ch0 = 0;
this->als_readings_.ch1 = 0;
this->als_readings_.gain = this->gain_;
this->als_readings_.integration_time = this->integration_time_;
this->als_readings_.lux = 0;
this->als_readings_.number_of_adjustments = 0;
} else {
ESP_LOGV(TAG, "Update. Component not ready yet.");
}
}
void LTRAlsPs501Component::loop() {
ErrorCode err = i2c::ERROR_OK;
static uint8_t tries{0};
switch (this->state_) {
case State::DELAYED_SETUP:
err = this->write(nullptr, 0);
if (err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "i2c connection failed");
this->mark_failed();
}
this->configure_reset_();
if (this->is_als_()) {
this->configure_als_();
this->configure_integration_time_(this->integration_time_);
}
if (this->is_ps_()) {
this->configure_ps_();
}
this->state_ = State::IDLE;
break;
case State::IDLE:
if (this->is_ps_()) {
this->check_and_trigger_ps_();
}
break;
case State::WAITING_FOR_DATA:
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
tries = 0;
ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
this->read_sensor_data_(this->als_readings_);
this->apply_lux_calculation_(this->als_readings_);
this->state_ = State::DATA_COLLECTED;
} else if (tries >= MAX_TRIES) {
ESP_LOGW(TAG, "Can't get data after several tries. Aborting.");
tries = 0;
this->status_set_warning();
this->state_ = State::IDLE;
return;
} else {
tries++;
}
break;
case State::COLLECTING_DATA_AUTO:
case State::DATA_COLLECTED:
// first measurement in auto mode (COLLECTING_DATA_AUTO state) require device reconfiguration
if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->als_readings_)) {
this->state_ = State::ADJUSTMENT_IN_PROGRESS;
ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
get_itime_ms(this->als_readings_.integration_time));
this->configure_integration_time_(this->als_readings_.integration_time);
this->configure_gain_(this->als_readings_.gain);
// if sensitivity adjustment needed - need to wait for first data samples after setting new parameters
this->set_timeout(2 * get_meas_time_ms(this->repeat_rate_),
[this]() { this->state_ = State::WAITING_FOR_DATA; });
} else {
this->state_ = State::READY_TO_PUBLISH;
}
break;
case State::ADJUSTMENT_IN_PROGRESS:
// nothing to be done, just waiting for the timeout
break;
case State::READY_TO_PUBLISH:
this->publish_data_part_1_(this->als_readings_);
this->state_ = State::KEEP_PUBLISHING;
break;
case State::KEEP_PUBLISHING:
this->publish_data_part_2_(this->als_readings_);
this->status_clear_warning();
this->state_ = State::IDLE;
break;
default:
break;
}
}
void LTRAlsPs501Component::check_and_trigger_ps_() {
static uint32_t last_high_trigger_time{0};
static uint32_t last_low_trigger_time{0};
uint16_t ps_data = this->read_ps_data_();
uint32_t now = millis();
if (ps_data != this->ps_readings_) {
this->ps_readings_ = ps_data;
// Higher values - object is closer to sensor
if (ps_data > this->ps_threshold_high_ && now - last_high_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
last_high_trigger_time = now;
ESP_LOGD(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data,
this->ps_threshold_high_);
this->on_ps_high_trigger_callback_.call();
} else if (ps_data < this->ps_threshold_low_ && now - last_low_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
last_low_trigger_time = now;
ESP_LOGD(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data,
this->ps_threshold_low_);
this->on_ps_low_trigger_callback_.call();
}
}
}
bool LTRAlsPs501Component::check_part_number_() {
uint8_t manuf_id = this->reg((uint8_t) CommandRegisters::MANUFAC_ID).get();
if (manuf_id != 0x05) { // 0x05 is Lite-On Semiconductor Corp. ID
ESP_LOGW(TAG, "Unknown manufacturer ID: 0x%02X", manuf_id);
this->mark_failed();
return false;
}
// Things getting not really funny here, we can't identify device type by part number ID
// ======================== ========= ===== =================
// Device Part ID Rev Capabilities
// ======================== ========= ===== =================
// ltr-558als 0x08 0 als + ps
// ltr-501als 0x08 0 als + ps
// ltr-301als - 0x08 0 als only
PartIdRegister part_id{0};
part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get();
if (part_id.part_number_id != 0x08) {
ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. LTR-501/301 shall have 0x08. It might not work properly.",
part_id.part_number_id);
this->status_set_warning();
return true;
}
return true;
}
void LTRAlsPs501Component::configure_reset_() {
ESP_LOGV(TAG, "Resetting");
AlsControlRegister501 als_ctrl{0};
als_ctrl.sw_reset = true;
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
uint8_t tries = MAX_TRIES;
do {
ESP_LOGV(TAG, "Waiting chip to reset");
delay(2);
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
} while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting
if (als_ctrl.sw_reset) {
ESP_LOGW(TAG, "Reset failed");
}
}
void LTRAlsPs501Component::configure_als_() {
AlsControlRegister501 als_ctrl{0};
als_ctrl.sw_reset = false;
als_ctrl.als_mode_active = true;
als_ctrl.gain = this->gain_;
ESP_LOGV(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw);
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(5);
uint8_t tries = MAX_TRIES;
do {
ESP_LOGV(TAG, "Waiting for ALS device to become active...");
delay(2);
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
} while (!als_ctrl.als_mode_active && tries--); // while active mode is not set - keep waiting
if (!als_ctrl.als_mode_active) {
ESP_LOGW(TAG, "Failed to activate ALS device");
}
}
void LTRAlsPs501Component::configure_ps_() {
PsMeasurementRateRegister ps_meas{0};
ps_meas.ps_measurement_rate = PsMeasurementRate::PS_MEAS_RATE_50MS;
this->reg((uint8_t) CommandRegisters::PS_MEAS_RATE) = ps_meas.raw;
PsControlRegister501 ps_ctrl{0};
ps_ctrl.ps_mode_active = true;
ps_ctrl.ps_mode_xxx = true;
this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw;
}
uint16_t LTRAlsPs501Component::read_ps_data_() {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.ps_new_data) {
return this->ps_readings_;
}
uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get();
PsData1Register ps_high;
ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get();
uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low);
return val;
}
void LTRAlsPs501Component::configure_gain_(AlsGain501 gain) {
AlsControlRegister501 als_ctrl{0};
als_ctrl.als_mode_active = true;
als_ctrl.gain = gain;
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
AlsControlRegister501 read_als_ctrl{0};
read_als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
if (read_als_ctrl.gain != gain) {
ESP_LOGW(TAG, "Failed to set gain. We will try one more time.");
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
}
}
void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time) {
MeasurementRateRegister501 meas{0};
meas.measurement_repeat_rate = this->repeat_rate_;
meas.integration_time = time;
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
delay(2);
MeasurementRateRegister501 read_meas{0};
read_meas.raw = this->reg((uint8_t) CommandRegisters::MEAS_RATE).get();
if (read_meas.integration_time != time) {
ESP_LOGW(TAG, "Failed to set integration time. We will try one more time.");
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
delay(2);
}
}
DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.als_new_data)
return DataAvail::NO_DATA;
ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
if (data.gain != als_status.gain) {
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
return DataAvail::BAD_DATA;
}
data.gain = als_status.gain;
return DataAvail::DATA_OK;
}
void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) {
data.ch1 = 0;
data.ch0 = 0;
uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_0).get();
uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_1).get();
uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_0).get();
uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_1).get();
data.ch1 = encode_uint16(ch1_1, ch1_0);
data.ch0 = encode_uint16(ch0_1, ch0_0);
ESP_LOGD(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0);
}
bool LTRAlsPs501Component::are_adjustments_required_(AlsReadings &data) {
if (!this->automatic_mode_enabled_)
return false;
// sometimes sensors fail to change sensitivity. this prevents us from infinite loop
if (data.number_of_adjustments++ > MAX_SENSITIVITY_ADJUSTMENTS) {
ESP_LOGW(TAG, "Too many sensitivity adjustments done. Something wrong with the sensor. Stopping.");
return false;
}
ESP_LOGV(TAG, "Adjusting sensitivity, run #%d", data.number_of_adjustments);
// available combinations of gain and integration times:
static const GainTimePair GAIN_TIME_PAIRS[] = {
{AlsGain501::GAIN_1, INTEGRATION_TIME_50MS}, {AlsGain501::GAIN_1, INTEGRATION_TIME_100MS},
{AlsGain501::GAIN_150, INTEGRATION_TIME_100MS}, {AlsGain501::GAIN_150, INTEGRATION_TIME_200MS},
{AlsGain501::GAIN_150, INTEGRATION_TIME_400MS},
};
GainTimePair current_pair = {data.gain, data.integration_time};
// Here comes funky business with this sensor. it has no internal error checking mechanism
// as in later versions (LTR-303/329/559/..) and sensor gets overwhelmed when saturated
// and readings are strange. We only check high sensitivity mode for now.
// Nothing is documented and it is a result of real-world testing.
if (data.gain == AlsGain501::GAIN_150) {
// when sensor is saturated it returns various crazy numbers
// CH1 = 1, CH0 = 0
if (data.ch1 == 1 && data.ch0 == 0) {
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 1, CH0 = 0, Gain 150x");
// fake saturation
data.ch0 = 0xffff;
data.ch1 = 0xffff;
} else if (data.ch1 == 65535 && data.ch0 == 0) {
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = 65535, CH0 = 0, Gain 150x");
data.ch0 = 0xffff;
} else if (data.ch1 > 1000 && data.ch0 == 0) {
ESP_LOGV(TAG, "Looks like sensor got saturated (?) CH1 = %d, CH0 = 0, Gain 150x", data.ch1);
data.ch0 = 0xffff;
}
}
static const uint16_t LOW_INTENSITY_THRESHOLD_1 = 100;
static const uint16_t LOW_INTENSITY_THRESHOLD_200 = 2000;
static const uint16_t HIGH_INTENSITY_THRESHOLD = 25000;
if (data.ch0 <= (data.gain == AlsGain501::GAIN_1 ? LOW_INTENSITY_THRESHOLD_1 : LOW_INTENSITY_THRESHOLD_200) ||
(data.gain == AlsGain501::GAIN_1 && data.lux < 320)) {
GainTimePair next_pair = get_next(GAIN_TIME_PAIRS, current_pair);
if (next_pair != current_pair) {
data.gain = next_pair.gain;
data.integration_time = next_pair.time;
ESP_LOGV(TAG, "Low illuminance. Increasing sensitivity.");
return true;
}
} else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD || data.ch1 >= HIGH_INTENSITY_THRESHOLD) {
GainTimePair prev_pair = get_prev(GAIN_TIME_PAIRS, current_pair);
if (prev_pair != current_pair) {
data.gain = prev_pair.gain;
data.integration_time = prev_pair.time;
ESP_LOGV(TAG, "High illuminance. Decreasing sensitivity.");
return true;
}
} else {
ESP_LOGD(TAG, "Illuminance is good enough.");
return false;
}
ESP_LOGD(TAG, "Can't adjust sensitivity anymore.");
return false;
}
void LTRAlsPs501Component::apply_lux_calculation_(AlsReadings &data) {
if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) {
ESP_LOGW(TAG, "Sensors got saturated");
data.lux = 0.0f;
return;
}
if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) {
ESP_LOGW(TAG, "Sensors blacked out");
data.lux = 0.0f;
return;
}
float ch0 = data.ch0;
float ch1 = data.ch1;
float ratio = ch1 / (ch0 + ch1);
float als_gain = get_gain_coeff(data.gain);
float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f;
float inv_pfactor = this->glass_attenuation_factor_;
float lux = 0.0f;
// method from
// https://github.com/fards/Ainol_fire_kernel/blob/83832cf8a3082fd8e963230f4b1984479d1f1a84/customer/drivers/lightsensor/ltr501als.c#L295
if (ratio < 0.45) {
lux = 1.7743 * ch0 + 1.1059 * ch1;
} else if (ratio < 0.64) {
lux = 3.7725 * ch0 - 1.3363 * ch1;
} else if (ratio < 0.85) {
lux = 1.6903 * ch0 - 0.1693 * ch1;
} else {
ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
lux = 0.0f;
}
lux = inv_pfactor * lux / als_gain / als_time;
data.lux = lux;
ESP_LOGD(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain,
als_time, inv_pfactor, lux);
}
void LTRAlsPs501Component::publish_data_part_1_(AlsReadings &data) {
if (this->proximity_counts_sensor_ != nullptr) {
this->proximity_counts_sensor_->publish_state(this->ps_readings_);
}
if (this->ambient_light_sensor_ != nullptr) {
this->ambient_light_sensor_->publish_state(data.lux);
}
if (this->infrared_counts_sensor_ != nullptr) {
this->infrared_counts_sensor_->publish_state(data.ch1);
}
if (this->full_spectrum_counts_sensor_ != nullptr) {
this->full_spectrum_counts_sensor_->publish_state(data.ch0);
}
}
void LTRAlsPs501Component::publish_data_part_2_(AlsReadings &data) {
if (this->actual_gain_sensor_ != nullptr) {
this->actual_gain_sensor_->publish_state(get_gain_coeff(data.gain));
}
if (this->actual_integration_time_sensor_ != nullptr) {
this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time));
}
}
} // namespace ltr501
} // namespace esphome

View file

@ -0,0 +1,184 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/optional.h"
#include "esphome/core/automation.h"
#include "ltr_definitions_501.h"
namespace esphome {
namespace ltr501 {
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
enum LtrType : uint8_t {
LTR_TYPE_UNKNOWN = 0,
LTR_TYPE_ALS_ONLY = 1,
LTR_TYPE_PS_ONLY = 2,
LTR_TYPE_ALS_AND_PS = 3,
};
class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
public:
//
// EspHome framework functions
//
float get_setup_priority() const override { return setup_priority::DATA; }
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
// Configuration setters : General
//
void set_ltr_type(LtrType type) { this->ltr_type_ = type; }
// Configuration setters : ALS
//
void set_als_auto_mode(bool enable) { this->automatic_mode_enabled_ = enable; }
void set_als_gain(AlsGain501 gain) { this->gain_ = gain; }
void set_als_integration_time(IntegrationTime501 time) { this->integration_time_ = time; }
void set_als_meas_repeat_rate(MeasurementRepeatRate rate) { this->repeat_rate_ = rate; }
void set_als_glass_attenuation_factor(float factor) { this->glass_attenuation_factor_ = factor; }
// Configuration setters : PS
//
void set_ps_high_threshold(uint16_t threshold) { this->ps_threshold_high_ = threshold; }
void set_ps_low_threshold(uint16_t threshold) { this->ps_threshold_low_ = threshold; }
void set_ps_cooldown_time_s(uint16_t time) { this->ps_cooldown_time_s_ = time; }
void set_ps_gain(PsGain501 gain) { this->ps_gain_ = gain; }
// Sensors setters
//
void set_ambient_light_sensor(sensor::Sensor *sensor) { this->ambient_light_sensor_ = sensor; }
void set_full_spectrum_counts_sensor(sensor::Sensor *sensor) { this->full_spectrum_counts_sensor_ = sensor; }
void set_infrared_counts_sensor(sensor::Sensor *sensor) { this->infrared_counts_sensor_ = sensor; }
void set_actual_gain_sensor(sensor::Sensor *sensor) { this->actual_gain_sensor_ = sensor; }
void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; }
void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; }
protected:
//
// Internal state machine, used to split all the actions into
// small steps in loop() to make sure we are not blocking execution
//
enum class State : uint8_t {
NOT_INITIALIZED,
DELAYED_SETUP,
IDLE,
WAITING_FOR_DATA,
COLLECTING_DATA_AUTO,
DATA_COLLECTED,
ADJUSTMENT_IN_PROGRESS,
READY_TO_PUBLISH,
KEEP_PUBLISHING
} state_{State::NOT_INITIALIZED};
LtrType ltr_type_{LtrType::LTR_TYPE_ALS_ONLY};
//
// Current measurements data
//
struct AlsReadings {
uint16_t ch0{0};
uint16_t ch1{0};
AlsGain501 gain{AlsGain501::GAIN_1};
IntegrationTime501 integration_time{IntegrationTime501::INTEGRATION_TIME_100MS};
float lux{0.0f};
uint8_t number_of_adjustments{0};
} als_readings_;
uint16_t ps_readings_{0xfffe};
inline bool is_als_() const {
return this->ltr_type_ == LtrType::LTR_TYPE_ALS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS;
}
inline bool is_ps_() const {
return this->ltr_type_ == LtrType::LTR_TYPE_PS_ONLY || this->ltr_type_ == LtrType::LTR_TYPE_ALS_AND_PS;
}
//
// Device interaction and data manipulation
//
bool check_part_number_();
void configure_reset_();
void configure_als_();
void configure_integration_time_(IntegrationTime501 time);
void configure_gain_(AlsGain501 gain);
DataAvail is_als_data_ready_(AlsReadings &data);
void read_sensor_data_(AlsReadings &data);
bool are_adjustments_required_(AlsReadings &data);
void apply_lux_calculation_(AlsReadings &data);
void publish_data_part_1_(AlsReadings &data);
void publish_data_part_2_(AlsReadings &data);
void configure_ps_();
uint16_t read_ps_data_();
void check_and_trigger_ps_();
//
// Component configuration
//
bool automatic_mode_enabled_{false};
AlsGain501 gain_{AlsGain501::GAIN_1};
IntegrationTime501 integration_time_{IntegrationTime501::INTEGRATION_TIME_100MS};
MeasurementRepeatRate repeat_rate_{MeasurementRepeatRate::REPEAT_RATE_500MS};
float glass_attenuation_factor_{1.0};
uint16_t ps_cooldown_time_s_{5};
PsGain501 ps_gain_{PsGain501::PS_GAIN_1};
uint16_t ps_threshold_high_{0xffff};
uint16_t ps_threshold_low_{0x0000};
//
// Sensors for publishing data
//
sensor::Sensor *infrared_counts_sensor_{nullptr}; // direct reading CH1, infrared only
sensor::Sensor *full_spectrum_counts_sensor_{nullptr}; // direct reading CH0, infrared + visible light
sensor::Sensor *ambient_light_sensor_{nullptr}; // calculated lux
sensor::Sensor *actual_gain_sensor_{nullptr}; // actual gain of reading
sensor::Sensor *actual_integration_time_sensor_{nullptr}; // actual integration time
sensor::Sensor *proximity_counts_sensor_{nullptr}; // proximity sensor
bool is_any_als_sensor_enabled_() const {
return this->ambient_light_sensor_ != nullptr || this->full_spectrum_counts_sensor_ != nullptr ||
this->infrared_counts_sensor_ != nullptr || this->actual_gain_sensor_ != nullptr ||
this->actual_integration_time_sensor_ != nullptr;
}
bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; }
//
// Trigger section for the automations
//
friend class LTRPsHighTrigger;
friend class LTRPsLowTrigger;
CallbackManager<void()> on_ps_high_trigger_callback_;
CallbackManager<void()> on_ps_low_trigger_callback_;
void add_on_ps_high_trigger_callback_(std::function<void()> callback) {
this->on_ps_high_trigger_callback_.add(std::move(callback));
}
void add_on_ps_low_trigger_callback_(std::function<void()> callback) {
this->on_ps_low_trigger_callback_.add(std::move(callback));
}
};
class LTRPsHighTrigger : public Trigger<> {
public:
explicit LTRPsHighTrigger(LTRAlsPs501Component *parent) {
parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); });
}
};
class LTRPsLowTrigger : public Trigger<> {
public:
explicit LTRPsLowTrigger(LTRAlsPs501Component *parent) {
parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); });
}
};
} // namespace ltr501
} // namespace esphome

View file

@ -0,0 +1,260 @@
#pragma once
#include <cstdint>
namespace esphome {
namespace ltr501 {
enum class CommandRegisters : uint8_t {
ALS_CONTR = 0x80, // ALS operation mode control and SW reset
PS_CONTR = 0x81, // PS operation mode control
PS_LED = 0x82, // PS LED pulse frequency control
PS_N_PULSES = 0x83, // PS number of pulses control
PS_MEAS_RATE = 0x84, // PS measurement rate in active mode
MEAS_RATE = 0x85, // ALS measurement rate in active mode
PART_ID = 0x86, // Part Number ID and Revision ID
MANUFAC_ID = 0x87, // Manufacturer ID
ALS_DATA_CH1_0 = 0x88, // ALS measurement CH1 data, lower byte - infrared only
ALS_DATA_CH1_1 = 0x89, // ALS measurement CH1 data, upper byte - infrared only
ALS_DATA_CH0_0 = 0x8A, // ALS measurement CH0 data, lower byte - visible + infrared
ALS_DATA_CH0_1 = 0x8B, // ALS measurement CH0 data, upper byte - visible + infrared
ALS_PS_STATUS = 0x8C, // ALS PS new data status
PS_DATA_0 = 0x8D, // PS measurement data, lower byte
PS_DATA_1 = 0x8E, // PS measurement data, upper byte
ALS_PS_INTERRUPT = 0x8F, // Interrupt status
PS_THRES_UP_0 = 0x90, // PS interrupt upper threshold, lower byte
PS_THRES_UP_1 = 0x91, // PS interrupt upper threshold, upper byte
PS_THRES_LOW_0 = 0x92, // PS interrupt lower threshold, lower byte
PS_THRES_LOW_1 = 0x93, // PS interrupt lower threshold, upper byte
PS_OFFSET_1 = 0x94, // PS offset, upper byte
PS_OFFSET_0 = 0x95, // PS offset, lower byte
// 0x96 - reserved
ALS_THRES_UP_0 = 0x97, // ALS interrupt upper threshold, lower byte
ALS_THRES_UP_1 = 0x98, // ALS interrupt upper threshold, upper byte
ALS_THRES_LOW_0 = 0x99, // ALS interrupt lower threshold, lower byte
ALS_THRES_LOW_1 = 0x9A, // ALS interrupt lower threshold, upper byte
// 0x9B - reserved
// 0x9C - reserved
// 0x9D - reserved
INTERRUPT_PERSIST = 0x9E // Interrupt persistence filter
};
// ALS Sensor gain levels
enum AlsGain501 : uint8_t {
GAIN_1 = 0, // GAIN_RANGE_2 // default
GAIN_150 = 1, // GAIN_RANGE_1
};
static const uint8_t GAINS_COUNT = 2;
// ALS Sensor integration times
enum IntegrationTime501 : uint8_t {
INTEGRATION_TIME_100MS = 0, // default
INTEGRATION_TIME_50MS = 1, // only in Dynamic GAIN_RANGE_2
INTEGRATION_TIME_200MS = 2, // only in Dynamic GAIN_RANGE_1
INTEGRATION_TIME_400MS = 3, // only in Dynamic GAIN_RANGE_1
};
static const uint8_t TIMES_COUNT = 4;
// ALS Sensor measurement repeat rate
enum MeasurementRepeatRate {
REPEAT_RATE_50MS = 0,
REPEAT_RATE_100MS = 1,
REPEAT_RATE_200MS = 2,
REPEAT_RATE_500MS = 3, // default
REPEAT_RATE_1000MS = 4,
REPEAT_RATE_2000MS = 5
};
// PS Sensor gain levels
enum PsGain501 : uint8_t {
PS_GAIN_1 = 0, // default
PS_GAIN_4 = 1,
PS_GAIN_8 = 2,
PS_GAIN_16 = 3,
};
// LED Pulse Modulation Frequency
enum PsLedFreq : uint8_t {
PS_LED_FREQ_30KHZ = 0,
PS_LED_FREQ_40KHZ = 1,
PS_LED_FREQ_50KHZ = 2,
PS_LED_FREQ_60KHZ = 3, // default
PS_LED_FREQ_70KHZ = 4,
PS_LED_FREQ_80KHZ = 5,
PS_LED_FREQ_90KHZ = 6,
PS_LED_FREQ_100KHZ = 7,
};
// LED current duty
enum PsLedDuty : uint8_t {
PS_LED_DUTY_25 = 0,
PS_LED_DUTY_50 = 1, // default
PS_LED_DUTY_75 = 2,
PS_LED_DUTY_100 = 3,
};
// LED pulsed current level
enum PsLedCurrent : uint8_t {
PS_LED_CURRENT_5MA = 0,
PS_LED_CURRENT_10MA = 1,
PS_LED_CURRENT_20MA = 2,
PS_LED_CURRENT_50MA = 3, // default
PS_LED_CURRENT_100MA = 4,
PS_LED_CURRENT_100MA1 = 5,
PS_LED_CURRENT_100MA2 = 6,
PS_LED_CURRENT_100MA3 = 7,
};
// PS measurement rate
enum PsMeasurementRate : uint8_t {
PS_MEAS_RATE_50MS = 0,
PS_MEAS_RATE_70MS = 1,
PS_MEAS_RATE_100MS = 2, // default
PS_MEAS_RATE_200MS = 3,
PS_MEAS_RATE_500MS = 4,
PS_MEAS_RATE_1000MS = 5,
PS_MEAS_RATE_2000MS = 6,
PS_MEAS_RATE_2000MS1 = 7,
};
//
// ALS_CONTR Register (0x80)
//
union AlsControlRegister501 {
uint8_t raw;
struct {
bool asl_mode_xxx : 1;
bool als_mode_active : 1;
bool sw_reset : 1;
AlsGain501 gain : 1;
uint8_t reserved : 4;
} __attribute__((packed));
};
//
// PS_CONTR Register (0x81)
//
union PsControlRegister501 {
uint8_t raw;
struct {
bool ps_mode_xxx : 1;
bool ps_mode_active : 1;
PsGain501 ps_gain : 2;
bool reserved_4 : 1;
bool reserved_5 : 1;
bool reserved_6 : 1;
bool reserved_7 : 1;
} __attribute__((packed));
};
//
// PS_LED Register (0x82)
//
union PsLedRegister {
uint8_t raw;
struct {
PsLedCurrent ps_led_current : 3;
PsLedDuty ps_led_duty : 2;
PsLedFreq ps_led_freq : 3;
} __attribute__((packed));
};
//
// PS_N_PULSES Register (0x83)
//
union PsNPulsesRegister501 {
uint8_t raw;
uint8_t number_of_pulses;
};
//
// PS_MEAS_RATE Register (0x84)
//
union PsMeasurementRateRegister {
uint8_t raw;
struct {
PsMeasurementRate ps_measurement_rate : 4;
uint8_t reserved : 4;
} __attribute__((packed));
};
//
// ALS_MEAS_RATE Register (0x85)
//
union MeasurementRateRegister501 {
uint8_t raw;
struct {
MeasurementRepeatRate measurement_repeat_rate : 3;
IntegrationTime501 integration_time : 2;
bool reserved_5 : 1;
bool reserved_6 : 1;
bool reserved_7 : 1;
} __attribute__((packed));
};
//
// PART_ID Register (0x86) (Read Only)
//
union PartIdRegister {
uint8_t raw;
struct {
uint8_t part_number_id : 4;
uint8_t revision_id : 4;
} __attribute__((packed));
};
//
// ALS_PS_STATUS Register (0x8C) (Read Only)
//
union AlsPsStatusRegister {
uint8_t raw;
struct {
bool ps_new_data : 1; // 0 - old data, 1 - new data
bool ps_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active
bool als_new_data : 1; // 0 - old data, 1 - new data
bool als_interrupt : 1; // 0 - interrupt signal not active, 1 - interrupt signal active
AlsGain501 gain : 1; // current ALS gain
bool reserved_5 : 1;
bool reserved_6 : 1;
bool reserved_7 : 1;
} __attribute__((packed));
};
//
// PS_DATA_1 Register (0x8E) (Read Only)
//
union PsData1Register {
uint8_t raw;
struct {
uint8_t ps_data_high : 3;
uint8_t reserved : 4;
bool ps_saturation_flag : 1;
} __attribute__((packed));
};
//
// INTERRUPT Register (0x8F) (Read Only)
//
union InterruptRegister {
uint8_t raw;
struct {
bool ps_interrupt : 1;
bool als_interrupt : 1;
bool interrupt_polarity : 1; // 0 - active low (default), 1 - active high
uint8_t reserved : 5;
} __attribute__((packed));
};
//
// INTERRUPT_PERSIST Register (0x9E)
//
union InterruptPersistRegister {
uint8_t raw;
struct {
uint8_t als_persist : 4; // 0 - every ALS cycle, 1 - every 2 ALS cycles, ... 15 - every 16 ALS cycles
uint8_t ps_persist : 4; // 0 - every PS cycle, 1 - every 2 PS cycles, ... 15 - every 16 PS cycles
} __attribute__((packed));
};
} // namespace ltr501
} // namespace esphome

View file

@ -0,0 +1,274 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ACTUAL_GAIN,
CONF_ACTUAL_INTEGRATION_TIME,
CONF_AMBIENT_LIGHT,
CONF_AUTO_MODE,
CONF_FULL_SPECTRUM_COUNTS,
CONF_GAIN,
CONF_GLASS_ATTENUATION_FACTOR,
CONF_ID,
CONF_INTEGRATION_TIME,
CONF_NAME,
CONF_REPEAT,
CONF_TRIGGER_ID,
CONF_TYPE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_ILLUMINANCE,
ICON_BRIGHTNESS_5,
ICON_BRIGHTNESS_6,
ICON_TIMER,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
UNIT_MILLISECOND,
)
CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["i2c"]
CONF_INFRARED_COUNTS = "infrared_counts"
CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold"
CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold"
CONF_PS_COOLDOWN = "ps_cooldown"
CONF_PS_COUNTS = "ps_counts"
CONF_PS_GAIN = "ps_gain"
CONF_PS_HIGH_THRESHOLD = "ps_high_threshold"
CONF_PS_LOW_THRESHOLD = "ps_low_threshold"
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
ICON_GAIN = "mdi:multiplication"
ICON_PROXIMITY = "mdi:hand-wave-outline"
UNIT_COUNTS = "#"
ltr501_ns = cg.esphome_ns.namespace("ltr501")
LTRAlsPsComponent = ltr501_ns.class_(
"LTRAlsPs501Component", cg.PollingComponent, i2c.I2CDevice
)
LtrType = ltr501_ns.enum("LtrType")
LTR_TYPES = {
"ALS": LtrType.LTR_TYPE_ALS_ONLY,
"PS": LtrType.LTR_TYPE_PS_ONLY,
"ALS_PS": LtrType.LTR_TYPE_ALS_AND_PS,
}
AlsGain = ltr501_ns.enum("AlsGain501")
ALS_GAINS = {
"1X": AlsGain.GAIN_1,
"150X": AlsGain.GAIN_150,
}
IntegrationTime = ltr501_ns.enum("IntegrationTime501")
INTEGRATION_TIMES = {
50: IntegrationTime.INTEGRATION_TIME_50MS,
100: IntegrationTime.INTEGRATION_TIME_100MS,
200: IntegrationTime.INTEGRATION_TIME_200MS,
400: IntegrationTime.INTEGRATION_TIME_400MS,
}
MeasurementRepeatRate = ltr501_ns.enum("MeasurementRepeatRate")
MEASUREMENT_REPEAT_RATES = {
50: MeasurementRepeatRate.REPEAT_RATE_50MS,
100: MeasurementRepeatRate.REPEAT_RATE_100MS,
200: MeasurementRepeatRate.REPEAT_RATE_200MS,
500: MeasurementRepeatRate.REPEAT_RATE_500MS,
1000: MeasurementRepeatRate.REPEAT_RATE_1000MS,
2000: MeasurementRepeatRate.REPEAT_RATE_2000MS,
}
PsGain = ltr501_ns.enum("PsGain501")
PS_GAINS = {
"1X": PsGain.PS_GAIN_1,
"4X": PsGain.PS_GAIN_4,
"8X": PsGain.PS_GAIN_8,
"16X": PsGain.PS_GAIN_16,
}
LTRPsHighTrigger = ltr501_ns.class_("LTRPsHighTrigger", automation.Trigger.template())
LTRPsLowTrigger = ltr501_ns.class_("LTRPsLowTrigger", automation.Trigger.template())
def validate_integration_time(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
return cv.enum(INTEGRATION_TIMES, int=True)(value)
def validate_repeat_rate(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
return cv.enum(MEASUREMENT_REPEAT_RATES, int=True)(value)
def validate_time_and_repeat_rate(config):
integraton_time = config[CONF_INTEGRATION_TIME]
repeat_rate = config[CONF_REPEAT]
if integraton_time > repeat_rate:
raise cv.Invalid(
f"Measurement repeat rate ({repeat_rate}ms) shall be greater or equal to integration time ({integraton_time}ms)"
)
return config
def validate_als_gain_and_integration_time(config):
integraton_time = config[CONF_INTEGRATION_TIME]
if config[CONF_GAIN] == "1X" and integraton_time > 100:
raise cv.Invalid(
"ALS gain 1X can only be used with integration time 50ms or 100ms"
)
if config[CONF_GAIN] == "200X" and integraton_time == 50:
raise cv.Invalid("ALS gain 200X can not be used with integration time 50ms")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LTRAlsPsComponent),
cv.Optional(CONF_TYPE, default="ALS_PS"): cv.enum(LTR_TYPES, upper=True),
cv.Optional(CONF_AUTO_MODE, default=True): cv.boolean,
cv.Optional(CONF_GAIN, default="1X"): cv.enum(ALS_GAINS, upper=True),
cv.Optional(
CONF_INTEGRATION_TIME, default="100ms"
): validate_integration_time,
cv.Optional(CONF_REPEAT, default="500ms"): validate_repeat_rate,
cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range(
min=1.0
),
cv.Optional(
CONF_PS_COOLDOWN, default="5s"
): cv.positive_time_period_seconds,
cv.Optional(CONF_PS_GAIN, default="1X"): cv.enum(PS_GAINS, upper=True),
cv.Optional(CONF_PS_HIGH_THRESHOLD, default=65535): cv.int_range(
min=0, max=65535
),
cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range(
min=0, max=65535
),
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger),
}
),
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger),
}
),
cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
icon=ICON_BRIGHTNESS_6,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_INFRARED_COUNTS): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_FULL_SPECTRUM_COUNTS): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_7,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_PS_COUNTS): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_PROXIMITY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DISTANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_ACTUAL_GAIN): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_GAIN,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_ACTUAL_INTEGRATION_TIME): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_MILLISECOND,
icon=ICON_TIMER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x23)),
validate_time_and_repeat_rate,
validate_als_gain_and_integration_time,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if als_config := config.get(CONF_AMBIENT_LIGHT):
sens = await sensor.new_sensor(als_config)
cg.add(var.set_ambient_light_sensor(sens))
if infrared_cnt_config := config.get(CONF_INFRARED_COUNTS):
sens = await sensor.new_sensor(infrared_cnt_config)
cg.add(var.set_infrared_counts_sensor(sens))
if full_spect_cnt_config := config.get(CONF_FULL_SPECTRUM_COUNTS):
sens = await sensor.new_sensor(full_spect_cnt_config)
cg.add(var.set_full_spectrum_counts_sensor(sens))
if act_gain_config := config.get(CONF_ACTUAL_GAIN):
sens = await sensor.new_sensor(act_gain_config)
cg.add(var.set_actual_gain_sensor(sens))
if act_itime_config := config.get(CONF_ACTUAL_INTEGRATION_TIME):
sens = await sensor.new_sensor(act_itime_config)
cg.add(var.set_actual_integration_time_sensor(sens))
if prox_cnt_config := config.get(CONF_PS_COUNTS):
sens = await sensor.new_sensor(prox_cnt_config)
cg.add(var.set_proximity_counts_sensor(sens))
for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_high_tr)
for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_low_tr)
cg.add(var.set_ltr_type(config[CONF_TYPE]))
cg.add(var.set_als_auto_mode(config[CONF_AUTO_MODE]))
cg.add(var.set_als_gain(config[CONF_GAIN]))
cg.add(var.set_als_integration_time(config[CONF_INTEGRATION_TIME]))
cg.add(var.set_als_meas_repeat_rate(config[CONF_REPEAT]))
cg.add(var.set_als_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR]))
cg.add(var.set_ps_cooldown_time_s(config[CONF_PS_COOLDOWN]))
cg.add(var.set_ps_gain(config[CONF_PS_GAIN]))
cg.add(var.set_ps_high_threshold(config[CONF_PS_HIGH_THRESHOLD]))
cg.add(var.set_ps_low_threshold(config[CONF_PS_LOW_THRESHOLD]))

View file

@ -4,8 +4,10 @@ from esphome import automation
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import ( from esphome.const import (
CONF_ACTUAL_GAIN, CONF_ACTUAL_GAIN,
CONF_ACTUAL_INTEGRATION_TIME,
CONF_AMBIENT_LIGHT, CONF_AMBIENT_LIGHT,
CONF_AUTO_MODE, CONF_AUTO_MODE,
CONF_FULL_SPECTRUM_COUNTS,
CONF_GAIN, CONF_GAIN,
CONF_GLASS_ATTENUATION_FACTOR, CONF_GLASS_ATTENUATION_FACTOR,
CONF_ID, CONF_ID,
@ -27,8 +29,6 @@ from esphome.const import (
CODEOWNERS = ["@latonita"] CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time"
CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts"
CONF_INFRARED_COUNTS = "infrared_counts" CONF_INFRARED_COUNTS = "infrared_counts"
CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold" CONF_ON_PS_HIGH_THRESHOLD = "on_ps_high_threshold"
CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold" CONF_ON_PS_LOW_THRESHOLD = "on_ps_low_threshold"

View file

@ -22,8 +22,9 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code from .automation import disp_update, focused_widgets, update_to_code
from .defines import CONF_ADJUSTABLE, CONF_SKIP from .defines import add_define
from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code from .encoders import ENCODERS_CONFIG, encoders_to_code, initial_focus_to_code
from .gradient import GRADIENT_SCHEMA, gradients_to_code
from .lv_validation import lv_bool, lv_images_used from .lv_validation import lv_bool, lv_images_used
from .lvcode import LvContext, LvglComponent from .lvcode import LvContext, LvglComponent
from .schemas import ( from .schemas import (
@ -128,17 +129,6 @@ for w_type in WIDGET_TYPES.values():
)(update_to_code) )(update_to_code)
lv_defines = {} # Dict of #defines to provide as build flags
def add_define(macro, value="1"):
if macro in lv_defines and lv_defines[macro] != value:
LOGGER.error(
"Redefinition of %s - was %s now %s", macro, lv_defines[macro], value
)
lv_defines[macro] = value
def as_macro(macro, value): def as_macro(macro, value):
if value is None: if value is None:
return f"#define {macro}" return f"#define {macro}"
@ -153,14 +143,14 @@ LV_CONF_H_FORMAT = """\
def generate_lv_conf_h(): def generate_lv_conf_h():
definitions = [as_macro(m, v) for m, v in lv_defines.items()] definitions = [as_macro(m, v) for m, v in df.lv_defines.items()]
definitions.sort() definitions.sort()
return LV_CONF_H_FORMAT.format("\n".join(definitions)) return LV_CONF_H_FORMAT.format("\n".join(definitions))
def final_validation(config): def final_validation(config):
if pages := config.get(CONF_PAGES): if pages := config.get(CONF_PAGES):
if all(p[CONF_SKIP] for p in pages): if all(p[df.CONF_SKIP] for p in pages):
raise cv.Invalid("At least one page must not be skipped") raise cv.Invalid("At least one page must not be skipped")
global_config = full_config.get() global_config = full_config.get()
for display_id in config[df.CONF_DISPLAYS]: for display_id in config[df.CONF_DISPLAYS]:
@ -185,7 +175,7 @@ def final_validation(config):
for w in focused_widgets: for w in focused_widgets:
path = global_config.get_path_for_id(w) path = global_config.get_path_for_id(w)
widget_conf = global_config.get_config_for_path(path[:-1]) widget_conf = global_config.get_config_for_path(path[:-1])
if CONF_ADJUSTABLE in widget_conf and not widget_conf[CONF_ADJUSTABLE]: if df.CONF_ADJUSTABLE in widget_conf and not widget_conf[df.CONF_ADJUSTABLE]:
raise cv.Invalid( raise cv.Invalid(
"A non adjustable arc may not be focused", "A non adjustable arc may not be focused",
path, path,
@ -268,6 +258,7 @@ async def to_code(config):
await encoders_to_code(lv_component, config) await encoders_to_code(lv_component, config)
await theme_to_code(config) await theme_to_code(config)
await styles_to_code(config) await styles_to_code(config)
await gradients_to_code(config)
await set_obj_properties(lv_scr_act, config) await set_obj_properties(lv_scr_act, config)
await add_widgets(lv_scr_act, config) await add_widgets(lv_scr_act, config)
await add_pages(lv_component, config) await add_pages(lv_component, config)
@ -351,6 +342,7 @@ CONFIG_SCHEMA = (
cv.Optional(df.CONF_THEME): cv.Schema( cv.Optional(df.CONF_THEME): cv.Schema(
{cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()} {cv.Optional(name): obj_schema(w) for name, w in WIDGET_TYPES.items()}
), ),
cv.Optional(df.CONF_GRADIENTS): GRADIENT_SCHEMA,
cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema, cv.Optional(df.CONF_TOUCHSCREENS, default=None): touchscreen_schema,
cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG, cv.Optional(df.CONF_ENCODERS, default=None): ENCODERS_CONFIG,
cv.GenerateID(df.CONF_DEFAULT_GROUP): cv.declare_id(lv_group_t), cv.GenerateID(df.CONF_DEFAULT_GROUP): cv.declare_id(lv_group_t),

View file

@ -229,19 +229,23 @@ async def obj_hide_to_code(config, action_id, template_arg, args):
async def do_hide(widget: Widget): async def do_hide(widget: Widget):
widget.add_flag("LV_OBJ_FLAG_HIDDEN") widget.add_flag("LV_OBJ_FLAG_HIDDEN")
return await action_to_code( widgets = [
await get_widgets(config), do_hide, action_id, template_arg, args widget.outer if widget.outer else widget for widget in await get_widgets(config)
) ]
return await action_to_code(widgets, do_hide, action_id, template_arg, args)
@automation.register_action("lvgl.widget.show", ObjUpdateAction, LIST_ACTION_SCHEMA) @automation.register_action("lvgl.widget.show", ObjUpdateAction, LIST_ACTION_SCHEMA)
async def obj_show_to_code(config, action_id, template_arg, args): async def obj_show_to_code(config, action_id, template_arg, args):
async def do_show(widget: Widget): async def do_show(widget: Widget):
widget.clear_flag("LV_OBJ_FLAG_HIDDEN") widget.clear_flag("LV_OBJ_FLAG_HIDDEN")
if widget.move_to_foreground:
lv_obj.move_foreground(widget.obj)
return await action_to_code( widgets = [
await get_widgets(config), do_show, action_id, template_arg, args widget.outer if widget.outer else widget for widget in await get_widgets(config)
) ]
return await action_to_code(widgets, do_show, action_id, template_arg, args)
def focused_id(value): def focused_id(value):

View file

@ -4,6 +4,8 @@ Constants already defined in esphome.const are not duplicated here and must be i
""" """
import logging
from esphome import codegen as cg, config_validation as cv from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS from esphome.const import CONF_ITEMS
from esphome.core import Lambda from esphome.core import Lambda
@ -13,8 +15,19 @@ from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from .helpers import requires_component from .helpers import requires_component
LOGGER = logging.getLogger(__name__)
lvgl_ns = cg.esphome_ns.namespace("lvgl") lvgl_ns = cg.esphome_ns.namespace("lvgl")
lv_defines = {} # Dict of #defines to provide as build flags
def add_define(macro, value="1"):
if macro in lv_defines and lv_defines[macro] != value:
LOGGER.error(
"Redefinition of %s - was %s now %s", macro, lv_defines[macro], value
)
lv_defines[macro] = value
def literal(arg): def literal(arg):
if isinstance(arg, str): if isinstance(arg, str):
@ -173,6 +186,9 @@ LV_ANIM = LvConstant(
"OUT_BOTTOM", "OUT_BOTTOM",
) )
LV_GRAD_DIR = LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER")
LV_DITHER = LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF")
LOG_LEVELS = ( LOG_LEVELS = (
"TRACE", "TRACE",
"INFO", "INFO",
@ -374,6 +390,7 @@ CONF_ANTIALIAS = "antialias"
CONF_ARC_LENGTH = "arc_length" CONF_ARC_LENGTH = "arc_length"
CONF_AUTO_START = "auto_start" CONF_AUTO_START = "auto_start"
CONF_BACKGROUND_STYLE = "background_style" CONF_BACKGROUND_STYLE = "background_style"
CONF_BUTTON_STYLE = "button_style"
CONF_DECIMAL_PLACES = "decimal_places" CONF_DECIMAL_PLACES = "decimal_places"
CONF_COLUMN = "column" CONF_COLUMN = "column"
CONF_DIGITS = "digits" CONF_DIGITS = "digits"
@ -405,6 +422,7 @@ CONF_FLEX_ALIGN_TRACK = "flex_align_track"
CONF_FLEX_GROW = "flex_grow" CONF_FLEX_GROW = "flex_grow"
CONF_FREEZE = "freeze" CONF_FREEZE = "freeze"
CONF_FULL_REFRESH = "full_refresh" CONF_FULL_REFRESH = "full_refresh"
CONF_GRADIENTS = "gradients"
CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos" CONF_GRID_CELL_ROW_POS = "grid_cell_row_pos"
CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos" CONF_GRID_CELL_COLUMN_POS = "grid_cell_column_pos"
CONF_GRID_CELL_ROW_SPAN = "grid_cell_row_span" CONF_GRID_CELL_ROW_SPAN = "grid_cell_row_span"

View file

@ -0,0 +1,61 @@
from esphome import config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_COLOR,
CONF_DIRECTION,
CONF_DITHER,
CONF_ID,
CONF_POSITION,
)
from esphome.cpp_generator import MockObj
from .defines import CONF_GRADIENTS, LV_DITHER, LV_GRAD_DIR, add_define
from .lv_validation import lv_color, lv_fraction
from .lvcode import lv_assign
from .types import lv_gradient_t
CONF_STOPS = "stops"
def min_stops(value):
if len(value) < 2:
raise cv.Invalid("Must have at least 2 stops")
return value
GRADIENT_SCHEMA = cv.ensure_list(
cv.Schema(
{
cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t),
cv.Optional(CONF_DIRECTION, default="NONE"): LV_GRAD_DIR.one_of,
cv.Optional(CONF_DITHER, default="NONE"): LV_DITHER.one_of,
cv.Required(CONF_STOPS): cv.All(
[
cv.Schema(
{
cv.Required(CONF_COLOR): lv_color,
cv.Required(CONF_POSITION): lv_fraction,
}
)
],
min_stops,
),
}
)
)
async def gradients_to_code(config):
max_stops = 2
for gradient in config.get(CONF_GRADIENTS, ()):
var = MockObj(cg.new_Pvariable(gradient[CONF_ID]), "->")
max_stops = max(max_stops, len(gradient[CONF_STOPS]))
lv_assign(var.dir, await LV_GRAD_DIR.process(gradient[CONF_DIRECTION]))
lv_assign(var.dither, await LV_DITHER.process(gradient[CONF_DITHER]))
lv_assign(var.stops_count, len(gradient[CONF_STOPS]))
for index, stop in enumerate(gradient[CONF_STOPS]):
lv_assign(var.stops[index].color, await lv_color.process(stop[CONF_COLOR]))
lv_assign(
var.stops[index].frac, await lv_fraction.process(stop[CONF_POSITION])
)
add_define("LV_GRADIENT_MAX_STOPS", max_stops)

View file

@ -1,12 +1,19 @@
from typing import Union from typing import Union
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.color import ColorStruct from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
from esphome.components.font import Font from esphome.components.font import Font
from esphome.components.image import Image_ from esphome.components.image import Image_
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ARGS, CONF_COLOR, CONF_FORMAT, CONF_TIME, CONF_VALUE from esphome.const import (
from esphome.core import HexInt, Lambda CONF_ARGS,
CONF_COLOR,
CONF_FORMAT,
CONF_ID,
CONF_TIME,
CONF_VALUE,
)
from esphome.core import CORE, ID, Lambda
from esphome.cpp_generator import MockObj from esphome.cpp_generator import MockObj
from esphome.cpp_types import ESPTime, uint32 from esphome.cpp_types import ESPTime, uint32
from esphome.helpers import cpp_string_escape from esphome.helpers import cpp_string_escape
@ -23,14 +30,9 @@ from .defines import (
call_lambda, call_lambda,
literal, literal,
) )
from .helpers import ( from .helpers import esphome_fonts_used, lv_fonts_used, requires_component
esphome_fonts_used,
lv_fonts_used,
lvgl_components_required,
requires_component,
)
from .lvcode import lv_expr from .lvcode import lv_expr
from .types import lv_font_t, lv_img_t from .types import lv_font_t, lv_gradient_t, lv_img_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@ -59,11 +61,17 @@ def color_retmapper(value):
if isinstance(value, cv.Lambda): if isinstance(value, cv.Lambda):
return cv.returning_lambda(value) return cv.returning_lambda(value)
if isinstance(value, int): if isinstance(value, int):
hexval = HexInt(value) return literal(
return lv_expr.color_hex(hexval) f"lv_color_make({(value >> 16) & 0xFF}, {(value >> 8) & 0xFF}, {value & 0xFF})"
# Must be an id )
lvgl_components_required.add(CONF_COLOR) if isinstance(value, ID):
return lv_expr.color_from(MockObj(value)) cval = [x for x in CORE.config[CONF_COLOR] if x[CONF_ID] == value][0]
if CONF_HEX in cval:
r, g, b = cval[CONF_HEX]
else:
r, g, b, _ = from_rgbw(cval)
return literal(f"lv_color_make({r}, {g}, {b})")
assert False
def option_string(value): def option_string(value):
@ -132,7 +140,7 @@ radius_consts = LvConstant("LV_RADIUS_", "CIRCLE")
@schema_extractor("one_of") @schema_extractor("one_of")
def radius_validator(value): def fraction_validator(value):
if value == SCHEMA_EXTRACT: if value == SCHEMA_EXTRACT:
return radius_consts.choices return radius_consts.choices
value = cv.Any(size, cv.percentage, radius_consts.one_of)(value) value = cv.Any(size, cv.percentage, radius_consts.one_of)(value)
@ -141,7 +149,7 @@ def radius_validator(value):
return value return value
radius = LValidator(radius_validator, uint32, retmapper=literal) lv_fraction = LValidator(fraction_validator, uint32, retmapper=literal)
def id_name(value): def id_name(value):
@ -242,6 +250,21 @@ lv_int = LValidator(cv.int_, cg.int_)
lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255)) lv_brightness = LValidator(cv.percentage, cg.float_, retmapper=lambda x: int(x * 255))
def gradient_mapper(value):
return MockObj(value)
def gradient_validator(value):
return cv.use_id(lv_gradient_t)(value)
lv_gradient = LValidator(
validator=gradient_validator,
rtype=lv_gradient_t,
retmapper=gradient_mapper,
)
def is_lv_font(font): def is_lv_font(font):
return isinstance(font, str) and font.lower() in LV_FONTS return isinstance(font, str) and font.lower() in LV_FONTS

View file

@ -184,8 +184,9 @@ class LvContext(LambdaContext):
self.lv_component = lv_component self.lv_component = lv_component
async def add_init_lambda(self): async def add_init_lambda(self):
cg.add(self.lv_component.add_init_lambda(await self.get_lambda())) if self.code_list:
LvContext.added_lambda_count += 1 cg.add(self.lv_component.add_init_lambda(await self.get_lambda()))
LvContext.added_lambda_count += 1
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
await super().__aexit__(exc_type, exc_val, exc_tb) await super().__aexit__(exc_type, exc_val, exc_tb)

View file

@ -42,9 +42,6 @@ extern lv_event_code_t lv_api_event; // NOLINT
extern lv_event_code_t lv_update_event; // NOLINT extern lv_event_code_t lv_update_event; // NOLINT
extern std::string lv_event_code_name_for(uint8_t event_code); extern std::string lv_event_code_name_for(uint8_t event_code);
extern bool lv_is_pre_initialise(); extern bool lv_is_pre_initialise();
#ifdef USE_LVGL_COLOR
inline lv_color_t lv_color_from(Color color) { return lv_color_make(color.red, color.green, color.blue); }
#endif // USE_LVGL_COLOR
#if LV_COLOR_DEPTH == 16 #if LV_COLOR_DEPTH == 16
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565; static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565;
#elif LV_COLOR_DEPTH == 32 #elif LV_COLOR_DEPTH == 32

View file

@ -17,9 +17,9 @@ from esphome.core import TimePeriod
from esphome.schema_extractors import SCHEMA_EXTRACT from esphome.schema_extractors import SCHEMA_EXTRACT
from . import defines as df, lv_validation as lvalid from . import defines as df, lv_validation as lvalid
from .defines import CONF_TIME_FORMAT from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
from .helpers import add_lv_use, requires_component, validate_printf from .helpers import add_lv_use, requires_component, validate_printf
from .lv_validation import lv_color, lv_font, lv_image from .lv_validation import lv_color, lv_font, lv_gradient, lv_image
from .lvcode import LvglComponent, lv_event_t_ptr from .lvcode import LvglComponent, lv_event_t_ptr
from .types import ( from .types import (
LVEncoderListener, LVEncoderListener,
@ -94,9 +94,10 @@ STYLE_PROPS = {
"arc_width": cv.positive_int, "arc_width": cv.positive_int,
"anim_time": lvalid.lv_milliseconds, "anim_time": lvalid.lv_milliseconds,
"bg_color": lvalid.lv_color, "bg_color": lvalid.lv_color,
"bg_grad": lv_gradient,
"bg_grad_color": lvalid.lv_color, "bg_grad_color": lvalid.lv_color,
"bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of, "bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of,
"bg_grad_dir": df.LvConstant("LV_GRAD_DIR_", "NONE", "HOR", "VER").one_of, "bg_grad_dir": LV_GRAD_DIR.one_of,
"bg_grad_stop": lvalid.stop_value, "bg_grad_stop": lvalid.stop_value,
"bg_image_opa": lvalid.opacity, "bg_image_opa": lvalid.opacity,
"bg_image_recolor": lvalid.lv_color, "bg_image_recolor": lvalid.lv_color,
@ -160,7 +161,7 @@ STYLE_PROPS = {
"max_width": lvalid.pixels_or_percent, "max_width": lvalid.pixels_or_percent,
"min_height": lvalid.pixels_or_percent, "min_height": lvalid.pixels_or_percent,
"min_width": lvalid.pixels_or_percent, "min_width": lvalid.pixels_or_percent,
"radius": lvalid.radius, "radius": lvalid.lv_fraction,
"width": lvalid.size, "width": lvalid.size,
"x": lvalid.pixels_or_percent, "x": lvalid.pixels_or_percent,
"y": lvalid.pixels_or_percent, "y": lvalid.pixels_or_percent,

View file

@ -59,6 +59,7 @@ LVEncoderListener = lvgl_ns.class_("LVEncoderListener")
lv_obj_t = LvType("lv_obj_t") lv_obj_t = LvType("lv_obj_t")
lv_page_t = LvType("LvPageType", parents=(LvCompound,)) lv_page_t = LvType("LvPageType", parents=(LvCompound,))
lv_img_t = LvType("lv_img_t") lv_img_t = LvType("lv_img_t")
lv_gradient_t = LvType("lv_grad_dsc_t")
LV_EVENT = MockObj(base="LV_EVENT_", op="") LV_EVENT = MockObj(base="LV_EVENT_", op="")
LV_STATE = MockObj(base="LV_STATE_", op="") LV_STATE = MockObj(base="LV_STATE_", op="")

View file

@ -89,6 +89,8 @@ class Widget:
self.obj = MockObj(f"{self.var}->obj") self.obj = MockObj(f"{self.var}->obj")
else: else:
self.obj = var self.obj = var
self.outer = None
self.move_to_foreground = False
@staticmethod @staticmethod
def create(name, var, wtype: WidgetType, config: dict = None): def create(name, var, wtype: WidgetType, config: dict = None):

View file

@ -5,6 +5,7 @@ from esphome.const import (
CONF_COLOR, CONF_COLOR,
CONF_COUNT, CONF_COUNT,
CONF_ID, CONF_ID,
CONF_ITEMS,
CONF_LENGTH, CONF_LENGTH,
CONF_LOCAL, CONF_LOCAL,
CONF_RANGE_FROM, CONF_RANGE_FROM,
@ -17,6 +18,7 @@ from esphome.const import (
from ..automation import action_to_code from ..automation import action_to_code
from ..defines import ( from ..defines import (
CONF_END_VALUE, CONF_END_VALUE,
CONF_INDICATOR,
CONF_MAIN, CONF_MAIN,
CONF_PIVOT_X, CONF_PIVOT_X,
CONF_PIVOT_Y, CONF_PIVOT_Y,
@ -165,7 +167,12 @@ METER_SCHEMA = {cv.Optional(CONF_SCALES): cv.ensure_list(SCALE_SCHEMA)}
class MeterType(WidgetType): class MeterType(WidgetType):
def __init__(self): def __init__(self):
super().__init__(CONF_METER, lv_meter_t, (CONF_MAIN,), METER_SCHEMA) super().__init__(
CONF_METER,
lv_meter_t,
(CONF_MAIN, CONF_INDICATOR, CONF_TICKS, CONF_ITEMS),
METER_SCHEMA,
)
async def to_code(self, w: Widget, config): async def to_code(self, w: Widget, config):
"""For a meter object, create and set parameters""" """For a meter object, create and set parameters"""

View file

@ -1,11 +1,12 @@
from esphome import config_validation as cv from esphome import config_validation as cv
from esphome.const import CONF_BUTTON, CONF_ID, CONF_TEXT from esphome.const import CONF_BUTTON, CONF_ID, CONF_ITEMS, CONF_TEXT
from esphome.core import ID from esphome.core import ID
from esphome.cpp_generator import new_Pvariable, static_const_array from esphome.cpp_generator import new_Pvariable, static_const_array
from esphome.cpp_types import nullptr from esphome.cpp_types import nullptr
from ..defines import ( from ..defines import (
CONF_BODY, CONF_BODY,
CONF_BUTTON_STYLE,
CONF_BUTTONS, CONF_BUTTONS,
CONF_CLOSE_BUTTON, CONF_CLOSE_BUTTON,
CONF_MSGBOXES, CONF_MSGBOXES,
@ -25,7 +26,7 @@ from ..lvcode import (
lv_obj, lv_obj,
lv_Pvariable, lv_Pvariable,
) )
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..styles import TOP_LAYER from ..styles import TOP_LAYER
from ..types import LV_EVENT, char_ptr, lv_obj_t from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, set_obj_properties from . import Widget, set_obj_properties
@ -48,9 +49,10 @@ MSGBOX_SCHEMA = container_schema(
{ {
cv.GenerateID(CONF_ID): cv.declare_id(lv_obj_t), cv.GenerateID(CONF_ID): cv.declare_id(lv_obj_t),
cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA, cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA,
cv.Optional(CONF_BODY): STYLED_TEXT_SCHEMA, cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA,
cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA), cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA),
cv.Optional(CONF_CLOSE_BUTTON): lv_bool, cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec),
cv.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool,
cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr), cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
} }
), ),
@ -74,7 +76,8 @@ async def msgbox_to_code(conf):
) )
lvgl_components_required.add("BUTTONMATRIX") lvgl_components_required.add("BUTTONMATRIX")
messagebox_id = conf[CONF_ID] messagebox_id = conf[CONF_ID]
outer = lv_Pvariable(lv_obj_t, messagebox_id.id) outer_id = f"{messagebox_id.id}_outer"
outer = lv_Pvariable(lv_obj_t, messagebox_id.id + "_outer")
buttonmatrix = new_Pvariable( buttonmatrix = new_Pvariable(
ID( ID(
f"{messagebox_id.id}_buttonmatrix_", f"{messagebox_id.id}_buttonmatrix_",
@ -82,8 +85,11 @@ async def msgbox_to_code(conf):
type=lv_buttonmatrix_t, type=lv_buttonmatrix_t,
) )
) )
msgbox = lv_Pvariable(lv_obj_t, f"{messagebox_id.id}_msgbox") msgbox = lv_Pvariable(lv_obj_t, messagebox_id.id)
outer_widget = Widget.create(messagebox_id, outer, obj_spec, conf) outer_widget = Widget.create(outer_id, outer, obj_spec, conf)
outer_widget.move_to_foreground = True
msgbox_widget = Widget.create(messagebox_id, msgbox, obj_spec, conf)
msgbox_widget.outer = outer_widget
buttonmatrix_widget = Widget.create( buttonmatrix_widget = Widget.create(
str(buttonmatrix), buttonmatrix, buttonmatrix_spec, conf str(buttonmatrix), buttonmatrix, buttonmatrix_spec, conf
) )
@ -92,10 +98,8 @@ async def msgbox_to_code(conf):
) )
text_id = conf[CONF_BUTTON_TEXT_LIST_ID] text_id = conf[CONF_BUTTON_TEXT_LIST_ID]
text_list = static_const_array(text_id, text_list) text_list = static_const_array(text_id, text_list)
if (text := conf.get(CONF_BODY)) is not None: text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, ""))
text = await lv_text.process(text.get(CONF_TEXT)) title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, ""))
if (title := conf.get(CONF_TITLE)) is not None:
title = await lv_text.process(title.get(CONF_TEXT))
close_button = conf[CONF_CLOSE_BUTTON] close_button = conf[CONF_CLOSE_BUTTON]
lv_assign(outer, lv_expr.obj_create(TOP_LAYER)) lv_assign(outer, lv_expr.obj_create(TOP_LAYER))
lv_obj.set_width(outer, lv_pct(100)) lv_obj.set_width(outer, lv_pct(100))
@ -111,20 +115,27 @@ async def msgbox_to_code(conf):
) )
lv_obj.set_style_align(msgbox, literal("LV_ALIGN_CENTER"), 0) lv_obj.set_style_align(msgbox, literal("LV_ALIGN_CENTER"), 0)
lv_add(buttonmatrix.set_obj(lv_expr.msgbox_get_btns(msgbox))) lv_add(buttonmatrix.set_obj(lv_expr.msgbox_get_btns(msgbox)))
await set_obj_properties(outer_widget, conf) if button_style := conf.get(CONF_BUTTON_STYLE):
button_style = {CONF_ITEMS: button_style}
await set_obj_properties(buttonmatrix_widget, button_style)
await set_obj_properties(msgbox_widget, conf)
async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
if close_button: if close_button:
async with LambdaContext(EVENT_ARG, where=messagebox_id) as context:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
with LocalVariable( with LocalVariable(
"close_btn_", lv_obj_t, lv_expr.msgbox_get_close_btn(msgbox) "close_btn_", lv_obj_t, lv_expr.msgbox_get_close_btn(msgbox)
) as close_btn: ) as close_btn:
lv_obj.remove_event_cb(close_btn, nullptr) lv_obj.remove_event_cb(close_btn, nullptr)
lv_obj.add_event_cb( lv_obj.add_event_cb(
close_btn, close_btn,
await context.get_lambda(), await close_action.get_lambda(),
LV_EVENT.CLICKED, LV_EVENT.CLICKED,
nullptr, nullptr,
) )
else:
lv_obj.add_event_cb(
outer, await close_action.get_lambda(), LV_EVENT.CLICKED, nullptr
)
if len(ctrl_list) != 0 or len(width_list) != 0: if len(ctrl_list) != 0 or len(width_list) != 0:
set_btn_data(buttonmatrix.obj, ctrl_list, width_list) set_btn_data(buttonmatrix.obj, ctrl_list, width_list)

View file

@ -32,6 +32,12 @@ void MAX31856Sensor::dump_config() {
LOG_PIN(" CS Pin: ", this->cs_); LOG_PIN(" CS Pin: ", this->cs_);
ESP_LOGCONFIG(TAG, " Mains Filter: %s", ESP_LOGCONFIG(TAG, " Mains Filter: %s",
(filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!"))); (filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!")));
if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) {
ESP_LOGCONFIG(TAG, " Thermocouple Type: Unknown");
} else {
ESP_LOGCONFIG(TAG, " Thermocouple Type: %c", "BEJKNRST"[this->thermocouple_type_]);
}
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }
@ -129,7 +135,12 @@ void MAX31856Sensor::clear_fault_() {
} }
void MAX31856Sensor::set_thermocouple_type_() { void MAX31856Sensor::set_thermocouple_type_() {
MAX31856ThermocoupleType type = MAX31856_TCTYPE_K; MAX31856ThermocoupleType type;
if (this->thermocouple_type_ < 0 || this->thermocouple_type_ > 7) {
type = MAX31856_TCTYPE_K;
} else {
type = this->thermocouple_type_;
}
ESP_LOGCONFIG(TAG, "set_thermocouple_type_: 0x%02X", type); ESP_LOGCONFIG(TAG, "set_thermocouple_type_: 0x%02X", type);
uint8_t t = this->read_register_(MAX31856_CR1_REG); uint8_t t = this->read_register_(MAX31856_CR1_REG);
t &= 0xF0; // mask off bottom 4 bits t &= 0xF0; // mask off bottom 4 bits

View file

@ -50,7 +50,6 @@ enum MAX31856Registers {
/** /**
* Multiple types of thermocouples supported by the chip. * Multiple types of thermocouples supported by the chip.
* Currently only K type implemented here.
*/ */
enum MAX31856ThermocoupleType { enum MAX31856ThermocoupleType {
MAX31856_TCTYPE_B = 0b0000, // 0x00 MAX31856_TCTYPE_B = 0b0000, // 0x00
@ -78,11 +77,15 @@ class MAX31856Sensor : public sensor::Sensor,
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_filter(MAX31856ConfigFilter filter) { filter_ = filter; } void set_filter(MAX31856ConfigFilter filter) { this->filter_ = filter; }
void set_thermocouple_type(MAX31856ThermocoupleType thermocouple_type) {
this->thermocouple_type_ = thermocouple_type;
}
void update() override; void update() override;
protected: protected:
MAX31856ConfigFilter filter_; MAX31856ConfigFilter filter_;
MAX31856ThermocoupleType thermocouple_type_;
uint8_t read_register_(uint8_t reg); uint8_t read_register_(uint8_t reg);
uint32_t read_register24_(uint8_t reg); uint32_t read_register24_(uint8_t reg);

View file

@ -3,6 +3,7 @@ from esphome.components import sensor, spi
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_MAINS_FILTER, CONF_MAINS_FILTER,
CONF_THERMOCOUPLE_TYPE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
@ -18,6 +19,17 @@ FILTER = {
50: MAX31865ConfigFilter.FILTER_50HZ, 50: MAX31865ConfigFilter.FILTER_50HZ,
60: MAX31865ConfigFilter.FILTER_60HZ, 60: MAX31865ConfigFilter.FILTER_60HZ,
} }
MAX31856ThermocoupleType = max31856_ns.enum("MAX31856ThermocoupleType")
THERMOCOUPLE_TYPE = {
"B": MAX31856ThermocoupleType.MAX31856_TCTYPE_B,
"E": MAX31856ThermocoupleType.MAX31856_TCTYPE_E,
"J": MAX31856ThermocoupleType.MAX31856_TCTYPE_J,
"K": MAX31856ThermocoupleType.MAX31856_TCTYPE_K,
"N": MAX31856ThermocoupleType.MAX31856_TCTYPE_N,
"R": MAX31856ThermocoupleType.MAX31856_TCTYPE_R,
"S": MAX31856ThermocoupleType.MAX31856_TCTYPE_S,
"T": MAX31856ThermocoupleType.MAX31856_TCTYPE_T,
}
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
sensor.sensor_schema( sensor.sensor_schema(
@ -34,6 +46,13 @@ CONFIG_SCHEMA = (
), ),
} }
) )
.extend(
{
cv.Optional(CONF_THERMOCOUPLE_TYPE, default="K"): cv.enum(
THERMOCOUPLE_TYPE, upper=True, space=""
),
}
)
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(spi.spi_device_schema()) .extend(spi.spi_device_schema())
) )
@ -44,3 +63,4 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
cg.add(var.set_filter(config[CONF_MAINS_FILTER])) cg.add(var.set_filter(config[CONF_MAINS_FILTER]))
cg.add(var.set_thermocouple_type(config[CONF_THERMOCOUPLE_TYPE]))

View file

@ -1,14 +1,14 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_THERMOCOUPLE_TYPE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
) )
CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
CONF_HOT_JUNCTION = "hot_junction" CONF_HOT_JUNCTION = "hot_junction"
CONF_COLD_JUNCTION = "cold_junction" CONF_COLD_JUNCTION = "cold_junction"

View file

@ -1,17 +1,17 @@
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DISABLED,
CONF_ID, CONF_ID,
CONF_PORT, CONF_PORT,
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_SERVICES,
CONF_SERVICE, CONF_SERVICE,
CONF_SERVICES,
KEY_CORE, KEY_CORE,
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
CONF_DISABLED,
) )
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.components.esp32 import add_idf_component
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
@ -91,7 +91,7 @@ async def to_code(config):
add_idf_component( add_idf_component(
name="mdns", name="mdns",
repo="https://github.com/espressif/esp-protocols.git", repo="https://github.com/espressif/esp-protocols.git",
ref="mdns-v1.2.5", ref="mdns-v1.3.2",
path="components/mdns", path="components/mdns",
) )

View file

@ -37,6 +37,7 @@ struct MediaPlayerSupportedFormat {
uint32_t sample_rate; uint32_t sample_rate;
uint32_t num_channels; uint32_t num_channels;
MediaPlayerFormatPurpose purpose; MediaPlayerFormatPurpose purpose;
uint32_t sample_bytes;
}; };
class MediaPlayer; class MediaPlayer;

View file

@ -4,8 +4,6 @@ import logging
from pathlib import Path from pathlib import Path
from urllib.parse import urljoin from urllib.parse import urljoin
import requests
from esphome import automation, external_files, git from esphome import automation, external_files, git
from esphome.automation import register_action, register_condition from esphome.automation import register_action, register_condition
import esphome.codegen as cg import esphome.codegen as cg
@ -26,7 +24,6 @@ from esphome.const import (
CONF_USERNAME, CONF_USERNAME,
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
__version__,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
@ -179,26 +176,6 @@ def _convert_manifest_v1_to_v2(v1_manifest):
return v2_manifest return v2_manifest
def _download_file(url: str, path: Path) -> bytes:
if not external_files.has_remote_file_changed(url, path):
_LOGGER.debug("Remote file has not changed, skipping download")
return path.read_bytes()
try:
req = requests.get(
url,
timeout=external_files.NETWORK_TIMEOUT,
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download file from {url}: {e}") from e
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(req.content)
return req.content
def _validate_manifest_version(manifest_data): def _validate_manifest_version(manifest_data):
if manifest_version := manifest_data.get(KEY_VERSION): if manifest_version := manifest_data.get(KEY_VERSION):
if manifest_version == 1: if manifest_version == 1:
@ -223,7 +200,7 @@ def _process_http_source(config):
json_path = path / "manifest.json" json_path = path / "manifest.json"
json_contents = _download_file(url, json_path) json_contents = external_files.download_content(url, json_path)
manifest_data = json.loads(json_contents) manifest_data = json.loads(json_contents)
if not isinstance(manifest_data, dict): if not isinstance(manifest_data, dict):
@ -234,7 +211,7 @@ def _process_http_source(config):
model_path = path / model model_path = path / model
_download_file(str(model_url), model_path) external_files.download_content(str(model_url), model_path)
return config return config

View file

@ -1,25 +1,29 @@
import binascii import binascii
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import modbus from esphome.components import modbus
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_ID, CONF_ID,
CONF_NAME,
CONF_LAMBDA, CONF_LAMBDA,
CONF_NAME,
CONF_OFFSET, CONF_OFFSET,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
) )
from esphome.cpp_helpers import logging from esphome.cpp_helpers import logging
from .const import ( from .const import (
CONF_ALLOW_DUPLICATE_COMMANDS,
CONF_BITMASK, CONF_BITMASK,
CONF_BYTE_OFFSET, CONF_BYTE_OFFSET,
CONF_COMMAND_THROTTLE, CONF_COMMAND_THROTTLE,
CONF_OFFLINE_SKIP_UPDATES,
CONF_CUSTOM_COMMAND, CONF_CUSTOM_COMMAND,
CONF_FORCE_NEW_RANGE, CONF_FORCE_NEW_RANGE,
CONF_MAX_CMD_RETRIES,
CONF_MODBUS_CONTROLLER_ID, CONF_MODBUS_CONTROLLER_ID,
CONF_OFFLINE_SKIP_UPDATES,
CONF_ON_COMMAND_SENT, CONF_ON_COMMAND_SENT,
CONF_REGISTER_COUNT, CONF_REGISTER_COUNT,
CONF_REGISTER_TYPE, CONF_REGISTER_TYPE,
@ -126,9 +130,11 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ModbusController), cv.GenerateID(): cv.declare_id(ModbusController),
cv.Optional(CONF_ALLOW_DUPLICATE_COMMANDS, default=False): cv.boolean,
cv.Optional( cv.Optional(
CONF_COMMAND_THROTTLE, default="0ms" CONF_COMMAND_THROTTLE, default="0ms"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MAX_CMD_RETRIES, default=4): cv.positive_int,
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int, cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
cv.Optional( cv.Optional(
CONF_SERVER_REGISTERS, CONF_SERVER_REGISTERS,
@ -253,7 +259,9 @@ async def add_modbus_base_properties(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS]))
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
cg.add(var.set_max_cmd_retries(config[CONF_MAX_CMD_RETRIES]))
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
if CONF_SERVER_REGISTERS in config: if CONF_SERVER_REGISTERS in config:
for server_register in config[CONF_SERVER_REGISTERS]: for server_register in config[CONF_SERVER_REGISTERS]:

View file

@ -1,16 +1,16 @@
import esphome.codegen as cg
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ADDRESS, CONF_ID from esphome.const import CONF_ADDRESS, CONF_ID
from .. import ( from .. import (
add_modbus_base_properties, MODBUS_REGISTER_TYPE,
modbus_controller_ns,
modbus_calc_properties,
validate_modbus_register,
ModbusItemBaseSchema, ModbusItemBaseSchema,
SensorItem, SensorItem,
MODBUS_REGISTER_TYPE, add_modbus_base_properties,
modbus_calc_properties,
modbus_controller_ns,
validate_modbus_register,
) )
from ..const import ( from ..const import (
CONF_BITMASK, CONF_BITMASK,

View file

@ -1,9 +1,11 @@
CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands"
CONF_BITMASK = "bitmask" CONF_BITMASK = "bitmask"
CONF_BYTE_OFFSET = "byte_offset" CONF_BYTE_OFFSET = "byte_offset"
CONF_COMMAND_THROTTLE = "command_throttle" CONF_COMMAND_THROTTLE = "command_throttle"
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates" CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
CONF_CUSTOM_COMMAND = "custom_command" CONF_CUSTOM_COMMAND = "custom_command"
CONF_FORCE_NEW_RANGE = "force_new_range" CONF_FORCE_NEW_RANGE = "force_new_range"
CONF_MAX_CMD_RETRIES = "max_cmd_retries"
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
CONF_ON_COMMAND_SENT = "on_command_sent" CONF_ON_COMMAND_SENT = "on_command_sent"

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