mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'dev' into MCP3204-differential-mode
This commit is contained in:
commit
dffce90ec8
72 changed files with 763 additions and 303 deletions
6
.github/actions/build-image/action.yaml
vendored
6
.github/actions/build-image/action.yaml
vendored
|
@ -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
91
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
|
@ -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.14-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 '*'
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -137,7 +137,8 @@ void BL0942::setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
||||||
|
@ -196,6 +197,7 @@ 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, " Current reference: %f", this->current_reference_);
|
||||||
|
|
|
@ -93,6 +93,7 @@ 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) {
|
void set_current_reference(float current_ref) {
|
||||||
this->current_reference_ = current_ref;
|
this->current_reference_ = current_ref;
|
||||||
this->current_reference_set_ = true;
|
this->current_reference_set_ = true;
|
||||||
|
@ -137,6 +138,7 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
|
||||||
float energy_reference_ = BL0942_EREF;
|
float energy_reference_ = BL0942_EREF;
|
||||||
bool energy_reference_set_ = false;
|
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 rx_start_ = 0;
|
||||||
uint32_t prev_cf_cnt_ = 0;
|
uint32_t prev_cf_cnt_ = 0;
|
||||||
|
|
|
@ -27,6 +27,7 @@ from esphome.const import (
|
||||||
CONF_CURRENT_REFERENCE = "current_reference"
|
CONF_CURRENT_REFERENCE = "current_reference"
|
||||||
CONF_ENERGY_REFERENCE = "energy_reference"
|
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||||
CONF_POWER_REFERENCE = "power_reference"
|
CONF_POWER_REFERENCE = "power_reference"
|
||||||
|
CONF_RESET = "reset"
|
||||||
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
@ -58,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,
|
||||||
),
|
),
|
||||||
|
@ -82,6 +83,7 @@ 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_CURRENT_REFERENCE): cv.float_,
|
||||||
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
||||||
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
||||||
|
@ -115,6 +117,7 @@ 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:
|
if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None:
|
||||||
cg.add(var.set_current_reference(current_reference))
|
cg.add(var.set_current_reference(current_reference))
|
||||||
if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None:
|
if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None:
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, )
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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_);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -170,13 +170,11 @@ def _notify_old_style(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
# NOTE: Keep this in mind when updating the recommended version:
|
|
||||||
# * For all constants below, update platformio.ini (in this repo)
|
|
||||||
# The dev and latest branches will be at *least* this version, which is what matters.
|
# The dev and latest branches will be at *least* this version, which is what matters.
|
||||||
ARDUINO_VERSIONS = {
|
ARDUINO_VERSIONS = {
|
||||||
"dev": (cv.Version(1, 7, 0), "https://github.com/libretiny-eu/libretiny.git"),
|
"dev": (cv.Version(1, 7, 0), "https://github.com/libretiny-eu/libretiny.git"),
|
||||||
"latest": (cv.Version(1, 7, 0), "libretiny"),
|
"latest": (cv.Version(1, 7, 0), "libretiny"),
|
||||||
"recommended": (cv.Version(1, 5, 1), None),
|
"recommended": (cv.Version(1, 7, 0), None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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",
|
||||||
|
@ -406,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"
|
||||||
|
|
61
esphome/components/lvgl/gradient.py
Normal file
61
esphome/components/lvgl/gradient.py
Normal 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)
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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="")
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ from .const import (
|
||||||
CONF_CUSTOM_COMMAND,
|
CONF_CUSTOM_COMMAND,
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
|
CONF_MAX_CMD_RETRIES,
|
||||||
CONF_ON_COMMAND_SENT,
|
CONF_ON_COMMAND_SENT,
|
||||||
CONF_REGISTER_COUNT,
|
CONF_REGISTER_COUNT,
|
||||||
CONF_REGISTER_TYPE,
|
CONF_REGISTER_TYPE,
|
||||||
|
@ -131,6 +132,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
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,
|
||||||
|
@ -257,6 +259,7 @@ 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_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]:
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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"
|
||||||
|
|
|
@ -18,11 +18,11 @@ void ModbusController::setup() { this->create_register_ranges_(); }
|
||||||
bool ModbusController::send_next_command_() {
|
bool ModbusController::send_next_command_() {
|
||||||
uint32_t last_send = millis() - this->last_command_timestamp_;
|
uint32_t last_send = millis() - this->last_command_timestamp_;
|
||||||
|
|
||||||
if ((last_send > this->command_throttle_) && !waiting_for_response() && !command_queue_.empty()) {
|
if ((last_send > this->command_throttle_) && !waiting_for_response() && !this->command_queue_.empty()) {
|
||||||
auto &command = command_queue_.front();
|
auto &command = this->command_queue_.front();
|
||||||
|
|
||||||
// remove from queue if command was sent too often
|
// remove from queue if command was sent too often
|
||||||
if (command->send_countdown < 1) {
|
if (!command->should_retry(this->max_cmd_retries_)) {
|
||||||
if (!this->module_offline_) {
|
if (!this->module_offline_) {
|
||||||
ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
|
ESP_LOGW(TAG, "Modbus device=%d set offline", this->address_);
|
||||||
|
|
||||||
|
@ -34,11 +34,9 @@ bool ModbusController::send_next_command_() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->module_offline_ = true;
|
this->module_offline_ = true;
|
||||||
ESP_LOGD(
|
ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue",
|
||||||
TAG,
|
this->address_, command->register_address);
|
||||||
"Modbus command to device=%d register=0x%02X countdown=%d no response received - removed from send queue",
|
this->command_queue_.pop_front();
|
||||||
this->address_, command->register_address, command->send_countdown);
|
|
||||||
command_queue_.pop_front();
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
|
ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_,
|
||||||
command->register_address, command->register_count);
|
command->register_address, command->register_count);
|
||||||
|
@ -50,11 +48,11 @@ bool ModbusController::send_next_command_() {
|
||||||
|
|
||||||
// remove from queue if no handler is defined
|
// remove from queue if no handler is defined
|
||||||
if (!command->on_data_func) {
|
if (!command->on_data_func) {
|
||||||
command_queue_.pop_front();
|
this->command_queue_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (!command_queue_.empty());
|
return (!this->command_queue_.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue incoming response
|
// Queue incoming response
|
||||||
|
@ -77,7 +75,7 @@ void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||||
current_command->payload = data;
|
current_command->payload = data;
|
||||||
this->incoming_queue_.push(std::move(current_command));
|
this->incoming_queue_.push(std::move(current_command));
|
||||||
ESP_LOGV(TAG, "Modbus response queued");
|
ESP_LOGV(TAG, "Modbus response queued");
|
||||||
command_queue_.pop_front();
|
this->command_queue_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +97,7 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
|
||||||
"payload size=%zu",
|
"payload size=%zu",
|
||||||
function_code, current_command->register_address, current_command->register_count,
|
function_code, current_command->register_address, current_command->register_count,
|
||||||
current_command->payload.size());
|
current_command->payload.size());
|
||||||
command_queue_.pop_front();
|
this->command_queue_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +176,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
|
||||||
if (!this->allow_duplicate_commands_) {
|
if (!this->allow_duplicate_commands_) {
|
||||||
// check if this command is already qeued.
|
// check if this command is already qeued.
|
||||||
// not very effective but the queue is never really large
|
// not very effective but the queue is never really large
|
||||||
for (auto &item : command_queue_) {
|
for (auto &item : this->command_queue_) {
|
||||||
if (item->is_equal(command)) {
|
if (item->is_equal(command)) {
|
||||||
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
|
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
|
||||||
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
|
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
|
||||||
|
@ -189,7 +187,7 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command_queue_.push_back(make_unique<ModbusCommandItem>(command));
|
this->command_queue_.push_back(make_unique<ModbusCommandItem>(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusController::update_range_(RegisterRange &r) {
|
void ModbusController::update_range_(RegisterRange &r) {
|
||||||
|
@ -224,8 +222,8 @@ void ModbusController::update_range_(RegisterRange &r) {
|
||||||
// Once we get a response to the command it is removed from the queue and the next command is send
|
// Once we get a response to the command it is removed from the queue and the next command is send
|
||||||
//
|
//
|
||||||
void ModbusController::update() {
|
void ModbusController::update() {
|
||||||
if (!command_queue_.empty()) {
|
if (!this->command_queue_.empty()) {
|
||||||
ESP_LOGV(TAG, "%zu modbus commands already in queue", command_queue_.size());
|
ESP_LOGV(TAG, "%zu modbus commands already in queue", this->command_queue_.size());
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Updating modbus component");
|
ESP_LOGV(TAG, "Updating modbus component");
|
||||||
}
|
}
|
||||||
|
@ -346,6 +344,8 @@ size_t ModbusController::create_register_ranges_() {
|
||||||
void ModbusController::dump_config() {
|
void ModbusController::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "ModbusController:");
|
ESP_LOGCONFIG(TAG, "ModbusController:");
|
||||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Max Command Retries: %d", this->max_cmd_retries_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
ESP_LOGCONFIG(TAG, "sensormap");
|
ESP_LOGCONFIG(TAG, "sensormap");
|
||||||
for (auto &it : sensorset_) {
|
for (auto &it : sensorset_) {
|
||||||
|
@ -560,8 +560,9 @@ bool ModbusCommandItem::send() {
|
||||||
} else {
|
} else {
|
||||||
modbusdevice->send_raw(this->payload);
|
modbusdevice->send_raw(this->payload);
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
|
this->send_count_++;
|
||||||
send_countdown--;
|
ESP_LOGV(TAG, "Command sent %d 0x%X %d send_count: %d", uint8_t(this->function_code), this->register_address,
|
||||||
|
this->register_count, this->send_count_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -312,7 +312,6 @@ struct RegisterRange {
|
||||||
class ModbusCommandItem {
|
class ModbusCommandItem {
|
||||||
public:
|
public:
|
||||||
static const size_t MAX_PAYLOAD_BYTES = 240;
|
static const size_t MAX_PAYLOAD_BYTES = 240;
|
||||||
static const uint8_t MAX_SEND_REPEATS = 5;
|
|
||||||
ModbusController *modbusdevice;
|
ModbusController *modbusdevice;
|
||||||
uint16_t register_address;
|
uint16_t register_address;
|
||||||
uint16_t register_count;
|
uint16_t register_count;
|
||||||
|
@ -322,9 +321,9 @@ class ModbusCommandItem {
|
||||||
on_data_func;
|
on_data_func;
|
||||||
std::vector<uint8_t> payload = {};
|
std::vector<uint8_t> payload = {};
|
||||||
bool send();
|
bool send();
|
||||||
// wrong commands (esp. custom commands) can block the send queue
|
/// Check if the command should be retried based on the max_retries parameter
|
||||||
// limit the number of repeats
|
bool should_retry(uint8_t max_retries) { return this->send_count_ <= max_retries; };
|
||||||
uint8_t send_countdown{MAX_SEND_REPEATS};
|
|
||||||
/// factory methods
|
/// factory methods
|
||||||
/** Create modbus read command
|
/** Create modbus read command
|
||||||
* Function code 02-04
|
* Function code 02-04
|
||||||
|
@ -413,6 +412,11 @@ class ModbusCommandItem {
|
||||||
&&handler = nullptr);
|
&&handler = nullptr);
|
||||||
|
|
||||||
bool is_equal(const ModbusCommandItem &other);
|
bool is_equal(const ModbusCommandItem &other);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// wrong commands (esp. custom commands) can block the send queue, limit the number of repeats.
|
||||||
|
/// How many times this command has been sent
|
||||||
|
uint8_t send_count_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Modbus controller class.
|
/** Modbus controller class.
|
||||||
|
@ -464,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||||
bool get_module_offline() { return module_offline_; }
|
bool get_module_offline() { return module_offline_; }
|
||||||
/// Set callback for commands
|
/// Set callback for commands
|
||||||
void add_on_command_sent_callback(std::function<void(int, int)> &&callback);
|
void add_on_command_sent_callback(std::function<void(int, int)> &&callback);
|
||||||
|
/// called by esphome generated code to set the max_cmd_retries.
|
||||||
|
void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
|
||||||
|
/// get how many times a command will be (re)sent if no response is received
|
||||||
|
uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// parse sensormap_ and create range of sequential addresses
|
/// parse sensormap_ and create range of sequential addresses
|
||||||
|
@ -498,6 +506,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||||
bool module_offline_;
|
bool module_offline_;
|
||||||
/// how many updates to skip if module is offline
|
/// how many updates to skip if module is offline
|
||||||
uint16_t offline_skip_updates_;
|
uint16_t offline_skip_updates_;
|
||||||
|
/// How many times we will retry a command if we get no response
|
||||||
|
uint8_t max_cmd_retries_{4};
|
||||||
CallbackManager<void(int, int)> command_sent_callback_{};
|
CallbackManager<void(int, int)> command_sent_callback_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT
|
||||||
CONF_ENABLE_IPV6,
|
|
||||||
CONF_MIN_IPV6_ADDR_COUNT,
|
|
||||||
PLATFORM_ESP32,
|
|
||||||
PLATFORM_ESP8266,
|
|
||||||
PLATFORM_RP2040,
|
|
||||||
)
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
@ -23,10 +17,17 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
esp8266=False,
|
esp8266=False,
|
||||||
esp32=False,
|
esp32=False,
|
||||||
rp2040=False,
|
rp2040=False,
|
||||||
|
bk72xx=False,
|
||||||
): cv.All(
|
): cv.All(
|
||||||
cv.boolean,
|
cv.boolean,
|
||||||
cv.Any(
|
cv.Any(
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
|
cv.require_framework_version(
|
||||||
|
esp_idf=cv.Version(0, 0, 0),
|
||||||
|
esp32_arduino=cv.Version(0, 0, 0),
|
||||||
|
esp8266_arduino=cv.Version(0, 0, 0),
|
||||||
|
rp2040_arduino=cv.Version(0, 0, 0),
|
||||||
|
bk72xx_libretiny=cv.Version(1, 7, 0),
|
||||||
|
),
|
||||||
cv.boolean_false,
|
cv.boolean_false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -53,3 +54,5 @@ async def to_code(config):
|
||||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
|
||||||
|
if CORE.is_bk72xx:
|
||||||
|
cg.add_build_flag("-DCONFIG_IPV6")
|
||||||
|
|
|
@ -1,31 +1,28 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import display
|
from esphome.components import display
|
||||||
|
from esphome.components.esp32 import const, only_on_variant
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ENABLE_PIN,
|
CONF_BLUE,
|
||||||
CONF_HSYNC_PIN,
|
CONF_COLOR_ORDER,
|
||||||
CONF_RESET_PIN,
|
|
||||||
CONF_DATA_PINS,
|
CONF_DATA_PINS,
|
||||||
|
CONF_DIMENSIONS,
|
||||||
|
CONF_ENABLE_PIN,
|
||||||
|
CONF_GREEN,
|
||||||
|
CONF_HEIGHT,
|
||||||
|
CONF_HSYNC_PIN,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_IGNORE_STRAPPING_WARNING,
|
CONF_IGNORE_STRAPPING_WARNING,
|
||||||
CONF_DIMENSIONS,
|
CONF_INVERT_COLORS,
|
||||||
CONF_VSYNC_PIN,
|
|
||||||
CONF_WIDTH,
|
|
||||||
CONF_HEIGHT,
|
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
CONF_COLOR_ORDER,
|
|
||||||
CONF_RED,
|
|
||||||
CONF_GREEN,
|
|
||||||
CONF_BLUE,
|
|
||||||
CONF_NUMBER,
|
CONF_NUMBER,
|
||||||
CONF_OFFSET_HEIGHT,
|
CONF_OFFSET_HEIGHT,
|
||||||
CONF_OFFSET_WIDTH,
|
CONF_OFFSET_WIDTH,
|
||||||
CONF_INVERT_COLORS,
|
CONF_RED,
|
||||||
)
|
CONF_RESET_PIN,
|
||||||
from esphome.components.esp32 import (
|
CONF_VSYNC_PIN,
|
||||||
only_on_variant,
|
CONF_WIDTH,
|
||||||
const,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
|
@ -6,9 +6,14 @@ namespace esphome {
|
||||||
namespace rpi_dpi_rgb {
|
namespace rpi_dpi_rgb {
|
||||||
|
|
||||||
void RpiDpiRgb::setup() {
|
void RpiDpiRgb::setup() {
|
||||||
esph_log_config(TAG, "Setting up RPI_DPI_RGB");
|
ESP_LOGCONFIG(TAG, "Setting up RPI_DPI_RGB");
|
||||||
|
this->reset_display_();
|
||||||
esp_lcd_rgb_panel_config_t config{};
|
esp_lcd_rgb_panel_config_t config{};
|
||||||
config.flags.fb_in_psram = 1;
|
config.flags.fb_in_psram = 1;
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
config.bounce_buffer_size_px = this->width_ * 10;
|
||||||
|
config.num_fbs = 1;
|
||||||
|
#endif // ESP_IDF_VERSION_MAJOR
|
||||||
config.timings.h_res = this->width_;
|
config.timings.h_res = this->width_;
|
||||||
config.timings.v_res = this->height_;
|
config.timings.v_res = this->height_;
|
||||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||||
|
@ -20,7 +25,6 @@ void RpiDpiRgb::setup() {
|
||||||
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
|
config.timings.flags.pclk_active_neg = this->pclk_inverted_;
|
||||||
config.timings.pclk_hz = this->pclk_frequency_;
|
config.timings.pclk_hz = this->pclk_frequency_;
|
||||||
config.clk_src = LCD_CLK_SRC_PLL160M;
|
config.clk_src = LCD_CLK_SRC_PLL160M;
|
||||||
config.sram_trans_align = 64;
|
|
||||||
config.psram_trans_align = 64;
|
config.psram_trans_align = 64;
|
||||||
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
|
||||||
for (size_t i = 0; i != data_pin_count; i++) {
|
for (size_t i = 0; i != data_pin_count; i++) {
|
||||||
|
@ -34,11 +38,19 @@ void RpiDpiRgb::setup() {
|
||||||
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
config.pclk_gpio_num = this->pclk_pin_->get_pin();
|
||||||
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err));
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_));
|
ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_));
|
||||||
ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_));
|
ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_));
|
||||||
esph_log_config(TAG, "RPI_DPI_RGB setup complete");
|
ESP_LOGCONFIG(TAG, "RPI_DPI_RGB setup complete");
|
||||||
|
}
|
||||||
|
void RpiDpiRgb::loop() {
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
if (this->handle_ != nullptr)
|
||||||
|
esp_lcd_rgb_panel_restart(this->handle_);
|
||||||
|
#endif // ESP_IDF_VERSION_MAJOR
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
|
@ -53,7 +65,7 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin
|
||||||
}
|
}
|
||||||
x_start += this->offset_x_;
|
x_start += this->offset_x_;
|
||||||
y_start += this->offset_y_;
|
y_start += this->offset_y_;
|
||||||
esp_err_t err;
|
esp_err_t err = ESP_OK;
|
||||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||||
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
|
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
|
||||||
|
@ -69,7 +81,7 @@ void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
esph_log_e(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
|
void RpiDpiRgb::draw_pixel_at(int x, int y, Color color) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ class RpiDpiRgb : public display::Display {
|
||||||
public:
|
public:
|
||||||
void update() override { this->do_update_(); }
|
void update() override { this->do_update_(); }
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
void loop() override;
|
||||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||||
void draw_pixel_at(int x, int y, Color color) override;
|
void draw_pixel_at(int x, int y, Color color) override;
|
||||||
|
|
|
@ -45,11 +45,8 @@ void UponorSmatrixComponent::loop() {
|
||||||
|
|
||||||
// Read incoming data
|
// Read incoming data
|
||||||
while (this->available()) {
|
while (this->available()) {
|
||||||
// The controller polls devices every 10 seconds, with around 200 ms between devices.
|
// The controller polls devices every 10 seconds in some units or continuously in others with around 200 ms between
|
||||||
// Remember timestamps so we can send our own packets when the bus is expected to be silent.
|
// devices. Remember timestamps so we can send our own packets when the bus is expected to be silent.
|
||||||
if (now - this->last_rx_ > 500) {
|
|
||||||
this->last_poll_start_ = now;
|
|
||||||
}
|
|
||||||
this->last_rx_ = now;
|
this->last_rx_ = now;
|
||||||
|
|
||||||
uint8_t byte;
|
uint8_t byte;
|
||||||
|
@ -60,7 +57,8 @@ void UponorSmatrixComponent::loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send packets during bus silence
|
// Send packets during bus silence
|
||||||
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
|
if (this->rx_buffer_.empty() && (now - this->last_rx_ > 50) && (now - this->last_rx_ < 100) &&
|
||||||
|
(now - this->last_tx_ > 200)) {
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
|
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
|
||||||
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
|
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
|
||||||
|
|
|
@ -93,7 +93,6 @@ class UponorSmatrixComponent : public uart::UARTDevice, public Component {
|
||||||
std::queue<std::vector<uint8_t>> tx_queue_;
|
std::queue<std::vector<uint8_t>> tx_queue_;
|
||||||
uint32_t last_rx_;
|
uint32_t last_rx_;
|
||||||
uint32_t last_tx_;
|
uint32_t last_tx_;
|
||||||
uint32_t last_poll_start_;
|
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
time::RealTimeClock *time_id_{nullptr};
|
time::RealTimeClock *time_id_{nullptr};
|
||||||
|
|
|
@ -755,7 +755,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
||||||
message = std::move(arg.value);
|
message = std::move(arg.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") {
|
if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") {
|
||||||
// Don't change state here since either the "tts-end" or "run-end" events will do it.
|
// Don't change state here since either the "tts-end" or "run-end" events will do it.
|
||||||
return;
|
return;
|
||||||
} else if (code == "wake-provider-missing" || code == "wake-engine-missing") {
|
} else if (code == "wake-provider-missing" || code == "wake-engine-missing") {
|
||||||
|
|
|
@ -85,7 +85,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
|
||||||
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
|
||||||
if (!this->has_sta())
|
if (!this->has_sta())
|
||||||
return {};
|
return {};
|
||||||
return {WiFi.localIP()};
|
network::IPAddresses addresses;
|
||||||
|
addresses[0] = WiFi.localIP();
|
||||||
|
#if USE_NETWORK_IPV6
|
||||||
|
int i = 1;
|
||||||
|
auto v6_addresses = WiFi.allLocalIPv6();
|
||||||
|
for (auto address : v6_addresses) {
|
||||||
|
addresses[i++] = network::IPAddress(address.toString().c_str());
|
||||||
|
}
|
||||||
|
#endif /* USE_NETWORK_IPV6 */
|
||||||
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WiFiComponent::wifi_apply_hostname_() {
|
bool WiFiComponent::wifi_apply_hostname_() {
|
||||||
|
@ -321,6 +330,11 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||||
s_sta_connecting = false;
|
s_sta_connecting = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
|
||||||
|
// auto it = info.got_ip.ip_info;
|
||||||
|
ESP_LOGV(TAG, "Event: Got IPv6");
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
|
case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
|
||||||
ESP_LOGV(TAG, "Event: Lost IP");
|
ESP_LOGV(TAG, "Event: Lost IP");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2045,6 +2045,7 @@ def require_framework_version(
|
||||||
esp32_arduino=None,
|
esp32_arduino=None,
|
||||||
esp8266_arduino=None,
|
esp8266_arduino=None,
|
||||||
rp2040_arduino=None,
|
rp2040_arduino=None,
|
||||||
|
bk72xx_libretiny=None,
|
||||||
host=None,
|
host=None,
|
||||||
max_version=False,
|
max_version=False,
|
||||||
extra_message=None,
|
extra_message=None,
|
||||||
|
@ -2059,6 +2060,13 @@ def require_framework_version(
|
||||||
msg += f". {extra_message}"
|
msg += f". {extra_message}"
|
||||||
raise Invalid(msg)
|
raise Invalid(msg)
|
||||||
required = esp_idf
|
required = esp_idf
|
||||||
|
elif CORE.is_bk72xx and framework == "arduino":
|
||||||
|
if bk72xx_libretiny is None:
|
||||||
|
msg = "This feature is incompatible with BK72XX"
|
||||||
|
if extra_message:
|
||||||
|
msg += f". {extra_message}"
|
||||||
|
raise Invalid(msg)
|
||||||
|
required = bk72xx_libretiny
|
||||||
elif CORE.is_esp32 and framework == "arduino":
|
elif CORE.is_esp32 and framework == "arduino":
|
||||||
if esp32_arduino is None:
|
if esp32_arduino is None:
|
||||||
msg = "This feature is incompatible with ESP32 using arduino framework"
|
msg = "This feature is incompatible with ESP32 using arduino framework"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2024.9.0-dev"
|
__version__ = "2024.10.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
@ -852,6 +852,7 @@ CONF_TEMPERATURE_STEP = "temperature_step"
|
||||||
CONF_TEXT = "text"
|
CONF_TEXT = "text"
|
||||||
CONF_TEXT_SENSORS = "text_sensors"
|
CONF_TEXT_SENSORS = "text_sensors"
|
||||||
CONF_THEN = "then"
|
CONF_THEN = "then"
|
||||||
|
CONF_THERMOCOUPLE_TYPE = "thermocouple_type"
|
||||||
CONF_THRESHOLD = "threshold"
|
CONF_THRESHOLD = "threshold"
|
||||||
CONF_THROTTLE = "throttle"
|
CONF_THROTTLE = "throttle"
|
||||||
CONF_TILT = "tilt"
|
CONF_TILT = "tilt"
|
||||||
|
|
|
@ -119,7 +119,7 @@ lib_deps =
|
||||||
WiFi ; wifi,web_server_base,ethernet (Arduino built-in)
|
WiFi ; wifi,web_server_base,ethernet (Arduino built-in)
|
||||||
Update ; ota,web_server_base (Arduino built-in)
|
Update ; ota,web_server_base (Arduino built-in)
|
||||||
${common:arduino.lib_deps}
|
${common:arduino.lib_deps}
|
||||||
esphome/AsyncTCP-esphome@2.1.3 ; async_tcp
|
esphome/AsyncTCP-esphome@2.1.4 ; async_tcp
|
||||||
WiFiClientSecure ; http_request,nextion (Arduino built-in)
|
WiFiClientSecure ; http_request,nextion (Arduino built-in)
|
||||||
HTTPClient ; http_request,nextion (Arduino built-in)
|
HTTPClient ; http_request,nextion (Arduino built-in)
|
||||||
ESPmDNS ; mdns (Arduino built-in)
|
ESPmDNS ; mdns (Arduino built-in)
|
||||||
|
|
|
@ -10,6 +10,7 @@ sensor:
|
||||||
- platform: bl0942
|
- platform: bl0942
|
||||||
address: 0
|
address: 0
|
||||||
line_frequency: 50Hz
|
line_frequency: 50Hz
|
||||||
|
reset: false
|
||||||
voltage:
|
voltage:
|
||||||
name: BL0942 Voltage
|
name: BL0942 Voltage
|
||||||
current:
|
current:
|
||||||
|
|
|
@ -8,6 +8,7 @@ uart:
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: bl0942
|
- platform: bl0942
|
||||||
|
reset: true
|
||||||
voltage:
|
voltage:
|
||||||
name: BL0942 Voltage
|
name: BL0942 Voltage
|
||||||
current:
|
current:
|
||||||
|
|
|
@ -1,12 +1,32 @@
|
||||||
lvgl:
|
lvgl:
|
||||||
log_level: TRACE
|
log_level: TRACE
|
||||||
bg_color: light_blue
|
bg_color: light_blue
|
||||||
disp_bg_color: 0xffff00
|
disp_bg_color: color_id
|
||||||
disp_bg_image: cat_image
|
disp_bg_image: cat_image
|
||||||
theme:
|
theme:
|
||||||
obj:
|
obj:
|
||||||
border_width: 1
|
border_width: 1
|
||||||
|
|
||||||
|
gradients:
|
||||||
|
- id: color_bar
|
||||||
|
direction: hor
|
||||||
|
dither: err_diff
|
||||||
|
stops:
|
||||||
|
- color: 0xFF0000
|
||||||
|
position: 0
|
||||||
|
- color: 0xFFFF00
|
||||||
|
position: 42
|
||||||
|
- color: 0x00FF00
|
||||||
|
position: 84
|
||||||
|
- color: 0x00FFFF
|
||||||
|
position: 127
|
||||||
|
- color: 0x0000FF
|
||||||
|
position: 169
|
||||||
|
- color: 0xFF00FF
|
||||||
|
position: 212
|
||||||
|
- color: 0xFF0000
|
||||||
|
position: 255
|
||||||
|
|
||||||
style_definitions:
|
style_definitions:
|
||||||
- id: style_test
|
- id: style_test
|
||||||
bg_color: 0x2F8CD8
|
bg_color: 0x2F8CD8
|
||||||
|
@ -31,7 +51,7 @@ lvgl:
|
||||||
- id: date_style
|
- id: date_style
|
||||||
text_font: roboto10
|
text_font: roboto10
|
||||||
align: center
|
align: center
|
||||||
text_color: 0x000000
|
text_color: color_id2
|
||||||
bg_opa: cover
|
bg_opa: cover
|
||||||
radius: 4
|
radius: 4
|
||||||
pad_all: 2
|
pad_all: 2
|
||||||
|
@ -386,6 +406,22 @@ lvgl:
|
||||||
|
|
||||||
- id: page2
|
- id: page2
|
||||||
widgets:
|
widgets:
|
||||||
|
- slider:
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
bg_opa: cover
|
||||||
|
bg_grad: color_bar
|
||||||
|
radius: 0
|
||||||
|
indicator:
|
||||||
|
bg_opa: transp
|
||||||
|
knob:
|
||||||
|
radius: 1
|
||||||
|
width: 4
|
||||||
|
height: 10%
|
||||||
|
bg_color: 0x000000
|
||||||
|
width: 100%
|
||||||
|
height: 10%
|
||||||
|
align: top_mid
|
||||||
- button:
|
- button:
|
||||||
styles: spin_button
|
styles: spin_button
|
||||||
id: spin_up
|
id: spin_up
|
||||||
|
@ -586,3 +622,13 @@ image:
|
||||||
color:
|
color:
|
||||||
- id: light_blue
|
- id: light_blue
|
||||||
hex: "3340FF"
|
hex: "3340FF"
|
||||||
|
- id: color_id
|
||||||
|
red: 0.5
|
||||||
|
green: 0.5
|
||||||
|
blue: 0.5
|
||||||
|
white: 0.5
|
||||||
|
- id: color_id2
|
||||||
|
red_int: 0xFF
|
||||||
|
green_int: 123
|
||||||
|
blue_int: 64
|
||||||
|
white_int: 255
|
||||||
|
|
|
@ -10,3 +10,4 @@ sensor:
|
||||||
cs_pin: 12
|
cs_pin: 12
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
||||||
cs_pin: 8
|
cs_pin: 8
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
||||||
cs_pin: 8
|
cs_pin: 8
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,4 @@ sensor:
|
||||||
cs_pin: 12
|
cs_pin: 12
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
||||||
cs_pin: 15
|
cs_pin: 15
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
||||||
|
|
|
@ -10,3 +10,5 @@ sensor:
|
||||||
cs_pin: 6
|
cs_pin: 6
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
mains_filter: 50Hz
|
mains_filter: 50Hz
|
||||||
|
thermocouple_type: N
|
||||||
|
|
||||||
|
|
|
@ -29,3 +29,4 @@ modbus_controller:
|
||||||
value_type: S_DWORD_R
|
value_type: S_DWORD_R
|
||||||
read_lambda: |-
|
read_lambda: |-
|
||||||
return 42.3;
|
return 42.3;
|
||||||
|
max_cmd_retries: 0
|
||||||
|
|
|
@ -13,3 +13,4 @@ modbus_controller:
|
||||||
address: 0x2
|
address: 0x2
|
||||||
modbus_id: mod_bus1
|
modbus_id: mod_bus1
|
||||||
allow_duplicate_commands: true
|
allow_duplicate_commands: true
|
||||||
|
max_cmd_retries: 10
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
substitutions:
|
substitutions:
|
||||||
network_enable_ipv6: "false"
|
network_enable_ipv6: "true"
|
||||||
|
|
||||||
|
bk72xx:
|
||||||
|
framework:
|
||||||
|
version: 1.7.0
|
||||||
|
|
||||||
<<: !include common.yaml
|
<<: !include common.yaml
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 13
|
i2s_dout_pin: 13
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 3
|
i2s_dout_pin: 3
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 3
|
i2s_dout_pin: 3
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -21,4 +21,3 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 13
|
i2s_dout_pin: 13
|
||||||
mode: mono
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 12
|
i2s_dout_pin: 12
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 2
|
i2s_dout_pin: 2
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 2
|
i2s_dout_pin: 2
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
|
@ -28,7 +28,6 @@ speaker:
|
||||||
id: speaker_id
|
id: speaker_id
|
||||||
dac_type: external
|
dac_type: external
|
||||||
i2s_dout_pin: 12
|
i2s_dout_pin: 12
|
||||||
mode: mono
|
|
||||||
|
|
||||||
voice_assistant:
|
voice_assistant:
|
||||||
microphone: mic_id_external
|
microphone: mic_id_external
|
||||||
|
|
Loading…
Reference in a new issue