Merge branch 'dev' into bump-2023.10.0b1

This commit is contained in:
Jesse Hills 2023-10-12 15:14:42 +13:00
commit d500531c04
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
116 changed files with 2045 additions and 522 deletions

View file

@ -40,9 +40,9 @@ jobs:
arch: [amd64, armv7, aarch64] arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"] build_type: ["ha-addon", "docker", "lint"]
steps: steps:
- uses: actions/checkout@v4.0.0 - uses: actions/checkout@v4.1.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx

View file

@ -34,13 +34,13 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }} cache-key: ${{ steps.cache-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Generate cache-key - name: Generate cache-key
id: cache-key id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
@ -66,7 +66,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -87,7 +87,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -108,7 +108,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -129,7 +129,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -150,7 +150,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -171,7 +171,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -191,7 +191,7 @@ jobs:
- common - common
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
@ -210,6 +210,17 @@ jobs:
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
compile-tests-list:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
- name: Find all YAML test files
id: set-matrix
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
compile-tests: compile-tests:
name: Run YAML test ${{ matrix.file }} name: Run YAML test ${{ matrix.file }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -222,23 +233,24 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- compile-tests-list
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 2 max-parallel: 2
matrix: matrix:
file: [1, 2, 3, 3.1, 4, 5, 6, 7, 8, 10, 11.5] file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run esphome compile tests/test${{ matrix.file }}.yaml - name: Run esphome compile ${{ matrix.file }}
run: | run: |
. venv/bin/activate . venv/bin/activate
esphome compile tests/test${{ matrix.file }}.yaml esphome compile ${{ matrix.file }}
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
@ -284,7 +296,7 @@ jobs:
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Restore Python - name: Restore Python
uses: ./.github/actions/restore-python uses: ./.github/actions/restore-python
with: with:

View file

@ -19,7 +19,7 @@ jobs:
outputs: outputs:
tag: ${{ steps.tag.outputs.tag }} tag: ${{ steps.tag.outputs.tag }}
steps: steps:
- uses: actions/checkout@v4.0.0 - uses: actions/checkout@v4.1.0
- name: Get tag - name: Get tag
id: tag id: tag
# yamllint disable rule:line-length # yamllint disable rule:line-length
@ -43,9 +43,9 @@ jobs:
if: github.repository == 'esphome/esphome' && github.event_name == 'release' if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4.0.0 - uses: actions/checkout@v4.1.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment - name: Set up python environment
@ -88,9 +88,9 @@ jobs:
target: "lint" target: "lint"
baseimg: "docker" baseimg: "docker"
steps: steps:
- uses: actions/checkout@v4.0.0 - uses: actions/checkout@v4.1.0
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: "3.9" python-version: "3.9"

View file

@ -13,16 +13,16 @@ jobs:
if: github.repository == 'esphome/esphome' if: github.repository == 'esphome/esphome'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Checkout Home Assistant - name: Checkout Home Assistant
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
with: with:
repository: home-assistant/core repository: home-assistant/core
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v4.7.0 uses: actions/setup-python@v4.7.1
with: with:
python-version: 3.11 python-version: 3.11

View file

@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.0.0 uses: actions/checkout@v4.1.0
- name: Run yamllint - name: Run yamllint
uses: frenck/action-yamllint@v1.4.1 uses: frenck/action-yamllint@v1.4.1

View file

@ -186,6 +186,7 @@ esphome/components/mitsubishi/* @RubyBailey
esphome/components/mlx90393/* @functionpointer esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz esphome/components/mlx90614/* @jesserockz
esphome/components/mmc5603/* @benhoff esphome/components/mmc5603/* @benhoff
esphome/components/mmc5983/* @agoode
esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/* @martgras
esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras
esphome/components/modbus_controller/number/* @martgras esphome/components/modbus_controller/number/* @martgras

View file

@ -26,8 +26,8 @@ RUN \
python3-venv=3.9.2-3 \ python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \ git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u9 \
openssh-client=1:8.4p1-5+deb11u1 \ openssh-client=1:8.4p1-5+deb11u2 \
python3-cffi=1.14.5-1 \ python3-cffi=1.14.5-1 \
libcairo2=1.16.0-5 \ libcairo2=1.16.0-5 \
patch=2.7.6-7; \ patch=2.7.6-7; \

View file

@ -11,6 +11,7 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME, CONF_TIME,
CONF_UPDATE_INTERVAL,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.util import Registry from esphome.util import Registry
@ -69,6 +70,8 @@ WhileAction = cg.esphome_ns.class_("WhileAction", Action)
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component) WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action) UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation") Automation = cg.esphome_ns.class_("Automation")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
@ -138,6 +141,7 @@ AUTOMATION_SCHEMA = cv.Schema(
AndCondition = cg.esphome_ns.class_("AndCondition", Condition) AndCondition = cg.esphome_ns.class_("AndCondition", Condition)
OrCondition = cg.esphome_ns.class_("OrCondition", Condition) OrCondition = cg.esphome_ns.class_("OrCondition", Condition)
NotCondition = cg.esphome_ns.class_("NotCondition", Condition) NotCondition = cg.esphome_ns.class_("NotCondition", Condition)
XorCondition = cg.esphome_ns.class_("XorCondition", Condition)
@register_condition("and", AndCondition, validate_condition_list) @register_condition("and", AndCondition, validate_condition_list)
@ -158,6 +162,12 @@ async def not_condition_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg, condition) return cg.new_Pvariable(condition_id, template_arg, condition)
@register_condition("xor", XorCondition, validate_condition_list)
async def xor_condition_to_code(config, condition_id, template_arg, args):
conditions = await build_condition_list(config, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition("lambda", LambdaCondition, cv.returning_lambda) @register_condition("lambda", LambdaCondition, cv.returning_lambda)
async def lambda_condition_to_code(config, condition_id, template_arg, args): async def lambda_condition_to_code(config, condition_id, template_arg, args):
lambda_ = await cg.process_lambda(config, args, return_type=bool) lambda_ = await cg.process_lambda(config, args, return_type=bool)
@ -303,6 +313,41 @@ async def component_update_action_to_code(config, action_id, template_arg, args)
return cg.new_Pvariable(action_id, template_arg, comp) return cg.new_Pvariable(action_id, template_arg, comp)
@register_action(
"component.suspend",
SuspendComponentAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
}
),
)
async def component_suspend_action_to_code(config, action_id, template_arg, args):
comp = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, comp)
@register_action(
"component.resume",
ResumeComponentAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(cg.PollingComponent),
cv.Optional(CONF_UPDATE_INTERVAL): cv.templatable(
cv.positive_time_period_milliseconds
),
}
),
)
async def component_resume_action_to_code(config, action_id, template_arg, args):
comp = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, comp)
if CONF_UPDATE_INTERVAL in config:
template_ = await cg.templatable(config[CONF_UPDATE_INTERVAL], args, int)
cg.add(var.set_update_interval(template_))
return var
async def build_action(full_config, template_arg, args): async def build_action(full_config, template_arg, args):
registry_entry, config = cg.extract_registry_entry_config( registry_entry, config = cg.extract_registry_entry_config(
ACTION_REGISTRY, full_config ACTION_REGISTRY, full_config

View file

@ -4,13 +4,14 @@ from esphome.components import sensor, i2c
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_IRQ_PIN,
CONF_VOLTAGE, CONF_VOLTAGE,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT, UNIT_WATT,
) )
@ -19,7 +20,6 @@ DEPENDENCIES = ["i2c"]
ade7953_ns = cg.esphome_ns.namespace("ade7953") ade7953_ns = cg.esphome_ns.namespace("ade7953")
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice) ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
CONF_IRQ_PIN = "irq_pin"
CONF_CURRENT_A = "current_a" CONF_CURRENT_A = "current_a"
CONF_CURRENT_B = "current_b" CONF_CURRENT_B = "current_b"
CONF_ACTIVE_POWER_A = "active_power_a" CONF_ACTIVE_POWER_A = "active_power_a"

View file

@ -151,7 +151,7 @@ async def to_code(config):
pos = 0 pos = 0
for frameIndex in range(frames): for frameIndex in range(frames):
image.seek(frameIndex) image.seek(frameIndex)
frame = image.convert("LA", dither=Image.NONE) frame = image.convert("LA", dither=Image.Dither.NONE)
if CONF_RESIZE in config: if CONF_RESIZE in config:
frame = frame.resize([width, height]) frame = frame.resize([width, height])
pixels = list(frame.getdata()) pixels = list(frame.getdata())
@ -259,7 +259,7 @@ async def to_code(config):
if transparent: if transparent:
alpha = image.split()[-1] alpha = image.split()[-1]
has_alpha = alpha.getextrema()[0] < 0xFF has_alpha = alpha.getextrema()[0] < 0xFF
frame = image.convert("1", dither=Image.NONE) frame = image.convert("1", dither=Image.Dither.NONE)
if CONF_RESIZE in config: if CONF_RESIZE in config:
frame = frame.resize([width, height]) frame = frame.resize([width, height])
if transparent: if transparent:

View file

@ -1413,6 +1413,18 @@ message SubscribeVoiceAssistantRequest {
bool subscribe = 1; bool subscribe = 1;
} }
enum VoiceAssistantRequestFlag {
VOICE_ASSISTANT_REQUEST_NONE = 0;
VOICE_ASSISTANT_REQUEST_USE_VAD = 1;
VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2;
}
message VoiceAssistantAudioSettings {
uint32 noise_suppression_level = 1;
uint32 auto_gain = 2;
float volume_multiplier = 3;
}
message VoiceAssistantRequest { message VoiceAssistantRequest {
option (id) = 90; option (id) = 90;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -1420,7 +1432,8 @@ message VoiceAssistantRequest {
bool start = 1; bool start = 1;
string conversation_id = 2; string conversation_id = 2;
bool use_vad = 3; uint32 flags = 3;
VoiceAssistantAudioSettings audio_settings = 4;
} }
message VoiceAssistantResponse { message VoiceAssistantResponse {
@ -1442,6 +1455,10 @@ enum VoiceAssistantEvent {
VOICE_ASSISTANT_INTENT_END = 6; VOICE_ASSISTANT_INTENT_END = 6;
VOICE_ASSISTANT_TTS_START = 7; VOICE_ASSISTANT_TTS_START = 7;
VOICE_ASSISTANT_TTS_END = 8; VOICE_ASSISTANT_TTS_END = 8;
VOICE_ASSISTANT_WAKE_WORD_START = 9;
VOICE_ASSISTANT_WAKE_WORD_END = 10;
VOICE_ASSISTANT_STT_VAD_START = 11;
VOICE_ASSISTANT_STT_VAD_END = 12;
} }
message VoiceAssistantEventData { message VoiceAssistantEventData {

View file

@ -907,21 +907,22 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIConnection::request_voice_assistant(bool start, const std::string &conversation_id, bool use_vad) { bool APIConnection::request_voice_assistant(const VoiceAssistantRequest &msg) {
if (!this->voice_assistant_subscription_) if (!this->voice_assistant_subscription_)
return false; return false;
VoiceAssistantRequest msg;
msg.start = start;
msg.conversation_id = conversation_id;
msg.use_vad = use_vad;
return this->send_voice_assistant_request(msg); return this->send_voice_assistant_request(msg);
} }
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
if (voice_assistant::global_voice_assistant != nullptr) { if (voice_assistant::global_voice_assistant != nullptr) {
if (msg.error) {
voice_assistant::global_voice_assistant->failed_to_start();
return;
}
struct sockaddr_storage storage; struct sockaddr_storage storage;
socklen_t len = sizeof(storage); socklen_t len = sizeof(storage);
this->helper_->getpeername((struct sockaddr *) &storage, &len); this->helper_->getpeername((struct sockaddr *) &storage, &len);
voice_assistant::global_voice_assistant->start(&storage, msg.port); voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
} }
}; };
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {

View file

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

View file

@ -410,6 +410,20 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
} }
#endif #endif
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::VoiceAssistantRequestFlag>(enums::VoiceAssistantRequestFlag value) {
switch (value) {
case enums::VOICE_ASSISTANT_REQUEST_NONE:
return "VOICE_ASSISTANT_REQUEST_NONE";
case enums::VOICE_ASSISTANT_REQUEST_USE_VAD:
return "VOICE_ASSISTANT_REQUEST_USE_VAD";
case enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD:
return "VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) { template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::VoiceAssistantEvent value) {
switch (value) { switch (value) {
case enums::VOICE_ASSISTANT_ERROR: case enums::VOICE_ASSISTANT_ERROR:
@ -430,6 +444,14 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
return "VOICE_ASSISTANT_TTS_START"; return "VOICE_ASSISTANT_TTS_START";
case enums::VOICE_ASSISTANT_TTS_END: case enums::VOICE_ASSISTANT_TTS_END:
return "VOICE_ASSISTANT_TTS_END"; return "VOICE_ASSISTANT_TTS_END";
case enums::VOICE_ASSISTANT_WAKE_WORD_START:
return "VOICE_ASSISTANT_WAKE_WORD_START";
case enums::VOICE_ASSISTANT_WAKE_WORD_END:
return "VOICE_ASSISTANT_WAKE_WORD_END";
case enums::VOICE_ASSISTANT_STT_VAD_START:
return "VOICE_ASSISTANT_STT_VAD_START";
case enums::VOICE_ASSISTANT_STT_VAD_END:
return "VOICE_ASSISTANT_STT_VAD_END";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@ -6344,6 +6366,56 @@ void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool VoiceAssistantAudioSettings::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->noise_suppression_level = value.as_uint32();
return true;
}
case 2: {
this->auto_gain = value.as_uint32();
return true;
}
default:
return false;
}
}
bool VoiceAssistantAudioSettings::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 3: {
this->volume_multiplier = value.as_float();
return true;
}
default:
return false;
}
}
void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, this->noise_suppression_level);
buffer.encode_uint32(2, this->auto_gain);
buffer.encode_float(3, this->volume_multiplier);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantAudioSettings::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantAudioSettings {\n");
out.append(" noise_suppression_level: ");
sprintf(buffer, "%" PRIu32, this->noise_suppression_level);
out.append(buffer);
out.append("\n");
out.append(" auto_gain: ");
sprintf(buffer, "%" PRIu32, this->auto_gain);
out.append(buffer);
out.append("\n");
out.append(" volume_multiplier: ");
sprintf(buffer, "%g", this->volume_multiplier);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 1: { case 1: {
@ -6351,7 +6423,7 @@ bool VoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
return true; return true;
} }
case 3: { case 3: {
this->use_vad = value.as_bool(); this->flags = value.as_uint32();
return true; return true;
} }
default: default:
@ -6364,6 +6436,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
this->conversation_id = value.as_string(); this->conversation_id = value.as_string();
return true; return true;
} }
case 4: {
this->audio_settings = value.as_message<VoiceAssistantAudioSettings>();
return true;
}
default: default:
return false; return false;
} }
@ -6371,7 +6447,8 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start); buffer.encode_bool(1, this->start);
buffer.encode_string(2, this->conversation_id); buffer.encode_string(2, this->conversation_id);
buffer.encode_bool(3, this->use_vad); buffer.encode_uint32(3, this->flags);
buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantRequest::dump_to(std::string &out) const { void VoiceAssistantRequest::dump_to(std::string &out) const {
@ -6385,8 +6462,13 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
out.append("'").append(this->conversation_id).append("'"); out.append("'").append(this->conversation_id).append("'");
out.append("\n"); out.append("\n");
out.append(" use_vad: "); out.append(" flags: ");
out.append(YESNO(this->use_vad)); sprintf(buffer, "%" PRIu32, this->flags);
out.append(buffer);
out.append("\n");
out.append(" audio_settings: ");
this->audio_settings.dump_to(out);
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
} }

View file

@ -165,6 +165,11 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
}; };
enum VoiceAssistantRequestFlag : uint32_t {
VOICE_ASSISTANT_REQUEST_NONE = 0,
VOICE_ASSISTANT_REQUEST_USE_VAD = 1,
VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2,
};
enum VoiceAssistantEvent : uint32_t { enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_ERROR = 0, VOICE_ASSISTANT_ERROR = 0,
VOICE_ASSISTANT_RUN_START = 1, VOICE_ASSISTANT_RUN_START = 1,
@ -175,6 +180,10 @@ enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_INTENT_END = 6, VOICE_ASSISTANT_INTENT_END = 6,
VOICE_ASSISTANT_TTS_START = 7, VOICE_ASSISTANT_TTS_START = 7,
VOICE_ASSISTANT_TTS_END = 8, VOICE_ASSISTANT_TTS_END = 8,
VOICE_ASSISTANT_WAKE_WORD_START = 9,
VOICE_ASSISTANT_WAKE_WORD_END = 10,
VOICE_ASSISTANT_STT_VAD_START = 11,
VOICE_ASSISTANT_STT_VAD_END = 12,
}; };
enum AlarmControlPanelState : uint32_t { enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0, ALARM_STATE_DISARMED = 0,
@ -1651,11 +1660,26 @@ class SubscribeVoiceAssistantRequest : public ProtoMessage {
protected: protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantAudioSettings : public ProtoMessage {
public:
uint32_t noise_suppression_level{0};
uint32_t auto_gain{0};
float volume_multiplier{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantRequest : public ProtoMessage { class VoiceAssistantRequest : public ProtoMessage {
public: public:
bool start{false}; bool start{false};
std::string conversation_id{}; std::string conversation_id{};
bool use_vad{false}; uint32_t flags{0};
VoiceAssistantAudioSettings audio_settings{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -1,13 +1,13 @@
#include "api_server.h" #include "api_server.h"
#include <cerrno>
#include "api_connection.h" #include "api_connection.h"
#include "esphome/components/network/util.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include "esphome/components/network/util.h"
#include <cerrno>
#ifdef USE_LOGGER #ifdef USE_LOGGER
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
@ -323,16 +323,24 @@ void APIServer::on_shutdown() {
} }
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIServer::start_voice_assistant(const std::string &conversation_id, bool use_vad) { bool APIServer::start_voice_assistant(const std::string &conversation_id, uint32_t flags,
const api::VoiceAssistantAudioSettings &audio_settings) {
VoiceAssistantRequest msg;
msg.start = true;
msg.conversation_id = conversation_id;
msg.flags = flags;
msg.audio_settings = audio_settings;
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(true, conversation_id, use_vad)) if (c->request_voice_assistant(msg))
return true; return true;
} }
return false; return false;
} }
void APIServer::stop_voice_assistant() { void APIServer::stop_voice_assistant() {
VoiceAssistantRequest msg;
msg.start = false;
for (auto &c : this->clients_) { for (auto &c : this->clients_) {
if (c->request_voice_assistant(false, "", false)) if (c->request_voice_assistant(msg))
return; return;
} }
} }

View file

@ -1,16 +1,16 @@
#pragma once #pragma once
#include "api_noise_context.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "esphome/components/socket/socket.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/socket/socket.h"
#include "api_pb2.h"
#include "api_pb2_service.h"
#include "list_entities.h" #include "list_entities.h"
#include "subscribe_state.h" #include "subscribe_state.h"
#include "user_services.h" #include "user_services.h"
#include "api_noise_context.h"
#include <vector> #include <vector>
@ -81,7 +81,8 @@ class APIServer : public Component, public Controller {
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool start_voice_assistant(const std::string &conversation_id, bool use_vad); bool start_voice_assistant(const std::string &conversation_id, uint32_t flags,
const api::VoiceAssistantAudioSettings &audio_settings);
void stop_voice_assistant(); void stop_voice_assistant();
#endif #endif

View file

@ -2,14 +2,17 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_CAPACITANCE,
CONF_DIV_RATIO,
CONF_INDOOR, CONF_INDOOR,
CONF_WATCHDOG_THRESHOLD, CONF_IRQ_PIN,
CONF_NOISE_LEVEL,
CONF_SPIKE_REJECTION,
CONF_LIGHTNING_THRESHOLD, CONF_LIGHTNING_THRESHOLD,
CONF_MASK_DISTURBER, CONF_MASK_DISTURBER,
CONF_DIV_RATIO, CONF_CALIBRATION,
CONF_CAPACITANCE, CONF_TUNE_ANTENNA,
CONF_NOISE_LEVEL,
CONF_SPIKE_REJECTION,
CONF_WATCHDOG_THRESHOLD,
) )
MULTI_CONF = True MULTI_CONF = True
@ -19,7 +22,6 @@ CONF_AS3935_ID = "as3935_id"
as3935_ns = cg.esphome_ns.namespace("as3935") as3935_ns = cg.esphome_ns.namespace("as3935")
AS3935 = as3935_ns.class_("AS3935Component", cg.Component) AS3935 = as3935_ns.class_("AS3935Component", cg.Component)
CONF_IRQ_PIN = "irq_pin"
AS3935_SCHEMA = cv.Schema( AS3935_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(AS3935), cv.GenerateID(): cv.declare_id(AS3935),
@ -34,6 +36,8 @@ AS3935_SCHEMA = cv.Schema(
cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean, cv.Optional(CONF_MASK_DISTURBER, default=False): cv.boolean,
cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True), cv.Optional(CONF_DIV_RATIO, default=0): cv.one_of(0, 16, 32, 64, 128, int=True),
cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15), cv.Optional(CONF_CAPACITANCE, default=0): cv.int_range(min=0, max=15),
cv.Optional(CONF_TUNE_ANTENNA, default=False): cv.boolean,
cv.Optional(CONF_CALIBRATION, default=True): cv.boolean,
} }
) )
@ -51,3 +55,5 @@ async def setup_as3935(var, config):
cg.add(var.set_mask_disturber(config[CONF_MASK_DISTURBER])) cg.add(var.set_mask_disturber(config[CONF_MASK_DISTURBER]))
cg.add(var.set_div_ratio(config[CONF_DIV_RATIO])) cg.add(var.set_div_ratio(config[CONF_DIV_RATIO]))
cg.add(var.set_capacitance(config[CONF_CAPACITANCE])) cg.add(var.set_capacitance(config[CONF_CAPACITANCE]))
cg.add(var.set_tune_antenna(config[CONF_TUNE_ANTENNA]))
cg.add(var.set_calibration(config[CONF_CALIBRATION]))

View file

@ -21,6 +21,14 @@ void AS3935Component::setup() {
this->write_mask_disturber(this->mask_disturber_); this->write_mask_disturber(this->mask_disturber_);
this->write_div_ratio(this->div_ratio_); this->write_div_ratio(this->div_ratio_);
this->write_capacitance(this->capacitance_); this->write_capacitance(this->capacitance_);
// Handle setting up tuning or auto-calibration
if (this->tune_antenna_) {
ESP_LOGCONFIG(TAG, " Antenna tuning: ENABLED - lightning detection will not function in this mode");
this->tune_antenna();
} else if (this->calibration_) {
this->calibrate_oscillator();
}
} }
void AS3935Component::dump_config() { void AS3935Component::dump_config() {
@ -227,6 +235,87 @@ uint32_t AS3935Component::get_lightning_energy_() {
return pure_light; return pure_light;
} }
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
// This function returns the current division ratio of the resonance frequency.
// The antenna resonance frequency should be within 3.5 percent of 500kHz, and
// so when modifying the resonance frequency with the internal capacitors
// (tuneCap()) it's important to keep in mind that the displayed frequency on
// the IRQ pin is divided by this number.
uint8_t AS3935Component::read_div_ratio() {
ESP_LOGV(TAG, "Calling read_div_ratio");
uint8_t reg_val = this->read_register_(INT_MASK_ANT, DIV_MASK);
reg_val >>= 6; // Front of the line.
if (reg_val == 0) {
return 16;
} else if (reg_val == 1) {
return 32;
} else if (reg_val == 2) {
return 64;
} else if (reg_val == 3) {
return 128;
}
ESP_LOGW(TAG, "Unknown response received for div_ratio");
return 0;
}
uint8_t AS3935Component::read_capacitance() {
ESP_LOGV(TAG, "Calling read_capacitance");
uint8_t reg_val = this->read_register_(FREQ_DISP_IRQ, CAP_MASK) * 8;
return (reg_val);
}
// REG0x08, bits [5,6,7], manufacturer default: 0.
// This will send the frequency of the oscillators to the IRQ pin.
// _osc 1, bit[5] = TRCO - System RCO at 32.768kHz
// _osc 2, bit[6] = SRCO - Timer RCO Oscillators 1.1MHz
// _osc 3, bit[7] = LCO - Frequency of the Antenna
void AS3935Component::display_oscillator(bool state, uint8_t osc) {
if ((osc < 1) || (osc > 3))
return;
this->write_register(FREQ_DISP_IRQ, OSC_MASK, state, 4 + osc);
}
// REG0x3D, bits[7:0]
// This function calibrates both internal oscillators The oscillators are tuned
// based on the resonance frequency of the antenna and so it should be trimmed
// before the calibration is done.
bool AS3935Component::calibrate_oscillator() {
ESP_LOGI(TAG, "Starting oscillators calibration...");
this->write_register(CALIB_RCO, WIPE_ALL, DIRECT_COMMAND, 0); // Send command to calibrate the oscillators
this->display_oscillator(true, 2);
delay(2); // Give time for the internal oscillators to start up.
this->display_oscillator(false, 2);
// Check it they were calibrated successfully.
uint8_t reg_val_srco = this->read_register_(CALIB_SRCO, CALIB_MASK_NOK);
uint8_t reg_val_trco = this->read_register_(CALIB_TRCO, CALIB_MASK_NOK);
// reg_val_srco &= CALIB_MASK;
// reg_val_srco >>= 6;
// reg_val_trco &= CALIB_MASK;
// reg_val_trco >>= 6;
if (!reg_val_srco && !reg_val_trco) { // Zero upon success
ESP_LOGI(TAG, "Calibration was succesful");
return true;
} else {
ESP_LOGW(TAG, "Calibration was NOT succesful");
return false;
}
}
void AS3935Component::tune_antenna() {
ESP_LOGI(TAG, "Starting antenna tuning...");
uint8_t div_ratio = this->read_div_ratio();
uint8_t tune_val = this->read_capacitance();
ESP_LOGI(TAG, "Division Ratio is set to: %d", div_ratio);
ESP_LOGI(TAG, "Internal Capacitor is set to: %d", tune_val);
ESP_LOGI(TAG, "Displaying oscillator on INT pin. Measure its frequency - multiply value by Division Ratio");
this->display_oscillator(true, ANTFREQ);
}
uint8_t AS3935Component::read_register_(uint8_t reg, uint8_t mask) { uint8_t AS3935Component::read_register_(uint8_t reg, uint8_t mask) {
uint8_t value = this->read_register(reg); uint8_t value = this->read_register(reg);
value &= (~mask); value &= (~mask);

View file

@ -13,6 +13,9 @@
namespace esphome { namespace esphome {
namespace as3935 { namespace as3935 {
static const uint8_t DIRECT_COMMAND = 0x96;
static const uint8_t ANTFREQ = 3;
enum AS3935RegisterNames { enum AS3935RegisterNames {
AFE_GAIN = 0x00, AFE_GAIN = 0x00,
THRESHOLD, THRESHOLD,
@ -30,6 +33,7 @@ enum AS3935RegisterNames {
}; };
enum AS3935RegisterMasks { enum AS3935RegisterMasks {
WIPE_ALL = 0x0,
GAIN_MASK = 0x3E, GAIN_MASK = 0x3E,
SPIKE_MASK = 0xF, SPIKE_MASK = 0xF,
IO_MASK = 0xC1, IO_MASK = 0xC1,
@ -44,6 +48,7 @@ enum AS3935RegisterMasks {
NOISE_FLOOR_MASK = 0x70, NOISE_FLOOR_MASK = 0x70,
OSC_MASK = 0xE0, OSC_MASK = 0xE0,
CALIB_MASK = 0x7F, CALIB_MASK = 0x7F,
CALIB_MASK_NOK = 0xBF,
DIV_MASK = 0x3F DIV_MASK = 0x3F
}; };
@ -90,6 +95,13 @@ class AS3935Component : public Component {
void write_div_ratio(uint8_t div_ratio); void write_div_ratio(uint8_t div_ratio);
void set_capacitance(uint8_t capacitance) { capacitance_ = capacitance; } void set_capacitance(uint8_t capacitance) { capacitance_ = capacitance; }
void write_capacitance(uint8_t capacitance); void write_capacitance(uint8_t capacitance);
uint8_t read_div_ratio();
uint8_t read_capacitance();
bool calibrate_oscillator();
void display_oscillator(bool state, uint8_t osc);
void tune_antenna();
void set_tune_antenna(bool tune_antenna) { tune_antenna_ = tune_antenna; }
void set_calibration(bool calibration) { calibration_ = calibration; }
protected: protected:
uint8_t read_interrupt_register_(); uint8_t read_interrupt_register_();
@ -112,6 +124,8 @@ class AS3935Component : public Component {
bool mask_disturber_; bool mask_disturber_;
uint8_t div_ratio_; uint8_t div_ratio_;
uint8_t capacitance_; uint8_t capacitance_;
bool tune_antenna_;
bool calibration_;
}; };
} // namespace as3935 } // namespace as3935

View file

@ -2,13 +2,19 @@
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.core import CORE, coroutine_with_priority
from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
)
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema({}), cv.Schema({}),
cv.only_with_arduino, cv.only_with_arduino,
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
) )

View file

@ -12,6 +12,8 @@ static const uint8_t BP1658CJ_ADDR_START_3CH = 0x10;
static const uint8_t BP1658CJ_ADDR_START_2CH = 0x20; static const uint8_t BP1658CJ_ADDR_START_2CH = 0x20;
static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30; static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30;
static const uint8_t BP1658CJ_DELAY = 2;
void BP1658CJ::setup() { void BP1658CJ::setup() {
ESP_LOGCONFIG(TAG, "Setting up BP1658CJ Output Component..."); ESP_LOGCONFIG(TAG, "Setting up BP1658CJ Output Component...");
this->data_pin_->setup(); this->data_pin_->setup();
@ -81,27 +83,41 @@ void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
} }
this->pwm_amounts_[channel] = value; this->pwm_amounts_[channel] = value;
} }
void BP1658CJ::write_bit_(bool value) { void BP1658CJ::write_bit_(bool value) {
this->clock_pin_->digital_write(false);
this->data_pin_->digital_write(value); this->data_pin_->digital_write(value);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(BP1658CJ_DELAY);
this->clock_pin_->digital_write(false);
} }
void BP1658CJ::write_byte_(uint8_t data) { void BP1658CJ::write_byte_(uint8_t data) {
for (uint8_t mask = 0x80; mask; mask >>= 1) { for (uint8_t mask = 0x80; mask; mask >>= 1) {
this->write_bit_(data & mask); this->write_bit_(data & mask);
delayMicroseconds(BP1658CJ_DELAY);
} }
this->clock_pin_->digital_write(false);
this->data_pin_->digital_write(true); // ack bit
this->data_pin_->pin_mode(gpio::FLAG_INPUT);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(BP1658CJ_DELAY);
this->clock_pin_->digital_write(false);
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
} }
void BP1658CJ::write_buffer_(uint8_t *buffer, uint8_t size) { void BP1658CJ::write_buffer_(uint8_t *buffer, uint8_t size) {
this->data_pin_->digital_write(false); this->data_pin_->digital_write(false);
this->clock_pin_->digital_write(false);
for (uint32_t i = 0; i < size; i++) { for (uint32_t i = 0; i < size; i++) {
this->write_byte_(buffer[i]); this->write_byte_(buffer[i]);
delayMicroseconds(BP1658CJ_DELAY);
} }
this->clock_pin_->digital_write(false);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
this->data_pin_->digital_write(true); this->data_pin_->digital_write(true);
} }

View file

@ -2,7 +2,13 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import web_server_base from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.const import CONF_ID from esphome.const import (
CONF_ID,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
)
from esphome.core import coroutine_with_priority, CORE from esphome.core import coroutine_with_priority, CORE
AUTO_LOAD = ["web_server_base"] AUTO_LOAD = ["web_server_base"]
@ -21,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
) )

View file

@ -48,7 +48,7 @@ void CaptivePortal::start() {
this->dns_server_ = make_unique<DNSServer>(); this->dns_server_ = make_unique<DNSServer>();
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
this->dns_server_->start(53, "*", (uint32_t) ip); this->dns_server_->start(53, "*", IPAddress(ip));
#endif #endif
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {

View file

@ -14,6 +14,8 @@ from esphome.const import (
CONF_SLEEP_DURATION, CONF_SLEEP_DURATION,
CONF_TIME_ID, CONF_TIME_ID,
CONF_WAKEUP_PIN, CONF_WAKEUP_PIN,
PLATFORM_ESP32,
PLATFORM_ESP8266,
) )
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
@ -24,6 +26,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S3, VARIANT_ESP32S3,
VARIANT_ESP32C2, VARIANT_ESP32C2,
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2,
) )
WAKEUP_PINS = { WAKEUP_PINS = {
@ -98,6 +101,7 @@ WAKEUP_PINS = {
], ],
VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5],
VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7],
VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14],
} }
@ -163,7 +167,8 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema(
} }
) )
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.All(
cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(DeepSleepComponent), cv.GenerateID(): cv.declare_id(DeepSleepComponent),
cv.Optional(CONF_RUN_DURATION): cv.Any( cv.Optional(CONF_RUN_DURATION): cv.Any(
@ -172,7 +177,9 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds, cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAKEUP_PIN): cv.All( cv.Optional(CONF_WAKEUP_PIN): cv.All(
cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number cv.only_on_esp32,
pins.internal_gpio_input_pin_schema,
validate_pin_number,
), ),
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All( cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True
@ -190,7 +197,9 @@ CONFIG_SCHEMA = cv.Schema(
), ),
cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
)
async def to_code(config): async def to_code(config):

View file

@ -40,7 +40,6 @@ void E131Component::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
server.ss_family = AF_INET;
err = this->socket_->bind((struct sockaddr *) &server, sizeof(server)); err = this->socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) { if (err != 0) {

View file

@ -67,8 +67,8 @@ bool E131Component::join_igmp_groups_() {
if (!universe.second) if (!universe.second)
continue; continue;
ip4_addr_t multicast_addr = {static_cast<uint32_t>( ip4_addr_t multicast_addr =
network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff)))}; network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff));
auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr); auto err = igmp_joingroup(IP4_ADDR_ANY4, &multicast_addr);
@ -101,8 +101,7 @@ void E131Component::leave_(int universe) {
} }
if (listen_method_ == E131_MULTICAST) { if (listen_method_ == E131_MULTICAST) {
ip4_addr_t multicast_addr = { ip4_addr_t multicast_addr = network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
static_cast<uint32_t>(network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff)))};
igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr); igmp_leavegroup(IP4_ADDR_ANY4, &multicast_addr);
} }

View file

@ -25,6 +25,7 @@ from esphome.const import (
KEY_NAME, KEY_NAME,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
__version__, __version__,
@ -62,7 +63,7 @@ AUTO_LOAD = ["preferences"]
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_ESP32] = {} CORE.data[KEY_ESP32] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp32" CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP32
conf = config[CONF_FRAMEWORK] conf = config[CONF_FRAMEWORK]
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf"

View file

@ -1,11 +1,53 @@
import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv import esphome.config_validation as cv
_ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}
_ESP32H2_USB_JTAG_PINS = {26, 27}
_ESP32H2_STRAPPING_PINS = {2, 3, 8, 9, 25}
_LOGGER = logging.getLogger(__name__)
def esp32_h2_validate_gpio_pin(value): def esp32_h2_validate_gpio_pin(value):
# ESP32-H2 not yet supported if value < 0 or value > 27:
raise cv.Invalid("ESP32-H2 isn't supported yet") raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
if value in _ESP32H2_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in _ESP32H2_SPI_FLASH_PINS:
_LOGGER.warning(
"GPIO%d is reserved for SPI Flash communication on some ESP32-H2 chip variants.\n"
"Utilizing SPI-reserved pins could cause unexpected failures.\n"
"See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html",
value,
)
if value in _ESP32H2_USB_JTAG_PINS:
_LOGGER.warning(
"GPIO%d is reserved for the USB-Serial-JTAG interface.\n"
"To use this pin as GPIO, USB-Serial-JTAG will be disabled.",
value,
)
return value
def esp32_h2_validate_supports(value): def esp32_h2_validate_supports(value):
# ESP32-H2 not yet supported num = value[CONF_NUMBER]
raise cv.Invalid("ESP32-H2 isn't supported yet") mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
if num < 0 or num > 27:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
if is_input:
# All ESP32 pins support input mode
pass
return value

View file

@ -64,6 +64,7 @@ RMT_CHANNELS = {
esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32C3: [0, 1], esp32.const.VARIANT_ESP32C3: [0, 1],
esp32.const.VARIANT_ESP32C6: [0, 1], esp32.const.VARIANT_ESP32C6: [0, 1],
esp32.const.VARIANT_ESP32H2: [0, 1],
} }

View file

@ -11,6 +11,7 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_ESP8266,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
import esphome.config_validation as cv import esphome.config_validation as cv
@ -38,7 +39,7 @@ AUTO_LOAD = ["preferences"]
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_ESP8266] = {} CORE.data[KEY_ESP8266] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "esp8266" CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_ESP8266
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION] config[CONF_FRAMEWORK][CONF_VERSION]

View file

@ -236,7 +236,7 @@ bool EthernetComponent::can_proceed() { return this->is_connected(); }
network::IPAddress EthernetComponent::get_ip_address() { network::IPAddress EthernetComponent::get_ip_address() {
esp_netif_ip_info_t ip; esp_netif_ip_info_t ip;
esp_netif_get_ip_info(this->eth_netif_, &ip); esp_netif_get_ip_info(this->eth_netif_, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) {
@ -293,9 +293,9 @@ void EthernetComponent::start_connect_() {
esp_netif_ip_info_t info; esp_netif_ip_info_t info;
if (this->manual_ip_.has_value()) { if (this->manual_ip_.has_value()) {
info.ip.addr = static_cast<uint32_t>(this->manual_ip_->static_ip); info.ip = this->manual_ip_->static_ip;
info.gw.addr = static_cast<uint32_t>(this->manual_ip_->gateway); info.gw = this->manual_ip_->gateway;
info.netmask.addr = static_cast<uint32_t>(this->manual_ip_->subnet); info.netmask = this->manual_ip_->subnet;
} else { } else {
info.ip.addr = 0; info.ip.addr = 0;
info.gw.addr = 0; info.gw.addr = 0;
@ -318,24 +318,14 @@ void EthernetComponent::start_connect_() {
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error"); ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
if (this->manual_ip_.has_value()) { if (this->manual_ip_.has_value()) {
if (uint32_t(this->manual_ip_->dns1) != 0) { if (this->manual_ip_->dns1.is_set()) {
ip_addr_t d; ip_addr_t d;
#if LWIP_IPV6 d = this->manual_ip_->dns1;
d.type = IPADDR_TYPE_V4;
d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns1);
#else
d.addr = static_cast<uint32_t>(this->manual_ip_->dns1);
#endif
dns_setserver(0, &d); dns_setserver(0, &d);
} }
if (uint32_t(this->manual_ip_->dns2) != 0) { if (this->manual_ip_->dns2.is_set()) {
ip_addr_t d; ip_addr_t d;
#if LWIP_IPV6 d = this->manual_ip_->dns2;
d.type = IPADDR_TYPE_V4;
d.u_addr.ip4.addr = static_cast<uint32_t>(this->manual_ip_->dns2);
#else
d.addr = static_cast<uint32_t>(this->manual_ip_->dns2);
#endif
dns_setserver(1, &d); dns_setserver(1, &d);
} }
} else { } else {
@ -360,21 +350,16 @@ bool EthernetComponent::is_connected() { return this->state_ == EthernetComponen
void EthernetComponent::dump_connect_params_() { void EthernetComponent::dump_connect_params_() {
esp_netif_ip_info_t ip; esp_netif_ip_info_t ip;
esp_netif_get_ip_info(this->eth_netif_, &ip); esp_netif_get_ip_info(this->eth_netif_, &ip);
ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(ip.ip.addr).str().c_str()); ESP_LOGCONFIG(TAG, " IP Address: %s", network::IPAddress(&ip.ip).str().c_str());
ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str()); ESP_LOGCONFIG(TAG, " Hostname: '%s'", App.get_name().c_str());
ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(ip.netmask.addr).str().c_str()); ESP_LOGCONFIG(TAG, " Subnet: %s", network::IPAddress(&ip.netmask).str().c_str());
ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(ip.gw.addr).str().c_str()); ESP_LOGCONFIG(TAG, " Gateway: %s", network::IPAddress(&ip.gw).str().c_str());
const ip_addr_t *dns_ip1 = dns_getserver(0); const ip_addr_t *dns_ip1 = dns_getserver(0);
const ip_addr_t *dns_ip2 = dns_getserver(1); const ip_addr_t *dns_ip2 = dns_getserver(1);
#if LWIP_IPV6 ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1).str().c_str());
ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->u_addr.ip4.addr).str().c_str()); ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str());
ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->u_addr.ip4.addr).str().c_str());
#else
ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1->addr).str().c_str());
ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str());
#endif
#if ENABLE_IPV6 #if ENABLE_IPV6
if (this->ipv6_count_ > 0) { if (this->ipv6_count_ > 0) {

View file

@ -67,18 +67,13 @@ def validate_pillow_installed(value):
except ImportError as err: except ImportError as err:
raise cv.Invalid( raise cv.Invalid(
"Please install the pillow python package to use this feature. " "Please install the pillow python package to use this feature. "
'(pip install pillow">4.0.0,<10.0.0")' '(pip install "pillow==10.0.1")'
) from err ) from err
if version.parse(PIL.__version__) < version.parse("4.0.0"): if version.parse(PIL.__version__) != version.parse("10.0.1"):
raise cv.Invalid( raise cv.Invalid(
"Please update your pillow installation to at least 4.0.x. " "Please update your pillow installation to 10.0.1. "
'(pip install pillow">4.0.0,<10.0.0")' '(pip install "pillow==10.0.1")'
)
if version.parse(PIL.__version__) >= version.parse("10.0.0"):
raise cv.Invalid(
"Please downgrade your pillow installation to below 10.0.0. "
'(pip install pillow">4.0.0,<10.0.0")'
) )
return value return value

View file

@ -3,6 +3,9 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import ( from esphome.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_FIELD_STRENGTH_X,
CONF_FIELD_STRENGTH_Y,
CONF_FIELD_STRENGTH_Z,
CONF_ID, CONF_ID,
CONF_OVERSAMPLING, CONF_OVERSAMPLING,
CONF_RANGE, CONF_RANGE,
@ -18,9 +21,6 @@ DEPENDENCIES = ["i2c"]
hmc5883l_ns = cg.esphome_ns.namespace("hmc5883l") hmc5883l_ns = cg.esphome_ns.namespace("hmc5883l")
CONF_FIELD_STRENGTH_X = "field_strength_x"
CONF_FIELD_STRENGTH_Y = "field_strength_y"
CONF_FIELD_STRENGTH_Z = "field_strength_z"
CONF_HEADING = "heading" CONF_HEADING = "heading"
HMC5883LComponent = hmc5883l_ns.class_( HMC5883LComponent = hmc5883l_ns.class_(

View file

@ -3,6 +3,7 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_HOST,
) )
from esphome.core import CORE from esphome.core import CORE
import esphome.config_validation as cv import esphome.config_validation as cv
@ -20,7 +21,7 @@ AUTO_LOAD = ["network"]
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_HOST] = {} CORE.data[KEY_HOST] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host" CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_HOST
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0) CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
return config return config

View file

@ -12,6 +12,9 @@ from esphome.const import (
CONF_SDA, CONF_SDA,
CONF_ADDRESS, CONF_ADDRESS,
CONF_I2C_ID, CONF_I2C_ID,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
) )
from esphome.core import coroutine_with_priority, CORE from esphome.core import coroutine_with_priority, CORE
@ -60,7 +63,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SCAN, default=True): cv.boolean, cv.Optional(CONF_SCAN, default=True): cv.boolean,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on(["esp32", "esp8266", "rp2040"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
) )

View file

@ -66,8 +66,9 @@ void I2SAudioMicrophone::start_() {
i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_); i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
i2s_adc_enable(this->parent_->get_port()); i2s_adc_enable(this->parent_->get_port());
} else { } else
#endif #endif
{
if (this->pdm_) if (this->pdm_)
config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM); config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
@ -77,9 +78,7 @@ void I2SAudioMicrophone::start_() {
pin_config.data_in_num = this->din_pin_; pin_config.data_in_num = this->din_pin_;
i2s_set_pin(this->parent_->get_port(), &pin_config); i2s_set_pin(this->parent_->get_port(), &pin_config);
#if SOC_I2S_SUPPORTS_ADC
} }
#endif
this->state_ = microphone::STATE_RUNNING; this->state_ = microphone::STATE_RUNNING;
this->high_freq_.start(); this->high_freq_.start();
} }
@ -110,6 +109,10 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) {
this->status_set_warning(); this->status_set_warning();
return 0; return 0;
} }
if (bytes_read == 0) {
this->status_set_warning();
return 0;
}
this->status_clear_warning(); this->status_clear_warning();
if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) {
return bytes_read; return bytes_read;

View file

@ -11,7 +11,7 @@
namespace esphome { namespace esphome {
namespace i2s_audio { namespace i2s_audio {
static const size_t BUFFER_COUNT = 10; static const size_t BUFFER_COUNT = 20;
static const char *const TAG = "i2s_audio.speaker"; static const char *const TAG = "i2s_audio.speaker";
@ -19,7 +19,7 @@ void I2SAudioSpeaker::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker..."); ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent)); this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent));
this->event_queue_ = xQueueCreate(20, sizeof(TaskEvent)); this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent));
} }
void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; } void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
@ -47,7 +47,7 @@ void I2SAudioSpeaker::player_task(void *params) {
.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 = 1024, .dma_buf_len = 128,
.use_apll = false, .use_apll = false,
.tx_desc_auto_clear = true, .tx_desc_auto_clear = true,
.fixed_mclk = I2S_PIN_NO_CHANGE, .fixed_mclk = I2S_PIN_NO_CHANGE,
@ -60,7 +60,17 @@ void I2SAudioSpeaker::player_task(void *params) {
} }
#endif #endif
i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr); esp_err_t err = i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr);
if (err != ESP_OK) {
event.type = TaskEventType::WARNING;
event.err = err;
xQueueSend(this_speaker->event_queue_, &event, 0);
event.type = TaskEventType::STOPPED;
xQueueSend(this_speaker->event_queue_, &event, 0);
while (true) {
delay(10);
}
}
#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) {
@ -88,9 +98,7 @@ void I2SAudioSpeaker::player_task(void *params) {
} }
if (data_event.stop) { if (data_event.stop) {
// Stop signal from main thread // Stop signal from main thread
while (xQueueReceive(this_speaker->buffer_queue_, &data_event, 0) == pdTRUE) { xQueueReset(this_speaker->buffer_queue_); // Flush queue
// Flush queue
}
break; break;
} }
size_t bytes_written; size_t bytes_written;
@ -103,7 +111,7 @@ void I2SAudioSpeaker::player_task(void *params) {
uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF); uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written, esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
(100 / 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};
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
@ -122,7 +130,6 @@ void I2SAudioSpeaker::player_task(void *params) {
event.type = TaskEventType::STOPPING; event.type = TaskEventType::STOPPING;
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY); xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
i2s_stop(this_speaker->parent_->get_port());
i2s_driver_uninstall(this_speaker->parent_->get_port()); i2s_driver_uninstall(this_speaker->parent_->get_port());
event.type = TaskEventType::STOPPED; event.type = TaskEventType::STOPPED;
@ -158,10 +165,11 @@ void I2SAudioSpeaker::watch_() {
this->status_clear_warning(); this->status_clear_warning();
break; break;
case TaskEventType::STOPPED: case TaskEventType::STOPPED:
this->parent_->unlock();
this->state_ = speaker::STATE_STOPPED; this->state_ = speaker::STATE_STOPPED;
vTaskDelete(this->player_task_handle_); vTaskDelete(this->player_task_handle_);
this->player_task_handle_ = nullptr; this->player_task_handle_ = nullptr;
this->parent_->unlock();
xQueueReset(this->buffer_queue_);
break; break;
case TaskEventType::WARNING: case TaskEventType::WARNING:
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err)); ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err));
@ -177,9 +185,9 @@ void I2SAudioSpeaker::loop() {
this->start_(); this->start_();
break; break;
case speaker::STATE_RUNNING: case speaker::STATE_RUNNING:
case speaker::STATE_STOPPING:
this->watch_(); this->watch_();
break; break;
case speaker::STATE_STOPPING:
case speaker::STATE_STOPPED: case speaker::STATE_STOPPED:
break; break;
} }

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core, pins from esphome import core, pins
from esphome.components import display, spi from esphome.components import display, spi, font
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
from esphome.const import ( from esphome.const import (
CONF_COLOR_PALETTE, CONF_COLOR_PALETTE,
@ -13,7 +13,6 @@ from esphome.const import (
CONF_PAGES, CONF_PAGES,
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_DIMENSIONS, CONF_DIMENSIONS,
CONF_DATA_RATE,
) )
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -55,6 +54,7 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
CONF_LED_PIN = "led_pin" CONF_LED_PIN = "led_pin"
CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
CONF_INVERT_DISPLAY = "invert_display"
def _validate(config): def _validate(config):
@ -85,6 +85,7 @@ def _validate(config):
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend( display.FULL_DISPLAY_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(ili9XXXSPI), cv.GenerateID(): cv.declare_id(ili9XXXSPI),
@ -100,11 +101,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
cv.file_ cv.file_
), ),
cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA, cv.Optional(CONF_INVERT_DISPLAY): cv.boolean,
} }
) )
.extend(cv.polling_component_schema("1s")) .extend(cv.polling_component_schema("1s"))
.extend(spi.spi_device_schema(False)), .extend(spi.spi_device_schema(False, "40MHz")),
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
_validate, _validate,
) )
@ -164,7 +165,7 @@ async def to_code(config):
x = x + i.width x = x + i.width
# reduce the colors on combined image to 256. # reduce the colors on combined image to 256.
converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256) converted = ref_image.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
# if you want to verify how the images look use # if you want to verify how the images look use
# ref_image.save("ref_in.png") # ref_image.save("ref_in.png")
# converted.save("ref_out.png") # converted.save("ref_out.png")
@ -177,4 +178,6 @@ async def to_code(config):
if rhs is not None: if rhs is not None:
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr)) cg.add(var.set_palette(prog_arr))
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
if CONF_INVERT_DISPLAY in config:
cg.add(var.invert_display(config[CONF_INVERT_DISPLAY]))

View file

@ -12,11 +12,13 @@ static const char *const TAG = "ili9xxx";
void ILI9XXXDisplay::setup() { void ILI9XXXDisplay::setup() {
this->setup_pins_(); this->setup_pins_();
this->initialize(); this->initialize();
this->command(this->pre_invertdisplay_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
this->x_low_ = this->width_; this->x_low_ = this->width_;
this->y_low_ = this->height_; this->y_low_ = this->height_;
this->x_high_ = 0; this->x_high_ = 0;
this->y_high_ = 0; this->y_high_ = 0;
if (this->buffer_color_mode_ == BITS_16) { if (this->buffer_color_mode_ == BITS_16) {
this->init_internal_(this->get_buffer_length_() * 2); this->init_internal_(this->get_buffer_length_() * 2);
if (this->buffer_ != nullptr) { if (this->buffer_ != nullptr) {
@ -333,7 +335,12 @@ void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint
this->command(ILI9XXX_RAMWR); // Write to RAM this->command(ILI9XXX_RAMWR); // Write to RAM
} }
void ILI9XXXDisplay::invert_display_(bool invert) { this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } void ILI9XXXDisplay::invert_display(bool invert) {
this->pre_invertdisplay_ = invert;
if (is_ready()) {
this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF);
}
}
int ILI9XXXDisplay::get_width_internal() { return this->width_; } int ILI9XXXDisplay::get_width_internal() { return this->width_; }
int ILI9XXXDisplay::get_height_internal() { return this->height_; } int ILI9XXXDisplay::get_height_internal() { return this->height_; }
@ -345,7 +352,7 @@ void ILI9XXXM5Stack::initialize() {
this->width_ = 320; this->width_ = 320;
if (this->height_ == 0) if (this->height_ == 0)
this->height_ = 240; this->height_ = 240;
this->invert_display_(true); this->pre_invertdisplay_ = true;
} }
// M5CORE display // Based on the configuration settings of M5stact's M5GFX code. // M5CORE display // Based on the configuration settings of M5stact's M5GFX code.
@ -355,7 +362,7 @@ void ILI9XXXM5CORE::initialize() {
this->width_ = 320; this->width_ = 320;
if (this->height_ == 0) if (this->height_ == 0)
this->height_ = 240; this->height_ = 240;
this->invert_display_(true); this->pre_invertdisplay_ = true;
} }
// 24_TFT display // 24_TFT display
@ -462,7 +469,7 @@ void ILI9XXXS3BoxLite::initialize() {
if (this->height_ == 0) { if (this->height_ == 0) {
this->height_ = 240; this->height_ = 240;
} }
this->invert_display_(true); this->pre_invertdisplay_ = true;
} }
} // namespace ili9xxx } // namespace ili9xxx

View file

@ -33,6 +33,7 @@ class ILI9XXXDisplay : public PollingComponent,
this->height_ = height; this->height_ = height;
this->width_ = width; this->width_ = width;
} }
void invert_display(bool invert);
void command(uint8_t value); void command(uint8_t value);
void data(uint8_t value); void data(uint8_t value);
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
@ -55,7 +56,7 @@ class ILI9XXXDisplay : public PollingComponent,
void display_(); void display_();
void init_lcd_(const uint8_t *init_cmd); void init_lcd_(const uint8_t *init_cmd);
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void invert_display_(bool invert);
void reset_(); void reset_();
int16_t width_{0}; ///< Display width as modified by current rotation int16_t width_{0}; ///< Display width as modified by current rotation
@ -88,6 +89,7 @@ class ILI9XXXDisplay : public PollingComponent,
bool prossing_update_ = false; bool prossing_update_ = false;
bool need_update_ = false; bool need_update_ = false;
bool is_18bitdisplay_ = false; bool is_18bitdisplay_ = false;
bool pre_invertdisplay_ = false;
}; };
//----------- M5Stack display -------------- //----------- M5Stack display --------------

View file

@ -255,7 +255,11 @@ async def to_code(config):
transparent = config[CONF_USE_TRANSPARENCY] transparent = config[CONF_USE_TRANSPARENCY]
dither = Image.NONE if config[CONF_DITHER] == "NONE" else Image.FLOYDSTEINBERG dither = (
Image.Dither.NONE
if config[CONF_DITHER] == "NONE"
else Image.Dither.FLOYDSTEINBERG
)
if config[CONF_TYPE] == "GRAYSCALE": if config[CONF_TYPE] == "GRAYSCALE":
image = image.convert("LA", dither=dither) image = image.convert("LA", dither=dither)
pixels = list(image.getdata()) pixels = list(image.getdata())

View file

@ -12,6 +12,8 @@ from esphome.const import (
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
KEY_CORE, KEY_CORE,
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
PLATFORM_ESP32,
PLATFORM_RP2040,
) )
from esphome.core import CORE from esphome.core import CORE
@ -49,7 +51,7 @@ CONFIG_SCHEMA = cv.All(
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("60s")), ).extend(cv.polling_component_schema("60s")),
cv.only_on(["esp32", "rp2040"]), cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040]),
validate_config, validate_config,
) )

View file

@ -32,6 +32,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S3, VARIANT_ESP32S3,
VARIANT_ESP32C2, VARIANT_ESP32C2,
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2,
) )
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import ( from esphome.components.libretiny.const import (
@ -86,6 +87,7 @@ UART_SELECTION_ESP32 = {
VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG], VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG],
VARIANT_ESP32C2: [UART0, UART1], VARIANT_ESP32C2: [UART0, UART1],
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
} }
UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]

View file

@ -121,7 +121,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
if ( if (
#if defined(USE_ESP32_VARIANT_ESP32S2) #if defined(USE_ESP32_VARIANT_ESP32S2)
uart_ == UART_SELECTION_USB_CDC uart_ == UART_SELECTION_USB_CDC
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
uart_ == UART_SELECTION_USB_SERIAL_JTAG uart_ == UART_SELECTION_USB_SERIAL_JTAG
#elif defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32S3)
uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG uart_ == UART_SELECTION_USB_CDC || uart_ == UART_SELECTION_USB_SERIAL_JTAG
@ -218,21 +218,24 @@ void Logger::pre_setup() {
uart_num_ = UART_NUM_1; uart_num_ = UART_NUM_1;
break; break;
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
case UART_SELECTION_UART2: case UART_SELECTION_UART2:
uart_num_ = UART_NUM_2; uart_num_ = UART_NUM_2;
break; break;
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 &&
// !USE_ESP32_VARIANT_ESP32H2
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
case UART_SELECTION_USB_CDC: case UART_SELECTION_USB_CDC:
uart_num_ = -1; uart_num_ = -1;
break; break;
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32H2)
case UART_SELECTION_USB_SERIAL_JTAG: case UART_SELECTION_USB_SERIAL_JTAG:
uart_num_ = -1; uart_num_ = -1;
break; break;
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
// USE_ESP32_VARIANT_ESP32H2
} }
if (uart_num_ >= 0) { if (uart_num_ >= 0) {
uart_config_t uart_config{}; uart_config_t uart_config{};
@ -331,9 +334,10 @@ const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DE
const char *const UART_SELECTIONS[] = { const char *const UART_SELECTIONS[] = {
"UART0", "UART1", "UART0", "UART1",
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
"UART2", "UART2",
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARINT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
#if defined(USE_ESP_IDF) #if defined(USE_ESP_IDF)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
"USB_CDC", "USB_CDC",

View file

@ -41,16 +41,19 @@ enum UARTSelection {
UART_SELECTION_UART1, UART_SELECTION_UART1,
#if defined(USE_ESP32) #if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ #if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) !defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
UART_SELECTION_UART2, UART_SELECTION_UART2,
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
UART_SELECTION_USB_CDC, UART_SELECTION_USB_CDC,
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32H2)
UART_SELECTION_USB_SERIAL_JTAG, UART_SELECTION_USB_SERIAL_JTAG,
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
// USE_ESP32_VARIANT_ESP32H2
#endif // USE_ESP_IDF #endif // USE_ESP_IDF
#endif // USE_ESP32 #endif // USE_ESP32
#ifdef USE_ESP8266 #ifdef USE_ESP8266

View file

@ -10,7 +10,7 @@ namespace max6675 {
class MAX6675Sensor : public sensor::Sensor, class MAX6675Sensor : public sensor::Sensor,
public PollingComponent, public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1KHZ> { spi::DATA_RATE_1MHZ> {
public: public:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;

View file

@ -164,6 +164,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
this->disable(); this->disable();
} }
void MAX7219Component::update() { void MAX7219Component::update() {
if (this->intensity_changed_) {
this->send_to_all_(MAX7219_REGISTER_INTENSITY, this->intensity_);
this->intensity_changed_ = false;
}
for (uint8_t i = 0; i < this->num_chips_ * 8; i++) for (uint8_t i = 0; i < this->num_chips_ * 8; i++)
this->buffer_[i] = 0; this->buffer_[i] = 0;
if (this->writer_.has_value()) if (this->writer_.has_value())
@ -217,7 +221,13 @@ uint8_t MAX7219Component::printf(const char *format, ...) {
return 0; return 0;
} }
void MAX7219Component::set_writer(max7219_writer_t &&writer) { this->writer_ = writer; } void MAX7219Component::set_writer(max7219_writer_t &&writer) { this->writer_ = writer; }
void MAX7219Component::set_intensity(uint8_t intensity) { this->intensity_ = intensity; } void MAX7219Component::set_intensity(uint8_t intensity) {
intensity &= 0xF;
if (intensity != this->intensity_) {
this->intensity_changed_ = true;
this->intensity_ = intensity;
}
}
void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; } void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; }
uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) { uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) {

View file

@ -52,7 +52,8 @@ class MAX7219Component : public PollingComponent,
void send_byte_(uint8_t a_register, uint8_t data); void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data); void send_to_all_(uint8_t a_register, uint8_t data);
uint8_t intensity_{15}; /// Intensity of the display from 0 to 15 (most) uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most)
bool intensity_changed_{}; // True if we need to re-send the intensity
uint8_t num_chips_{1}; uint8_t num_chips_{1};
uint8_t *buffer_; uint8_t *buffer_;
bool reverse_{false}; bool reverse_{false};

View file

@ -13,8 +13,7 @@ namespace mdns {
void MDNSComponent::setup() { void MDNSComponent::setup() {
this->compile_records_(); this->compile_records_();
network::IPAddress addr = network::get_ip_address(); MDNS.begin(this->hostname_.c_str());
MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
for (const auto &service : this->services_) { for (const auto &service : this->services_) {
// Strip the leading underscore from the proto and service_type. While it is // Strip the leading underscore from the proto and service_type. While it is

View file

@ -3,6 +3,9 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import ( from esphome.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_FIELD_STRENGTH_X,
CONF_FIELD_STRENGTH_Y,
CONF_FIELD_STRENGTH_Z,
CONF_ID, CONF_ID,
ICON_MAGNET, ICON_MAGNET,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@ -16,9 +19,6 @@ DEPENDENCIES = ["i2c"]
mmc5603_ns = cg.esphome_ns.namespace("mmc5603") mmc5603_ns = cg.esphome_ns.namespace("mmc5603")
CONF_FIELD_STRENGTH_X = "field_strength_x"
CONF_FIELD_STRENGTH_Y = "field_strength_y"
CONF_FIELD_STRENGTH_Z = "field_strength_z"
CONF_HEADING = "heading" CONF_HEADING = "heading"
MMC5603Component = mmc5603_ns.class_( MMC5603Component = mmc5603_ns.class_(

View file

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

View file

@ -0,0 +1,141 @@
// See https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/tree/main
// for datasheets and an Arduino implementation.
#include "mmc5983.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mmc5983 {
static const char *const TAG = "mmc5983";
namespace {
constexpr uint8_t IC0_ADDR = 0x09;
constexpr uint8_t IC1_ADDR = 0x0a;
constexpr uint8_t IC2_ADDR = 0x0b;
constexpr uint8_t IC3_ADDR = 0x0c;
constexpr uint8_t PRODUCT_ID_ADDR = 0x2f;
float convert_data_to_millitesla(uint8_t data_17_10, uint8_t data_9_2, uint8_t data_1_0) {
int32_t counts = (data_17_10 << 10) | (data_9_2 << 2) | data_1_0;
counts -= 131072; // "Null Field Output" from datasheet.
// Sensitivity is 16384 counts/gauss, which is 163840 counts/mT.
return counts / 163840.0f;
}
} // namespace
void MMC5983Component::update() {
// Schedule a SET/RESET. This will recalibrate the sensor.
// We are supposed to be able to set this once, and have it automatically continue every reading, but
// this does not appear to work in continuous mode, even with En_prd_set turned on in Internal Control 2.
// Bit 5 = Auto_SR_en (automatic SET/RESET enable).
const uint8_t ic0_value = 0b10000;
i2c::ErrorCode err = this->write_register(IC0_ADDR, &ic0_value, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGW(TAG, "Writing Internal Control 0 failed with i2c error %d", err);
this->status_set_warning();
}
// Read out the data, 7 bytes starting from 0x00.
uint8_t data[7];
err = this->read_register(0x00, data, sizeof(data));
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGW(TAG, "Reading data failed with i2c error %d", err);
this->status_set_warning();
return;
}
// Unpack the data and publish to sensors.
// Data is in this format:
// data[0]: Xout[17:10]
// data[1]: Xout[9:2]
// data[2]: Yout[17:10]
// data[3]: Yout[9:2]
// data[4]: Zout[17:10]
// data[5]: Zout[9:2]
// data[6]: { Xout[1], Xout[0], Yout[1], Yout[0], Zout[1], Zout[0], 0, 0 }
if (this->x_sensor_) {
this->x_sensor_->publish_state(convert_data_to_millitesla(data[0], data[1], (data[6] & 0b11000000) >> 6));
}
if (this->y_sensor_) {
this->y_sensor_->publish_state(convert_data_to_millitesla(data[2], data[3], (data[6] & 0b00110000) >> 4));
}
if (this->z_sensor_) {
this->z_sensor_->publish_state(convert_data_to_millitesla(data[4], data[5], (data[6] & 0b00001100) >> 2));
}
}
void MMC5983Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MMC5983...");
// Verify product id.
const uint8_t mmc5983_product_id = 0x30;
uint8_t id;
i2c::ErrorCode err = this->read_register(PRODUCT_ID_ADDR, &id, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Reading product id failed with i2c error %d", err);
this->mark_failed();
return;
}
if (id != mmc5983_product_id) {
ESP_LOGE(TAG, "Product id 0x%02x does not match expected value 0x%02x", id, mmc5983_product_id);
this->mark_failed();
return;
}
// Initialize Internal Control registers to 0.
// Internal Control 0.
const uint8_t zero = 0;
err = this->write_register(IC0_ADDR, &zero, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Initializing Internal Control 0 failed with i2c error %d", err);
this->mark_failed();
return;
}
// Internal Control 1.
err = this->write_register(IC1_ADDR, &zero, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Initializing Internal Control 1 failed with i2c error %d", err);
this->mark_failed();
return;
}
// Internal Control 2.
err = this->write_register(IC2_ADDR, &zero, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Initializing Internal Control 2 failed with i2c error %d", err);
this->mark_failed();
return;
}
// Internal Control 3.
err = this->write_register(IC3_ADDR, &zero, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Initializing Internal Control 3 failed with i2c error %d", err);
this->mark_failed();
return;
}
// Enable continuous mode at 100 Hz, using Internal Control 2.
// Bit 3 = Cmm_en (continuous mode enable).
// Bit [2:0] = Cm_freq. 0b101 = 100 Hz, the fastest reading speed at Bandwidth=100 Hz.
const uint8_t ic2_value = 0b00001101;
err = this->write_register(IC2_ADDR, &ic2_value, 1);
if (err != i2c::ErrorCode::ERROR_OK) {
ESP_LOGE(TAG, "Writing Internal Control 2 failed with i2c error %d", err);
this->mark_failed();
return;
}
}
void MMC5983Component::dump_config() {
ESP_LOGD(TAG, "MMC5983:");
LOG_I2C_DEVICE(this);
LOG_SENSOR(" ", "X", this->x_sensor_);
LOG_SENSOR(" ", "Y", this->y_sensor_);
LOG_SENSOR(" ", "Z", this->z_sensor_);
}
float MMC5983Component::get_setup_priority() const { return setup_priority::DATA; }
} // namespace mmc5983
} // namespace esphome

View file

@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace mmc5983 {
class MMC5983Component : public PollingComponent, public i2c::I2CDevice {
public:
void update() override;
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; }
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
protected:
sensor::Sensor *x_sensor_{nullptr};
sensor::Sensor *y_sensor_{nullptr};
sensor::Sensor *z_sensor_{nullptr};
};
} // namespace mmc5983
} // namespace esphome

View file

@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_FIELD_STRENGTH_X,
CONF_FIELD_STRENGTH_Y,
CONF_FIELD_STRENGTH_Z,
CONF_ID,
ICON_MAGNET,
STATE_CLASS_MEASUREMENT,
UNIT_MICROTESLA,
)
DEPENDENCIES = ["i2c"]
mmc5983_ns = cg.esphome_ns.namespace("mmc5983")
MMC5983Component = mmc5983_ns.class_(
"MMC5983Component", cg.PollingComponent, i2c.I2CDevice
)
field_strength_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_MICROTESLA,
icon=ICON_MAGNET,
accuracy_decimals=4,
state_class=STATE_CLASS_MEASUREMENT,
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MMC5983Component),
cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x30))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if x_config := config.get(CONF_FIELD_STRENGTH_X):
sens = await sensor.new_sensor(x_config)
cg.add(var.set_x_sensor(sens))
if y_config := config.get(CONF_FIELD_STRENGTH_Y):
sens = await sensor.new_sensor(y_config)
cg.add(var.set_y_sensor(sens))
if z_config := config.get(CONF_FIELD_STRENGTH_Z):
sens = await sensor.new_sensor(z_config)
cg.add(var.set_z_sensor(sens))

View file

@ -43,6 +43,9 @@ from esphome.const import (
CONF_USE_ABBREVIATIONS, CONF_USE_ABBREVIATIONS,
CONF_USERNAME, CONF_USERNAME,
CONF_WILL_MESSAGE, CONF_WILL_MESSAGE,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
) )
from esphome.core import coroutine_with_priority, CORE from esphome.core import coroutine_with_priority, CORE
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
@ -250,7 +253,7 @@ CONFIG_SCHEMA = cv.All(
} }
), ),
validate_config, validate_config,
cv.only_on(["esp32", "esp8266", "bk72xx"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
) )

View file

@ -19,9 +19,7 @@ class MQTTBackendESP8266 final : public MQTTBackend {
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
mqtt_client_.setWill(topic, qos, retain, payload); mqtt_client_.setWill(topic, qos, retain, payload);
} }
void set_server(network::IPAddress ip, uint16_t port) final { void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(IPAddress(ip), port); }
mqtt_client_.setServer(IPAddress(static_cast<uint32_t>(ip)), port);
}
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
void set_secure(bool secure) { mqtt_client.setSecure(secure); } void set_secure(bool secure) { mqtt_client.setSecure(secure); }

View file

@ -171,11 +171,7 @@ void MQTTClientComponent::start_dnslookup_() {
case ERR_OK: { case ERR_OK: {
// Got IP immediately // Got IP immediately
this->dns_resolved_ = true; this->dns_resolved_ = true;
#if LWIP_IPV6 this->ip_ = network::IPAddress(&addr);
this->ip_ = addr.u_addr.ip4.addr;
#else
this->ip_ = addr.addr;
#endif
this->start_connect_(); this->start_connect_();
return; return;
} }
@ -226,11 +222,7 @@ void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t *
if (ipaddr == nullptr) { if (ipaddr == nullptr) {
a_this->dns_resolve_error_ = true; a_this->dns_resolve_error_ = true;
} else { } else {
#if LWIP_IPV6 a_this->ip_ = network::IPAddress(ipaddr);
a_this->ip_ = ipaddr->u_addr.ip4.addr;
#else
a_this->ip_ = ipaddr->addr;
#endif
a_this->dns_resolved_ = true; a_this->dns_resolved_ = true;
} }
} }

View file

@ -3,42 +3,104 @@
#include <string> #include <string>
#include <cstdio> #include <cstdio>
#include <array> #include <array>
#include <lwip/ip_addr.h>
#if USE_ARDUINO
#include <Arduino.h>
#include <IPAddress.h>
#endif /* USE_ADRDUINO */
#if USE_ESP32_FRAMEWORK_ARDUINO
#define arduino_ns Arduino_h
#elif USE_LIBRETINY
#define arduino_ns arduino
#elif USE_ARDUINO
#define arduino_ns
#endif
#ifdef USE_ESP32
#include <cstring>
#include <esp_netif.h>
#endif
namespace esphome { namespace esphome {
namespace network { namespace network {
struct IPAddress { struct IPAddress {
public: public:
IPAddress() : addr_({0, 0, 0, 0}) {} IPAddress() { ip_addr_set_zero(&ip_addr_); }
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) : addr_({first, second, third, fourth}) {} IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
IPAddress(uint32_t raw) { IP_ADDR4(&ip_addr_, first, second, third, fourth);
addr_[0] = (uint8_t) (raw >> 0);
addr_[1] = (uint8_t) (raw >> 8);
addr_[2] = (uint8_t) (raw >> 16);
addr_[3] = (uint8_t) (raw >> 24);
} }
operator uint32_t() const { IPAddress(const ip_addr_t *other_ip) { ip_addr_copy(ip_addr_, *other_ip); }
uint32_t res = 0; IPAddress(const std::string &in_address) { ipaddr_aton(in_address.c_str(), &ip_addr_); }
res |= ((uint32_t) addr_[0]) << 0; IPAddress(ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip4_addr_t)); }
res |= ((uint32_t) addr_[1]) << 8; #if USE_ARDUINO
res |= ((uint32_t) addr_[2]) << 16; IPAddress(const arduino_ns::IPAddress &other_ip) { ip_addr_set_ip4_u32(&ip_addr_, other_ip); }
res |= ((uint32_t) addr_[3]) << 24; #endif
return res; #if LWIP_IPV6
IPAddress(ip6_addr_t *other_ip) {
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip6_addr_t));
ip_addr_.type = IPADDR_TYPE_V6;
} }
std::string str() const { #endif /* LWIP_IPV6 */
char buffer[24];
snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", addr_[0], addr_[1], addr_[2], addr_[3]); #ifdef USE_ESP32
return buffer; #if LWIP_IPV6
IPAddress(esp_ip6_addr_t *other_ip) {
memcpy((void *) &ip_addr_.u_addr.ip6, (void *) other_ip, sizeof(esp_ip6_addr_t));
ip_addr_.type = IPADDR_TYPE_V6;
} }
bool operator==(const IPAddress &other) const { #endif /* LWIP_IPV6 */
return addr_[0] == other.addr_[0] && addr_[1] == other.addr_[1] && addr_[2] == other.addr_[2] && IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); }
addr_[3] == other.addr_[3]; operator esp_ip_addr_t() const {
esp_ip_addr_t tmp;
#if LWIP_IPV6
memcpy((void *) &tmp, (void *) &ip_addr_, sizeof(ip_addr_));
#else
memcpy((void *) &tmp.u_addr.ip4, (void *) &ip_addr_, sizeof(ip_addr_));
#endif /* LWIP_IPV6 */
return tmp;
}
operator esp_ip4_addr_t() const {
esp_ip4_addr_t tmp;
#if LWIP_IPV6
memcpy((void *) &tmp, (void *) &ip_addr_.u_addr.ip4, sizeof(esp_ip4_addr_t));
#else
memcpy((void *) &tmp, (void *) &ip_addr_, sizeof(ip_addr_));
#endif /* LWIP_IPV6 */
return tmp;
}
#endif /* USE_ESP32 */
operator ip_addr_t() const { return ip_addr_; }
#if LWIP_IPV6
operator ip4_addr_t() const { return *ip_2_ip4(&ip_addr_); }
#endif /* LWIP_IPV6 */
#if USE_ARDUINO
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
#endif
bool is_set() { return !ip_addr_isany(&ip_addr_); }
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
std::string str() const { return ipaddr_ntoa(&ip_addr_); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !(&ip_addr_ == &other.ip_addr_); }
IPAddress &operator+=(uint8_t increase) {
if (IP_IS_V4(&ip_addr_)) {
#if LWIP_IPV6
(((u8_t *) (&ip_addr_.u_addr.ip4))[3]) += increase;
#else
(((u8_t *) (&ip_addr_.addr))[3]) += increase;
#endif /* LWIP_IPV6 */
}
return *this;
} }
uint8_t operator[](int index) const { return addr_[index]; }
uint8_t &operator[](int index) { return addr_[index]; }
protected: protected:
std::array<uint8_t, 4> addr_; ip_addr_t ip_addr_;
}; };
} // namespace network } // namespace network

View file

@ -61,6 +61,11 @@ bool Nextion::check_connect_() {
std::string response; std::string response;
this->recv_ret_string_(response, 0, false); this->recv_ret_string_(response, 0, false);
if (!response.empty() && response[0] == 0x1A) {
// Swallow invalid variable name responses that may be caused by the above commands
ESP_LOGD(TAG, "0x1A error ignored during setup");
return false;
}
if (response.empty() || response.find("comok") == std::string::npos) { if (response.empty() || response.find("comok") == std::string::npos) {
#ifdef NEXTION_PROTOCOL_LOG #ifdef NEXTION_PROTOCOL_LOG
ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); ESP_LOGN(TAG, "Bad connect request %s", response.c_str());

View file

@ -76,9 +76,15 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
} }
} }
float published_state = state;
if (this->wave_chan_id_ == UINT8_MAX) { if (this->wave_chan_id_ == UINT8_MAX) {
if (publish) { if (publish) {
this->publish_state(state); if (this->precision_ > 0) {
double to_multiply = pow(10, -this->precision_);
published_state = (float) (state * to_multiply);
}
this->publish_state(published_state);
} else { } else {
this->raw_state = state; this->raw_state = state;
this->state = state; this->state = state;
@ -87,7 +93,7 @@ void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) {
} }
this->update_component_settings(); this->update_component_settings();
ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), published_state);
} }
void NextionSensor::wave_update_() { void NextionSensor::wave_update_() {

View file

@ -17,12 +17,12 @@ struct PM25AQIData {
uint16_t pm10_env, ///< Environmental PM1.0 uint16_t pm10_env, ///< Environmental PM1.0
pm25_env, ///< Environmental PM2.5 pm25_env, ///< Environmental PM2.5
pm100_env; ///< Environmental PM10.0 pm100_env; ///< Environmental PM10.0
uint16_t particles_03um, ///< 0.3um Particle Count uint16_t particles_03um, ///> 0.3um Particle Count
particles_05um, ///< 0.5um Particle Count particles_05um, ///> 0.5um Particle Count
particles_10um, ///< 1.0um Particle Count particles_10um, ///> 1.0um Particle Count
particles_25um, ///< 2.5um Particle Count particles_25um, ///> 2.5um Particle Count
particles_50um, ///< 5.0um Particle Count particles_50um, ///> 5.0um Particle Count
particles_100um; ///< 10.0um Particle Count particles_100um; ///> 10.0um Particle Count
uint16_t unused; ///< Unused uint16_t unused; ///< Unused
uint16_t checksum; ///< Packet checksum uint16_t checksum; ///< Packet checksum
}; };

View file

@ -3,6 +3,9 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import ( from esphome.const import (
CONF_ADDRESS, CONF_ADDRESS,
CONF_FIELD_STRENGTH_X,
CONF_FIELD_STRENGTH_Y,
CONF_FIELD_STRENGTH_Z,
CONF_ID, CONF_ID,
CONF_OVERSAMPLING, CONF_OVERSAMPLING,
CONF_RANGE, CONF_RANGE,
@ -18,9 +21,6 @@ DEPENDENCIES = ["i2c"]
qmc5883l_ns = cg.esphome_ns.namespace("qmc5883l") qmc5883l_ns = cg.esphome_ns.namespace("qmc5883l")
CONF_FIELD_STRENGTH_X = "field_strength_x"
CONF_FIELD_STRENGTH_Y = "field_strength_y"
CONF_FIELD_STRENGTH_Z = "field_strength_z"
CONF_HEADING = "heading" CONF_HEADING = "heading"
QMC5883LComponent = qmc5883l_ns.class_( QMC5883LComponent = qmc5883l_ns.class_(

View file

@ -1558,3 +1558,37 @@ async def aeha_action(var, config, args):
config[CONF_DATA], args, cg.std_vector.template(cg.uint8) config[CONF_DATA], args, cg.std_vector.template(cg.uint8)
) )
cg.add(var.set_data(template_)) cg.add(var.set_data(template_))
# Haier
HaierData, HaierBinarySensor, HaierTrigger, HaierAction, HaierDumper = declare_protocol(
"Haier"
)
HaierAction = ns.class_("HaierAction", RemoteTransmitterActionBase)
HAIER_SCHEMA = cv.Schema(
{
cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=13, max=13)),
}
)
@register_binary_sensor("haier", HaierBinarySensor, HAIER_SCHEMA)
def haier_binary_sensor(var, config):
cg.add(var.set_code(config[CONF_CODE]))
@register_trigger("haier", HaierTrigger, HaierData)
def haier_trigger(var, config):
pass
@register_dumper("haier", HaierDumper)
def haier_dumper(var, config):
pass
@register_action("haier", HaierAction, HAIER_SCHEMA)
async def haier_action(var, config, args):
vec_ = cg.std_vector.template(cg.uint8)
template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_)
cg.add(var.set_code(template_))

View file

@ -0,0 +1,84 @@
#include "haier_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *const TAG = "remote.haier";
constexpr uint32_t HEADER_LOW_US = 3100;
constexpr uint32_t HEADER_HIGH_US = 4400;
constexpr uint32_t BIT_MARK_US = 540;
constexpr uint32_t BIT_ONE_SPACE_US = 1650;
constexpr uint32_t BIT_ZERO_SPACE_US = 580;
constexpr unsigned int HAIER_IR_PACKET_BIT_SIZE = 112;
void HaierProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t item) {
for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) {
if (item & mask) {
dst->space(BIT_ONE_SPACE_US);
} else {
dst->space(BIT_ZERO_SPACE_US);
}
dst->mark(BIT_MARK_US);
}
}
void HaierProtocol::encode(RemoteTransmitData *dst, const HaierData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(5 + ((data.data.size() + 1) * 2));
dst->mark(HEADER_LOW_US);
dst->space(HEADER_LOW_US);
dst->mark(HEADER_LOW_US);
dst->space(HEADER_HIGH_US);
dst->mark(BIT_MARK_US);
uint8_t checksum = 0;
for (uint8_t item : data.data) {
this->encode_byte_(dst, item);
checksum += item;
}
this->encode_byte_(dst, checksum);
}
optional<HaierData> HaierProtocol::decode(RemoteReceiveData src) {
if (!src.expect_item(HEADER_LOW_US, HEADER_LOW_US) || !src.expect_item(HEADER_LOW_US, HEADER_HIGH_US)) {
return {};
}
if (!src.expect_mark(BIT_MARK_US)) {
return {};
}
size_t size = src.size() - src.get_index() - 1;
if (size < HAIER_IR_PACKET_BIT_SIZE * 2)
return {};
size = HAIER_IR_PACKET_BIT_SIZE * 2;
uint8_t checksum = 0;
HaierData out;
while (size > 0) {
uint8_t data = 0;
for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
if (src.expect_space(BIT_ONE_SPACE_US)) {
data |= mask;
} else if (!src.expect_space(BIT_ZERO_SPACE_US)) {
return {};
}
if (!src.expect_mark(BIT_MARK_US)) {
return {};
}
size -= 2;
}
if (size > 0) {
checksum += data;
out.data.push_back(data);
} else if (checksum != data) {
return {};
}
}
return out;
}
void HaierProtocol::dump(const HaierData &data) {
ESP_LOGI(TAG, "Received Haier: %s", format_hex_pretty(data.data).c_str());
}
} // namespace remote_base
} // namespace esphome

View file

@ -0,0 +1,40 @@
#pragma once
#include "remote_base.h"
#include <vector>
namespace esphome {
namespace remote_base {
struct HaierData {
std::vector<uint8_t> data;
bool operator==(const HaierData &rhs) const { return data == rhs.data; }
};
class HaierProtocol : public RemoteProtocol<HaierData> {
public:
void encode(RemoteTransmitData *dst, const HaierData &data) override;
optional<HaierData> decode(RemoteReceiveData src) override;
void dump(const HaierData &data) override;
protected:
void encode_byte_(RemoteTransmitData *dst, uint8_t item);
};
DECLARE_REMOTE_PROTOCOL(Haier)
template<typename... Ts> class HaierAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(std::vector<uint8_t>, data)
void set_code(const std::vector<uint8_t> &code) { data_ = code; }
void encode(RemoteTransmitData *dst, Ts... x) override {
HaierData data{};
data.data = this->data_.value(x...);
HaierProtocol().encode(dst, data);
}
};
} // namespace remote_base
} // namespace esphome

View file

@ -14,6 +14,7 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_RP2040,
) )
from esphome.core import CORE, coroutine_with_priority, EsphomeError from esphome.core import CORE, coroutine_with_priority, EsphomeError
from esphome.helpers import mkdir_p, write_file, copy_file_if_changed from esphome.helpers import mkdir_p, write_file, copy_file_if_changed
@ -30,7 +31,7 @@ AUTO_LOAD = []
def set_core_data(config): def set_core_data(config):
CORE.data[KEY_RP2040] = {} CORE.data[KEY_RP2040] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040" CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_RP2040
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION] config[CONF_FRAMEWORK][CONF_VERSION]

View file

@ -85,6 +85,7 @@ from esphome.const import (
DEVICE_CLASS_WATER, DEVICE_CLASS_WATER,
DEVICE_CLASS_WEIGHT, DEVICE_CLASS_WEIGHT,
DEVICE_CLASS_WIND_SPEED, DEVICE_CLASS_WIND_SPEED,
ENTITY_CATEGORY_CONFIG,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@ -188,6 +189,15 @@ def validate_datapoint(value):
return validate_datapoint({CONF_FROM: cv.float_(a), CONF_TO: cv.float_(b)}) return validate_datapoint({CONF_FROM: cv.float_(a), CONF_TO: cv.float_(b)})
_SENSOR_ENTITY_CATEGORIES = {
k: v for k, v in cv.ENTITY_CATEGORIES.items() if k != ENTITY_CATEGORY_CONFIG
}
def sensor_entity_category(value):
return cv.enum(_SENSOR_ENTITY_CATEGORIES, lower=True)(value)
# Base # Base
Sensor = sensor_ns.class_("Sensor", cg.EntityBase) Sensor = sensor_ns.class_("Sensor", cg.EntityBase)
SensorPtr = Sensor.operator("ptr") SensorPtr = Sensor.operator("ptr")
@ -246,6 +256,7 @@ SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals, cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_STATE_CLASS): validate_state_class, cv.Optional(CONF_STATE_CLASS): validate_state_class,
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
cv.Optional("last_reset_type"): cv.invalid( cv.Optional("last_reset_type"): cv.invalid(
"last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values." "last_reset_type has been removed since 2021.9.0. state_class: total_increasing should be used for total values."
), ),
@ -301,7 +312,7 @@ def sensor_schema(
(CONF_ACCURACY_DECIMALS, accuracy_decimals, validate_accuracy_decimals), (CONF_ACCURACY_DECIMALS, accuracy_decimals, validate_accuracy_decimals),
(CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_DEVICE_CLASS, device_class, validate_device_class),
(CONF_STATE_CLASS, state_class, validate_state_class), (CONF_STATE_CLASS, state_class, validate_state_class),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), (CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category),
]: ]:
if default is not _UNDEF: if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator schema[cv.Optional(key, default=default)] = validator

View file

@ -38,7 +38,7 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_HEATER_ENABLED, default=True): cv.boolean, cv.Optional(CONF_HEATER_ENABLED, default=False): cv.boolean,
}, },
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))

View file

@ -25,6 +25,10 @@ from esphome.const import (
KEY_CORE, KEY_CORE,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
KEY_VARIANT, KEY_VARIANT,
CONF_DATA_RATE,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
) )
from esphome.core import coroutine_with_priority, CORE from esphome.core import coroutine_with_priority, CORE
@ -33,6 +37,7 @@ spi_ns = cg.esphome_ns.namespace("spi")
SPIComponent = spi_ns.class_("SPIComponent", cg.Component) SPIComponent = spi_ns.class_("SPIComponent", cg.Component)
SPIDevice = spi_ns.class_("SPIDevice") SPIDevice = spi_ns.class_("SPIDevice")
SPIDataRate = spi_ns.enum("SPIDataRate") SPIDataRate = spi_ns.enum("SPIDataRate")
SPIMode = spi_ns.enum("SPIMode")
SPI_DATA_RATE_OPTIONS = { SPI_DATA_RATE_OPTIONS = {
80e6: SPIDataRate.DATA_RATE_80MHZ, 80e6: SPIDataRate.DATA_RATE_80MHZ,
@ -50,6 +55,18 @@ SPI_DATA_RATE_OPTIONS = {
} }
SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS))
SPI_MODE_OPTIONS = {
"MODE0": SPIMode.MODE0,
"MODE1": SPIMode.MODE1,
"MODE2": SPIMode.MODE2,
"MODE3": SPIMode.MODE3,
0: SPIMode.MODE0,
1: SPIMode.MODE1,
2: SPIMode.MODE2,
3: SPIMode.MODE3,
}
CONF_SPI_MODE = "spi_mode"
CONF_FORCE_SW = "force_sw" CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface" CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index" CONF_INTERFACE_INDEX = "interface_index"
@ -88,9 +105,9 @@ def get_target_variant():
# The returned value is a list of lists of names # The returned value is a list of lists of names
def get_hw_interface_list(): def get_hw_interface_list():
target_platform = get_target_platform() target_platform = get_target_platform()
if target_platform == "esp8266": if target_platform == PLATFORM_ESP8266:
return [["spi", "hspi"]] return [["spi", "hspi"]]
if target_platform == "esp32": if target_platform == PLATFORM_ESP32:
if get_target_variant() in [ if get_target_variant() in [
VARIANT_ESP32C2, VARIANT_ESP32C2,
VARIANT_ESP32C3, VARIANT_ESP32C3,
@ -99,7 +116,7 @@ def get_hw_interface_list():
]: ]:
return [["spi", "spi2"]] return [["spi", "spi2"]]
return [["spi", "spi2"], ["spi3"]] return [["spi", "spi2"], ["spi3"]]
if target_platform == "rp2040": if target_platform == PLATFORM_RP2040:
return [["spi"], ["spi1"]] return [["spi"], ["spi1"]]
return [] return []
@ -136,17 +153,17 @@ def validate_hw_pins(spi, index=-1):
sdi_pin_no = sdi_pin[CONF_NUMBER] sdi_pin_no = sdi_pin[CONF_NUMBER]
target_platform = get_target_platform() target_platform = get_target_platform()
if target_platform == "esp8266": if target_platform == PLATFORM_ESP8266:
if clk_pin_no == 6: if clk_pin_no == 6:
return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7) return sdo_pin_no in (-1, 8) and sdi_pin_no in (-1, 7)
if clk_pin_no == 14: if clk_pin_no == 14:
return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12) return sdo_pin_no in (-1, 13) and sdi_pin_no in (-1, 12)
return False return False
if target_platform == "esp32": if target_platform == PLATFORM_ESP32:
return clk_pin_no >= 0 return clk_pin_no >= 0
if target_platform == "rp2040": if target_platform == PLATFORM_RP2040:
pin_set = ( pin_set = (
list(filter(lambda s: clk_pin_no in s[CONF_CLK_PIN], RP_SPI_PINSETS))[0] list(filter(lambda s: clk_pin_no in s[CONF_CLK_PIN], RP_SPI_PINSETS))[0]
if index == -1 if index == -1
@ -222,7 +239,7 @@ def get_spi_interface(index):
return ["SPI2_HOST", "SPI3_HOST"][index] return ["SPI2_HOST", "SPI3_HOST"][index]
# Arduino code follows # Arduino code follows
platform = get_target_platform() platform = get_target_platform()
if platform == "rp2040": if platform == PLATFORM_RP2040:
return ["&SPI", "&SPI1"][index] return ["&SPI", "&SPI1"][index]
if index == 0: if index == 0:
return "&SPI" return "&SPI"
@ -247,7 +264,7 @@ SPI_SCHEMA = cv.All(
} }
), ),
cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN), cv.has_at_least_one_key(CONF_MISO_PIN, CONF_MOSI_PIN),
cv.only_on(["esp32", "esp8266", "rp2040"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]),
) )
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
@ -258,6 +275,7 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(1.0) @coroutine_with_priority(1.0)
async def to_code(configs): async def to_code(configs):
cg.add_define("USE_SPI")
cg.add_global(spi_ns.using) cg.add_global(spi_ns.using)
for spi in configs: for spi in configs:
var = cg.new_Pvariable(spi[CONF_ID]) var = cg.new_Pvariable(spi[CONF_ID])
@ -286,13 +304,20 @@ async def to_code(configs):
cg.add_library("SPI", None) cg.add_library("SPI", None)
def spi_device_schema(cs_pin_required=True): def spi_device_schema(
cs_pin_required=True, default_data_rate=cv.UNDEFINED, default_mode=cv.UNDEFINED
):
"""Create a schema for an SPI device. """Create a schema for an SPI device.
:param cs_pin_required: If true, make the CS_PIN required in the config. :param cs_pin_required: If true, make the CS_PIN required in the config.
:param default_data_rate: Optional data_rate to use as default
:return: The SPI device schema, `extend` this in your config schema. :return: The SPI device schema, `extend` this in your config schema.
""" """
schema = { schema = {
cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent), cv.GenerateID(CONF_SPI_ID): cv.use_id(SPIComponent),
cv.Optional(CONF_DATA_RATE, default=default_data_rate): SPI_DATA_RATE_SCHEMA,
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True
),
} }
if cs_pin_required: if cs_pin_required:
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
@ -307,6 +332,10 @@ async def register_spi_device(var, config):
if CONF_CS_PIN in config: if CONF_CS_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN]) pin = await cg.gpio_pin_expression(config[CONF_CS_PIN])
cg.add(var.set_cs_pin(pin)) cg.add(var.set_cs_pin(pin))
if CONF_DATA_RATE in config:
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
if CONF_SPI_MODE in config:
cg.add(var.set_mode(config[CONF_SPI_MODE]))
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import spi from esphome.components import spi
from esphome.const import CONF_ID, CONF_DATA_RATE, CONF_MODE from esphome.const import CONF_ID, CONF_MODE
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
CODEOWNERS = ["@clydebarrow"] CODEOWNERS = ["@clydebarrow"]
@ -33,17 +33,15 @@ CONF_BIT_ORDER = "bit_order"
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(CONF_ID): cv.declare_id(spi_device), cv.GenerateID(CONF_ID): cv.declare_id(spi_device),
cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True), cv.Optional(CONF_BIT_ORDER, default="msb_first"): cv.enum(ORDERS, lower=True),
cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True), cv.Optional(CONF_MODE, default="0"): cv.enum(MODES, upper=True),
} }
).extend(spi.spi_device_schema(False)) ).extend(spi.spi_device_schema(False, "1MHz"))
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)
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
cg.add(var.set_mode(config[CONF_MODE])) cg.add(var.set_mode(config[CONF_MODE]))
cg.add(var.set_bit_order(config[CONF_BIT_ORDER])) cg.add(var.set_bit_order(config[CONF_BIT_ORDER]))
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import light from esphome.components import light
from esphome.components import spi from esphome.components import spi
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_DATA_RATE from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS
spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip") spi_led_strip_ns = cg.esphome_ns.namespace("spi_led_strip")
SpiLedStrip = spi_led_strip_ns.class_( SpiLedStrip = spi_led_strip_ns.class_(
@ -13,14 +13,12 @@ CONFIG_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{ {
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpiLedStrip),
cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int, cv.Optional(CONF_NUM_LEDS, default=1): cv.positive_not_null_int,
cv.Optional(CONF_DATA_RATE, default="1MHz"): spi.SPI_DATA_RATE_SCHEMA,
} }
).extend(spi.spi_device_schema(False)) ).extend(spi.spi_device_schema(False, "1MHz"))
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
cg.add(var.set_data_rate(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]]))
cg.add(var.set_num_leds(config[CONF_NUM_LEDS])) cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
await light.register_light(var, config) await light.register_light(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)

View file

@ -571,18 +571,12 @@ async def sprinkler_simple_action_to_code(config, action_id, template_arg, args)
async def to_code(config): async def to_code(config):
for sprinkler_controller in config: for sprinkler_controller in config:
var = cg.new_Pvariable(sprinkler_controller[CONF_ID])
if CONF_NAME in sprinkler_controller:
cg.add(var.set_name(sprinkler_controller[CONF_NAME]))
else:
if len(sprinkler_controller[CONF_VALVES]) > 1: if len(sprinkler_controller[CONF_VALVES]) > 1:
name = sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME] name = sprinkler_controller[CONF_MAIN_SWITCH][CONF_NAME]
else: else:
name = sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][ name = sprinkler_controller[CONF_VALVES][0][CONF_VALVE_SWITCH][CONF_NAME]
CONF_NAME name = sprinkler_controller.get(CONF_NAME, name)
] var = cg.new_Pvariable(sprinkler_controller[CONF_ID], name)
cg.add(var.set_name(name))
await cg.register_component(var, sprinkler_controller) await cg.register_component(var, sprinkler_controller)

View file

@ -386,12 +386,17 @@ SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this
SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; } SprinklerValveRunRequestOrigin SprinklerValveRunRequest::request_is_from() { return this->origin_; }
void Sprinkler::setup() { Sprinkler::Sprinkler() {}
Sprinkler::Sprinkler(const std::string &name) {
// The `name` is needed to set timers up, hence non-default constructor
// replaces `set_name()` method previously existed
this->name_ = name;
this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)}); this->timer_.push_back({this->name_ + "sm", false, 0, 0, std::bind(&Sprinkler::sm_timer_callback_, this)});
this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)}); this->timer_.push_back({this->name_ + "vs", false, 0, 0, std::bind(&Sprinkler::valve_selection_callback_, this)});
this->all_valves_off_(true);
} }
void Sprinkler::setup() { this->all_valves_off_(true); }
void Sprinkler::loop() { void Sprinkler::loop() {
for (auto &p : this->pump_) { for (auto &p : this->pump_) {
p.loop(); p.loop();

View file

@ -204,12 +204,12 @@ class SprinklerValveRunRequest {
class Sprinkler : public Component { class Sprinkler : public Component {
public: public:
Sprinkler();
Sprinkler(const std::string &name);
void setup() override; void setup() override;
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
void set_name(const std::string &name) { this->name_ = name; }
/// add a valve to the controller /// add a valve to the controller
void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw = nullptr); void add_valve(SprinklerControllerSwitch *valve_sw, SprinklerControllerSwitch *enable_sw = nullptr);

View file

@ -20,7 +20,7 @@ from esphome.const import (
DEVICE_CLASS_PM25, DEVICE_CLASS_PM25,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_MICROGRAMS_PER_CUBIC_METER,
UNIT_COUNTS_PER_CUBIC_METER, UNIT_COUNTS_PER_CUBIC_CENTIMETER,
UNIT_MICROMETER, UNIT_MICROMETER,
ICON_CHEMICAL_WEAPON, ICON_CHEMICAL_WEAPON,
ICON_COUNTER, ICON_COUNTER,
@ -73,31 +73,31 @@ CONFIG_SCHEMA = (
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_PMC_0_5): sensor.sensor_schema( cv.Optional(CONF_PMC_0_5): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
icon=ICON_COUNTER, icon=ICON_COUNTER,
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_PMC_1_0): sensor.sensor_schema( cv.Optional(CONF_PMC_1_0): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
icon=ICON_COUNTER, icon=ICON_COUNTER,
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_PMC_2_5): sensor.sensor_schema( cv.Optional(CONF_PMC_2_5): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
icon=ICON_COUNTER, icon=ICON_COUNTER,
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_PMC_4_0): sensor.sensor_schema( cv.Optional(CONF_PMC_4_0): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
icon=ICON_COUNTER, icon=ICON_COUNTER,
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_PMC_10_0): sensor.sensor_schema( cv.Optional(CONF_PMC_10_0): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS_PER_CUBIC_METER, unit_of_measurement=UNIT_COUNTS_PER_CUBIC_CENTIMETER,
icon=ICON_COUNTER, icon=ICON_COUNTER,
accuracy_decimals=2, accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,

View file

@ -112,6 +112,9 @@ void SSD1351::set_brightness(float brightness) {
} else { } else {
this->brightness_ = brightness; this->brightness_ = brightness;
} }
if (!this->is_ready()) {
return; // Component is not yet setup skip the command
}
// now write the new brightness level to the display // now write the new brightness level to the display
this->command(SSD1351_CONTRASTMASTER); this->command(SSD1351_CONTRASTMASTER);
this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_))); this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_)));

View file

@ -138,7 +138,10 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_MODEL): cv.one_of(*MODELS.keys(), upper=True, space="_"), cv.Required(CONF_MODEL): cv.one_of(*MODELS.keys(), upper=True, space="_"),
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_BACKLIGHT_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_BACKLIGHT_PIN): cv.Any(
cv.boolean,
pins.gpio_output_pin_schema,
),
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply), cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean, cv.Optional(CONF_EIGHTBITCOLOR, default=False): cv.boolean,
cv.Optional(CONF_HEIGHT): cv.int_, cv.Optional(CONF_HEIGHT): cv.int_,
@ -174,7 +177,7 @@ async def to_code(config):
reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(reset)) cg.add(var.set_reset_pin(reset))
if CONF_BACKLIGHT_PIN in config: if CONF_BACKLIGHT_PIN in config and config[CONF_BACKLIGHT_PIN]:
bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN]) bl = await cg.gpio_pin_expression(config[CONF_BACKLIGHT_PIN])
cg.add(var.set_backlight_pin(bl)) cg.add(var.set_backlight_pin(bl))

View file

@ -133,6 +133,7 @@ void ST7789V::dump_config() {
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" B/L Pin: ", this->backlight_pin_); LOG_PIN(" B/L Pin: ", this->backlight_pin_);
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
ESP_LOGCONFIG(TAG, " Power Supply Configured: yes"); ESP_LOGCONFIG(TAG, " Power Supply Configured: yes");
#endif #endif

View file

@ -14,8 +14,8 @@ SX1509BinarySensor = sx1509_ns.class_("SX1509BinarySensor", binary_sensor.Binary
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(SX1509BinarySensor).extend( CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(SX1509BinarySensor).extend(
{ {
cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component),
cv.Required(CONF_ROW): cv.int_range(min=0, max=4), cv.Required(CONF_ROW): cv.int_range(min=0, max=7),
cv.Required(CONF_COL): cv.int_range(min=0, max=4), cv.Required(CONF_COL): cv.int_range(min=0, max=7),
} }
) )

View file

@ -37,7 +37,7 @@ void UltrasonicSensorComponent::update() {
this->publish_state(NAN); this->publish_state(NAN);
} else { } else {
float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start); float result = UltrasonicSensorComponent::us_to_m(pulse_end - pulse_start);
ESP_LOGD(TAG, "'%s' - Got distance: %.2f m", this->name_.c_str(), result); ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result);
this->publish_state(result); this->publish_state(result);
} }
} }

View file

@ -19,11 +19,18 @@ CODEOWNERS = ["@jesserockz"]
CONF_SILENCE_DETECTION = "silence_detection" CONF_SILENCE_DETECTION = "silence_detection"
CONF_ON_LISTENING = "on_listening" CONF_ON_LISTENING = "on_listening"
CONF_ON_START = "on_start" CONF_ON_START = "on_start"
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
CONF_ON_STT_END = "on_stt_end" CONF_ON_STT_END = "on_stt_end"
CONF_ON_TTS_START = "on_tts_start" CONF_ON_TTS_START = "on_tts_start"
CONF_ON_TTS_END = "on_tts_end" CONF_ON_TTS_END = "on_tts_end"
CONF_ON_END = "on_end" CONF_ON_END = "on_end"
CONF_ON_ERROR = "on_error" CONF_ON_ERROR = "on_error"
CONF_USE_WAKE_WORD = "use_wake_word"
CONF_VAD_THRESHOLD = "vad_threshold"
CONF_NOISE_SUPPRESSION_LEVEL = "noise_suppression_level"
CONF_AUTO_GAIN = "auto_gain"
CONF_VOLUME_MULTIPLIER = "volume_multiplier"
voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant") voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant")
@ -42,23 +49,40 @@ IsRunningCondition = voice_assistant_ns.class_(
"IsRunningCondition", automation.Condition, cg.Parented.template(VoiceAssistant) "IsRunningCondition", automation.Condition, cg.Parented.template(VoiceAssistant)
) )
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA = cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(VoiceAssistant), cv.GenerateID(): cv.declare_id(VoiceAssistant),
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
cv.Exclusive(CONF_SPEAKER, "output"): cv.use_id(speaker.Speaker), cv.Exclusive(CONF_SPEAKER, "output"): cv.use_id(speaker.Speaker),
cv.Exclusive(CONF_MEDIA_PLAYER, "output"): cv.use_id(media_player.MediaPlayer), cv.Exclusive(CONF_MEDIA_PLAYER, "output"): cv.use_id(
cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean, media_player.MediaPlayer
),
cv.Optional(CONF_USE_WAKE_WORD, default=False): cv.boolean,
cv.Optional(CONF_VAD_THRESHOLD): cv.All(
cv.requires_component("esp_adf"), cv.only_with_esp_idf, cv.uint8_t
),
cv.Optional(CONF_NOISE_SUPPRESSION_LEVEL, default=0): cv.int_range(0, 4),
cv.Optional(CONF_AUTO_GAIN, default="0dBFS"): cv.All(
cv.float_with_unit("decibel full scale", "(dBFS|dbfs|DBFS)"),
cv.int_range(0, 31),
),
cv.Optional(CONF_VOLUME_MULTIPLIER, default=1.0): cv.float_range(
min=0.0, min_included=False
),
cv.Optional(CONF_ON_LISTENING): automation.validate_automation(single=True), cv.Optional(CONF_ON_LISTENING): automation.validate_automation(single=True),
cv.Optional(CONF_ON_START): automation.validate_automation(single=True), cv.Optional(CONF_ON_START): automation.validate_automation(single=True),
cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True), cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True),
cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True), cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True),
cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True), cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True),
cv.Optional(CONF_ON_END): automation.validate_automation(single=True), cv.Optional(CONF_ON_END): automation.validate_automation(single=True),
cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA),
)
async def to_code(config): async def to_code(config):
@ -76,7 +100,14 @@ async def to_code(config):
mp = await cg.get_variable(config[CONF_MEDIA_PLAYER]) mp = await cg.get_variable(config[CONF_MEDIA_PLAYER])
cg.add(var.set_media_player(mp)) cg.add(var.set_media_player(mp))
cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION])) cg.add(var.set_use_wake_word(config[CONF_USE_WAKE_WORD]))
if (vad_threshold := config.get(CONF_VAD_THRESHOLD)) is not None:
cg.add(var.set_vad_threshold(vad_threshold))
cg.add(var.set_noise_suppression_level(config[CONF_NOISE_SUPPRESSION_LEVEL]))
cg.add(var.set_auto_gain(config[CONF_AUTO_GAIN]))
cg.add(var.set_volume_multiplier(config[CONF_VOLUME_MULTIPLIER]))
if CONF_ON_LISTENING in config: if CONF_ON_LISTENING in config:
await automation.build_automation( await automation.build_automation(
@ -88,6 +119,13 @@ async def to_code(config):
var.get_start_trigger(), [], config[CONF_ON_START] var.get_start_trigger(), [], config[CONF_ON_START]
) )
if CONF_ON_WAKE_WORD_DETECTED in config:
await automation.build_automation(
var.get_wake_word_detected_trigger(),
[],
config[CONF_ON_WAKE_WORD_DETECTED],
)
if CONF_ON_STT_END in config: if CONF_ON_STT_END in config:
await automation.build_automation( await automation.build_automation(
var.get_stt_end_trigger(), [(cg.std_string, "x")], config[CONF_ON_STT_END] var.get_stt_end_trigger(), [(cg.std_string, "x")], config[CONF_ON_STT_END]
@ -128,10 +166,20 @@ VOICE_ASSISTANT_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(VoiceAssis
StartContinuousAction, StartContinuousAction,
VOICE_ASSISTANT_ACTION_SCHEMA, VOICE_ASSISTANT_ACTION_SCHEMA,
) )
@register_action("voice_assistant.start", StartAction, VOICE_ASSISTANT_ACTION_SCHEMA) @register_action(
"voice_assistant.start",
StartAction,
VOICE_ASSISTANT_ACTION_SCHEMA.extend(
{
cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean,
}
),
)
async def voice_assistant_listen_to_code(config, action_id, template_arg, args): async def voice_assistant_listen_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
if CONF_SILENCE_DETECTION in config:
cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION]))
return var return var

View file

@ -11,6 +11,17 @@ namespace voice_assistant {
static const char *const TAG = "voice_assistant"; static const char *const TAG = "voice_assistant";
#ifdef SAMPLE_RATE_HZ
#undef SAMPLE_RATE_HZ
#endif
static const size_t SAMPLE_RATE_HZ = 16000;
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms
static const size_t BUFFER_SIZE = 1000 * SAMPLE_RATE_HZ / 1000; // 1s
static const size_t SEND_BUFFER_SIZE = INPUT_BUFFER_SIZE * sizeof(int16_t);
static const size_t RECEIVE_SIZE = 1024;
static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
void VoiceAssistant::setup() { void VoiceAssistant::setup() {
@ -47,7 +58,6 @@ void VoiceAssistant::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
server.ss_family = AF_INET;
err = socket_->bind((struct sockaddr *) &server, sizeof(server)); err = socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) { if (err != 0) {
@ -55,60 +65,273 @@ void VoiceAssistant::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
ExternalRAMAllocator<uint8_t> speaker_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE);
if (this->speaker_buffer_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate speaker buffer.");
this->mark_failed();
return;
}
} }
#endif #endif
this->mic_->add_data_callback([this](const std::vector<int16_t> &data) { ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
if (!this->running_) { this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE);
if (this->input_buffer_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate input buffer.");
this->mark_failed();
return; return;
} }
this->socket_->sendto(data.data(), data.size() * sizeof(int16_t), 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_)); #ifdef USE_ESP_ADF
}); this->vad_instance_ = vad_create(VAD_MODE_4);
this->ring_buffer_ = rb_create(BUFFER_SIZE, sizeof(int16_t));
if (this->ring_buffer_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate ring buffer.");
this->mark_failed();
return;
}
#endif
ExternalRAMAllocator<uint8_t> send_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE);
if (send_buffer_ == nullptr) {
ESP_LOGW(TAG, "Could not allocate send buffer.");
this->mark_failed();
return;
}
}
int VoiceAssistant::read_microphone_() {
size_t bytes_read = 0;
if (this->mic_->is_running()) { // Read audio into input buffer
bytes_read = this->mic_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
if (bytes_read == 0) {
memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t));
return 0;
}
#ifdef USE_ESP_ADF
// Write audio into ring buffer
int available = rb_bytes_available(this->ring_buffer_);
if (available < bytes_read) {
rb_read(this->ring_buffer_, nullptr, bytes_read - available, 0);
}
rb_write(this->ring_buffer_, (char *) this->input_buffer_, bytes_read, 0);
#endif
} else {
ESP_LOGD(TAG, "microphone not running");
}
return bytes_read;
} }
void VoiceAssistant::loop() { void VoiceAssistant::loop() {
if (this->state_ != State::IDLE && this->state_ != State::STOP_MICROPHONE &&
this->state_ != State::STOPPING_MICROPHONE && !api::global_api_server->is_connected()) {
if (this->mic_->is_running() || this->state_ == State::STARTING_MICROPHONE) {
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
} else {
this->set_state_(State::IDLE, State::IDLE);
}
this->continuous_ = false;
this->signal_stop_();
return;
}
switch (this->state_) {
case State::IDLE: {
if (this->continuous_ && this->desired_state_ == State::IDLE) {
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
} else
#endif
{
this->set_state_(State::START_PIPELINE, State::START_MICROPHONE);
}
} else {
this->high_freq_.stop();
}
break;
}
case State::START_MICROPHONE: {
ESP_LOGD(TAG, "Starting Microphone");
memset(this->send_buffer_, 0, SEND_BUFFER_SIZE);
memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t));
this->mic_->start();
this->high_freq_.start();
this->set_state_(State::STARTING_MICROPHONE);
break;
}
case State::STARTING_MICROPHONE: {
if (this->mic_->is_running()) {
this->set_state_(this->desired_state_);
}
break;
}
#ifdef USE_ESP_ADF
case State::WAIT_FOR_VAD: {
this->read_microphone_();
ESP_LOGD(TAG, "Waiting for speech...");
this->set_state_(State::WAITING_FOR_VAD);
break;
}
case State::WAITING_FOR_VAD: {
size_t bytes_read = this->read_microphone_();
if (bytes_read > 0) {
vad_state_t vad_state =
vad_process(this->vad_instance_, this->input_buffer_, SAMPLE_RATE_HZ, VAD_FRAME_LENGTH_MS);
if (vad_state == VAD_SPEECH) {
if (this->vad_counter_ < this->vad_threshold_) {
this->vad_counter_++;
} else {
ESP_LOGD(TAG, "VAD detected speech");
this->set_state_(State::START_PIPELINE, State::STREAMING_MICROPHONE);
// Reset for next time
this->vad_counter_ = 0;
}
} else {
if (this->vad_counter_ > 0) {
this->vad_counter_--;
}
}
}
break;
}
#endif
case State::START_PIPELINE: {
this->read_microphone_();
ESP_LOGD(TAG, "Requesting start...");
uint32_t flags = 0;
if (this->use_wake_word_)
flags |= api::enums::VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD;
if (this->silence_detection_)
flags |= api::enums::VOICE_ASSISTANT_REQUEST_USE_VAD;
api::VoiceAssistantAudioSettings audio_settings;
audio_settings.noise_suppression_level = this->noise_suppression_level_;
audio_settings.auto_gain = this->auto_gain_;
audio_settings.volume_multiplier = this->volume_multiplier_;
if (!api::global_api_server->start_voice_assistant(this->conversation_id_, flags, audio_settings)) {
ESP_LOGW(TAG, "Could not request start.");
this->error_trigger_->trigger("not-connected", "Could not request start.");
this->continuous_ = false;
this->set_state_(State::IDLE, State::IDLE);
break;
}
this->set_state_(State::STARTING_PIPELINE);
this->set_timeout("reset-conversation_id", 5 * 60 * 1000, [this]() { this->conversation_id_ = ""; });
break;
}
case State::STARTING_PIPELINE: {
this->read_microphone_();
break; // State changed when udp server port received
}
case State::STREAMING_MICROPHONE: {
size_t bytes_read = this->read_microphone_();
#ifdef USE_ESP_ADF
if (rb_bytes_filled(this->ring_buffer_) >= SEND_BUFFER_SIZE) {
rb_read(this->ring_buffer_, (char *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
}
#else
if (bytes_read > 0) {
this->socket_->sendto(this->input_buffer_, bytes_read, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
}
#endif
break;
}
case State::STOP_MICROPHONE: {
if (this->mic_->is_running()) {
this->mic_->stop();
this->set_state_(State::STOPPING_MICROPHONE);
} else {
this->set_state_(this->desired_state_);
}
break;
}
case State::STOPPING_MICROPHONE: {
if (this->mic_->is_stopped()) {
this->set_state_(this->desired_state_);
}
break;
}
case State::AWAITING_RESPONSE: {
break; // State changed by events
}
case State::STREAMING_RESPONSE: {
bool playing = false;
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
if (this->speaker_ != nullptr) { if (this->speaker_ != nullptr) {
uint8_t buf[1024]; if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) {
auto len = this->socket_->read(buf, sizeof(buf)); auto len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE);
if (len == -1) { if (len > 0) {
return; this->speaker_buffer_index_ += len;
this->speaker_buffer_size_ += len;
} }
this->speaker_->play(buf, len); } else {
this->set_timeout("data-incoming", 200, [this]() { ESP_LOGW(TAG, "Receive buffer full.");
if (this->continuous_) {
this->request_start(true);
} }
}); if (this->speaker_buffer_size_ > 0) {
return; size_t written = this->speaker_->play(this->speaker_buffer_, this->speaker_buffer_size_);
if (written > 0) {
memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written);
this->speaker_buffer_size_ -= written;
this->speaker_buffer_index_ -= written;
this->set_timeout("speaker-timeout", 1000, [this]() { this->speaker_->stop(); });
} else {
ESP_LOGW(TAG, "Speaker buffer full.");
}
}
playing = this->speaker_->is_running();
} }
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) { if (this->media_player_ != nullptr) {
if (!this->playing_tts_ || playing = (this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING);
this->media_player_->state == media_player::MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING) {
return;
}
this->set_timeout("playing-media", 1000, [this]() {
this->playing_tts_ = false;
if (this->continuous_) {
this->request_start(true);
}
});
return;
} }
#endif #endif
// Set a 1 second timeout to start the voice assistant again. if (playing) {
this->set_timeout("continuous-no-sound", 1000, [this]() { this->set_timeout("playing", 100, [this]() {
if (this->continuous_) { this->cancel_timeout("speaker-timeout");
this->request_start(true); this->set_state_(State::IDLE, State::IDLE);
}
}); });
} }
break;
}
default:
break;
}
}
void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { void VoiceAssistant::set_state_(State state) {
ESP_LOGD(TAG, "Starting..."); State old_state = this->state_;
this->state_ = state;
ESP_LOGD(TAG, "State changed from %d to %d", static_cast<uint8_t>(old_state), static_cast<uint8_t>(state));
}
void VoiceAssistant::set_state_(State state, State desired_state) {
this->set_state_(state);
this->desired_state_ = desired_state;
ESP_LOGD(TAG, "Desired state set to %d", static_cast<uint8_t>(desired_state));
}
void VoiceAssistant::failed_to_start() {
ESP_LOGE(TAG, "Failed to start server. See Home Assistant logs for more details.");
this->error_trigger_->trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details.");
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
}
void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t port) {
if (this->state_ != State::STARTING_PIPELINE) {
this->signal_stop_();
return;
}
ESP_LOGD(TAG, "Client started, streaming microphone");
memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_)); memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_));
if (this->dest_addr_.ss_family == AF_INET) { if (this->dest_addr_.ss_family == AF_INET) {
@ -123,38 +346,90 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) {
ESP_LOGW(TAG, "Unknown address family: %d", this->dest_addr_.ss_family); ESP_LOGW(TAG, "Unknown address family: %d", this->dest_addr_.ss_family);
return; return;
} }
this->running_ = true;
this->mic_->start(); if (this->mic_->is_running()) {
this->listening_trigger_->trigger(); this->set_state_(State::STREAMING_MICROPHONE, State::STREAMING_MICROPHONE);
} else {
this->set_state_(State::START_MICROPHONE, State::STREAMING_MICROPHONE);
}
} }
void VoiceAssistant::request_start(bool continuous) { void VoiceAssistant::request_start(bool continuous, bool silence_detection) {
ESP_LOGD(TAG, "Requesting start..."); if (!api::global_api_server->is_connected()) {
if (!api::global_api_server->start_voice_assistant(this->conversation_id_, this->silence_detection_)) { ESP_LOGE(TAG, "No API client connected");
ESP_LOGW(TAG, "Could not request start."); this->set_state_(State::IDLE, State::IDLE);
this->error_trigger_->trigger("not-connected", "Could not request start.");
this->continuous_ = false; this->continuous_ = false;
return; return;
} }
if (this->state_ == State::IDLE) {
this->continuous_ = continuous; this->continuous_ = continuous;
this->set_timeout("reset-conversation_id", 5 * 60 * 1000, [this]() { this->conversation_id_ = ""; }); this->silence_detection_ = silence_detection;
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
} else
#endif
{
this->set_state_(State::START_PIPELINE, State::START_MICROPHONE);
}
}
} }
void VoiceAssistant::signal_stop() { void VoiceAssistant::request_stop() {
this->continuous_ = false;
switch (this->state_) {
case State::IDLE:
break;
case State::START_MICROPHONE:
case State::STARTING_MICROPHONE:
case State::WAIT_FOR_VAD:
case State::WAITING_FOR_VAD:
case State::START_PIPELINE:
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
break;
case State::STARTING_PIPELINE:
case State::STREAMING_MICROPHONE:
this->signal_stop_();
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
break;
case State::STOP_MICROPHONE:
case State::STOPPING_MICROPHONE:
this->desired_state_ = State::IDLE;
break;
case State::AWAITING_RESPONSE:
case State::STREAMING_RESPONSE:
break; // Let the incoming audio stream finish then it will go to idle.
}
}
void VoiceAssistant::signal_stop_() {
ESP_LOGD(TAG, "Signaling stop..."); ESP_LOGD(TAG, "Signaling stop...");
this->mic_->stop();
this->running_ = false;
api::global_api_server->stop_voice_assistant(); api::global_api_server->stop_voice_assistant();
memset(&this->dest_addr_, 0, sizeof(this->dest_addr_)); memset(&this->dest_addr_, 0, sizeof(this->dest_addr_));
} }
void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
ESP_LOGD(TAG, "Event Type: %d", msg.event_type);
switch (msg.event_type) { switch (msg.event_type) {
case api::enums::VOICE_ASSISTANT_RUN_START: case api::enums::VOICE_ASSISTANT_RUN_START:
ESP_LOGD(TAG, "Assist Pipeline running"); ESP_LOGD(TAG, "Assist Pipeline running");
this->start_trigger_->trigger(); this->start_trigger_->trigger();
break; break;
case api::enums::VOICE_ASSISTANT_WAKE_WORD_START:
break;
case api::enums::VOICE_ASSISTANT_WAKE_WORD_END: {
ESP_LOGD(TAG, "Wake word detected");
this->wake_word_detected_trigger_->trigger();
break;
}
case api::enums::VOICE_ASSISTANT_STT_START:
ESP_LOGD(TAG, "STT Started");
this->listening_trigger_->trigger();
break;
case api::enums::VOICE_ASSISTANT_STT_END: { case api::enums::VOICE_ASSISTANT_STT_END: {
this->set_state_(State::STOP_MICROPHONE, State::AWAITING_RESPONSE);
std::string text; std::string text;
for (auto arg : msg.data) { for (auto arg : msg.data) {
if (arg.name == "text") { if (arg.name == "text") {
@ -166,7 +441,6 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
return; return;
} }
ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str());
this->signal_stop();
this->stt_end_trigger_->trigger(text); this->stt_end_trigger_->trigger(text);
break; break;
} }
@ -191,6 +465,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); ESP_LOGD(TAG, "Response: \"%s\"", text.c_str());
this->tts_start_trigger_->trigger(text); this->tts_start_trigger_->trigger(text);
#ifdef USE_SPEAKER
this->speaker_->start();
#endif
break; break;
} }
case api::enums::VOICE_ASSISTANT_TTS_END: { case api::enums::VOICE_ASSISTANT_TTS_END: {
@ -207,17 +484,31 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str()); ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str());
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
if (this->media_player_ != nullptr) { if (this->media_player_ != nullptr) {
this->playing_tts_ = true;
this->media_player_->make_call().set_media_url(url).perform(); this->media_player_->make_call().set_media_url(url).perform();
} }
#endif #endif
State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE;
this->set_state_(new_state, new_state);
this->tts_end_trigger_->trigger(url); this->tts_end_trigger_->trigger(url);
break; break;
} }
case api::enums::VOICE_ASSISTANT_RUN_END: case api::enums::VOICE_ASSISTANT_RUN_END: {
ESP_LOGD(TAG, "Assist Pipeline ended"); ESP_LOGD(TAG, "Assist Pipeline ended");
if (this->state_ == State::STREAMING_MICROPHONE) {
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
rb_reset(this->ring_buffer_);
// No need to stop the microphone since we didn't use the speaker
this->set_state_(State::WAIT_FOR_VAD, State::WAITING_FOR_VAD);
} else
#endif
{
this->set_state_(State::IDLE, State::IDLE);
}
}
this->end_trigger_->trigger(); this->end_trigger_->trigger();
break; break;
}
case api::enums::VOICE_ASSISTANT_ERROR: { case api::enums::VOICE_ASSISTANT_ERROR: {
std::string code = ""; std::string code = "";
std::string message = ""; std::string message = "";
@ -228,12 +519,20 @@ 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") {
// Don't change state here since either the "tts-end" or "run-end" events will do it.
return;
}
ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str()); ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str());
this->continuous_ = false; if (this->state_ != State::IDLE) {
this->signal_stop(); this->signal_stop_();
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
}
this->error_trigger_->trigger(code, message); this->error_trigger_->trigger(code, message);
break;
} }
default: default:
ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type);
break; break;
} }
} }

View file

@ -19,6 +19,11 @@
#endif #endif
#include "esphome/components/socket/socket.h" #include "esphome/components/socket/socket.h"
#ifdef USE_ESP_ADF
#include <esp_vad.h>
#include <ringbuf.h>
#endif
namespace esphome { namespace esphome {
namespace voice_assistant { namespace voice_assistant {
@ -28,19 +33,41 @@ namespace voice_assistant {
static const uint32_t INITIAL_VERSION = 1; static const uint32_t INITIAL_VERSION = 1;
static const uint32_t SPEAKER_SUPPORT = 2; static const uint32_t SPEAKER_SUPPORT = 2;
enum class State {
IDLE,
START_MICROPHONE,
STARTING_MICROPHONE,
WAIT_FOR_VAD,
WAITING_FOR_VAD,
START_PIPELINE,
STARTING_PIPELINE,
STREAMING_MICROPHONE,
STOP_MICROPHONE,
STOPPING_MICROPHONE,
AWAITING_RESPONSE,
STREAMING_RESPONSE,
};
class VoiceAssistant : public Component { class VoiceAssistant : public Component {
public: public:
void setup() override; void setup() override;
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void start(struct sockaddr_storage *addr, uint16_t port); void start_streaming(struct sockaddr_storage *addr, uint16_t port);
void failed_to_start();
void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; } void set_microphone(microphone::Microphone *mic) { this->mic_ = mic; }
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; } void set_speaker(speaker::Speaker *speaker) {
this->speaker_ = speaker;
this->local_output_ = true;
}
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void set_media_player(media_player::MediaPlayer *media_player) { this->media_player_ = media_player; } void set_media_player(media_player::MediaPlayer *media_player) {
this->media_player_ = media_player;
this->local_output_ = true;
}
#endif #endif
uint32_t get_version() const { uint32_t get_version() const {
@ -52,19 +79,29 @@ class VoiceAssistant : public Component {
return INITIAL_VERSION; return INITIAL_VERSION;
} }
void request_start(bool continuous = false); void request_start(bool continuous, bool silence_detection);
void signal_stop(); void request_stop();
void on_event(const api::VoiceAssistantEventResponse &msg); void on_event(const api::VoiceAssistantEventResponse &msg);
bool is_running() const { return this->running_; } bool is_running() const { return this->state_ != State::IDLE; }
void set_continuous(bool continuous) { this->continuous_ = continuous; } void set_continuous(bool continuous) { this->continuous_ = continuous; }
bool is_continuous() const { return this->continuous_; } bool is_continuous() const { return this->continuous_; }
void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; } void set_use_wake_word(bool use_wake_word) { this->use_wake_word_ = use_wake_word; }
#ifdef USE_ESP_ADF
void set_vad_threshold(uint8_t vad_threshold) { this->vad_threshold_ = vad_threshold; }
#endif
void set_noise_suppression_level(uint8_t noise_suppression_level) {
this->noise_suppression_level_ = noise_suppression_level;
}
void set_auto_gain(uint8_t auto_gain) { this->auto_gain_ = auto_gain; }
void set_volume_multiplier(float volume_multiplier) { this->volume_multiplier_ = volume_multiplier; }
Trigger<> *get_listening_trigger() const { return this->listening_trigger_; } Trigger<> *get_listening_trigger() const { return this->listening_trigger_; }
Trigger<> *get_start_trigger() const { return this->start_trigger_; } Trigger<> *get_start_trigger() const { return this->start_trigger_; }
Trigger<> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
Trigger<std::string> *get_stt_end_trigger() const { return this->stt_end_trigger_; } Trigger<std::string> *get_stt_end_trigger() const { return this->stt_end_trigger_; }
Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; } Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; }
Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; } Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; }
@ -72,11 +109,17 @@ class VoiceAssistant : public Component {
Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; } Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; }
protected: protected:
int read_microphone_();
void set_state_(State state);
void set_state_(State state, State desired_state);
void signal_stop_();
std::unique_ptr<socket::Socket> socket_ = nullptr; std::unique_ptr<socket::Socket> socket_ = nullptr;
struct sockaddr_storage dest_addr_; struct sockaddr_storage dest_addr_;
Trigger<> *listening_trigger_ = new Trigger<>(); Trigger<> *listening_trigger_ = new Trigger<>();
Trigger<> *start_trigger_ = new Trigger<>(); Trigger<> *start_trigger_ = new Trigger<>();
Trigger<> *wake_word_detected_trigger_ = new Trigger<>();
Trigger<std::string> *stt_end_trigger_ = new Trigger<std::string>(); Trigger<std::string> *stt_end_trigger_ = new Trigger<std::string>();
Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>(); Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>();
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>(); Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
@ -86,35 +129,61 @@ class VoiceAssistant : public Component {
microphone::Microphone *mic_{nullptr}; microphone::Microphone *mic_{nullptr};
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
speaker::Speaker *speaker_{nullptr}; speaker::Speaker *speaker_{nullptr};
uint8_t *speaker_buffer_;
size_t speaker_buffer_index_{0};
size_t speaker_buffer_size_{0};
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
media_player::MediaPlayer *media_player_{nullptr}; media_player::MediaPlayer *media_player_{nullptr};
bool playing_tts_{false}; bool playing_tts_{false};
#endif #endif
bool local_output_{false};
std::string conversation_id_{""}; std::string conversation_id_{""};
bool running_{false}; HighFrequencyLoopRequester high_freq_;
#ifdef USE_ESP_ADF
vad_handle_t vad_instance_;
ringbuf_handle_t ring_buffer_;
uint8_t vad_threshold_{5};
uint8_t vad_counter_{0};
#endif
bool use_wake_word_;
uint8_t noise_suppression_level_;
uint8_t auto_gain_;
float volume_multiplier_;
uint8_t *send_buffer_;
int16_t *input_buffer_;
bool continuous_{false}; bool continuous_{false};
bool silence_detection_; bool silence_detection_;
State state_{State::IDLE};
State desired_state_{State::IDLE};
}; };
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<VoiceAssistant> { template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<VoiceAssistant> {
public: public:
void play(Ts... x) override { this->parent_->request_start(); } void play(Ts... x) override { this->parent_->request_start(false, this->silence_detection_); }
void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; }
protected:
bool silence_detection_;
}; };
template<typename... Ts> class StartContinuousAction : public Action<Ts...>, public Parented<VoiceAssistant> { template<typename... Ts> class StartContinuousAction : public Action<Ts...>, public Parented<VoiceAssistant> {
public: public:
void play(Ts... x) override { this->parent_->request_start(true); } void play(Ts... x) override { this->parent_->request_start(true, true); }
}; };
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<VoiceAssistant> { template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<VoiceAssistant> {
public: public:
void play(Ts... x) override { void play(Ts... x) override { this->parent_->request_stop(); }
this->parent_->set_continuous(false);
this->parent_->signal_stop();
}
}; };
template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<VoiceAssistant> { template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<VoiceAssistant> {

View file

@ -30,11 +30,10 @@ void WakeOnLanButton::press_action() {
ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); ESP_LOGI(TAG, "Sending Wake-on-LAN Packet...");
bool begin_status = false; bool begin_status = false;
bool end_status = false; bool end_status = false;
uint32_t interface = esphome::network::get_ip_address();
IPAddress interface_ip = IPAddress(interface);
IPAddress broadcast = IPAddress(255, 255, 255, 255); IPAddress broadcast = IPAddress(255, 255, 255, 255);
#ifdef USE_ESP8266 #ifdef USE_ESP8266
begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, interface_ip, 128); begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9,
IPAddress((ip_addr_t) esphome::network::get_ip_address()), 128);
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
begin_status = this->udp_client_.beginPacket(broadcast, 9); begin_status = this->udp_client_.beginPacket(broadcast, 9);

View file

@ -18,6 +18,10 @@ from esphome.const import (
CONF_LOG, CONF_LOG,
CONF_VERSION, CONF_VERSION,
CONF_LOCAL, CONF_LOCAL,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -90,7 +94,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on(["esp32", "esp8266", "bk72xx", "rtl87xx"]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
default_url, default_url,
validate_local, validate_local,
validate_ota, validate_ota,

View file

@ -805,7 +805,12 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
continue; continue;
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
std::string data = this->select_json(obj, obj->state, DETAIL_STATE); auto detail = DETAIL_STATE;
auto *param = request->getParam("detail");
if (param && param->value() == "all") {
detail = DETAIL_ALL;
}
std::string data = this->select_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }

View file

@ -34,12 +34,14 @@ from esphome.const import (
CONF_EAP, CONF_EAP,
) )
from esphome.core import CORE, HexInt, coroutine_with_priority from esphome.core import CORE, HexInt, coroutine_with_priority
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
from esphome.components.network import IPAddress from esphome.components.network import IPAddress
from . import wpa2_eap from . import wpa2_eap
AUTO_LOAD = ["network"] AUTO_LOAD = ["network"]
NO_WIFI_VARIANTS = [const.VARIANT_ESP32H2]
wifi_ns = cg.esphome_ns.namespace("wifi") wifi_ns = cg.esphome_ns.namespace("wifi")
EAPAuth = wifi_ns.struct("EAPAuth") EAPAuth = wifi_ns.struct("EAPAuth")
ManualIP = wifi_ns.struct("ManualIP") ManualIP = wifi_ns.struct("ManualIP")
@ -148,6 +150,13 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
) )
def validate_variant(_):
if CORE.is_esp32:
variant = get_esp32_variant()
if variant in NO_WIFI_VARIANTS:
raise cv.Invalid(f"{variant} does not support WiFi")
def final_validate(config): def final_validate(config):
has_sta = bool(config.get(CONF_NETWORKS, True)) has_sta = bool(config.get(CONF_NETWORKS, True))
has_ap = CONF_AP in config has_ap = CONF_AP in config
@ -199,6 +208,7 @@ FINAL_VALIDATE_SCHEMA = cv.All(
extra=cv.ALLOW_EXTRA, extra=cv.ALLOW_EXTRA,
), ),
final_validate, final_validate,
validate_variant,
) )

View file

@ -113,9 +113,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
tcpip_adapter_ip_info_t info; tcpip_adapter_ip_info_t info;
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
esp_err_t dhcp_stop_ret = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); esp_err_t dhcp_stop_ret = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
if (dhcp_stop_ret != ESP_OK && dhcp_stop_ret != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) { if (dhcp_stop_ret != ESP_OK && dhcp_stop_ret != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) {
@ -128,23 +128,16 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
} }
ip_addr_t dns; ip_addr_t dns;
// TODO: is this needed?
#if LWIP_IPV6 #if LWIP_IPV6
dns.type = IPADDR_TYPE_V4; dns.type = IPADDR_TYPE_V4;
#endif #endif
if (uint32_t(manual_ip->dns1) != 0) { if (manual_ip->dns1.is_set()) {
#if LWIP_IPV6 dns = manual_ip->dns1;
dns.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns1);
#else
dns.addr = static_cast<uint32_t>(manual_ip->dns1);
#endif
dns_setserver(0, &dns); dns_setserver(0, &dns);
} }
if (uint32_t(manual_ip->dns2) != 0) { if (manual_ip->dns2.is_set()) {
#if LWIP_IPV6 dns = manual_ip->dns2;
dns.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns2);
#else
dns.addr = static_cast<uint32_t>(manual_ip->dns2);
#endif
dns_setserver(1, &dns); dns_setserver(1, &dns);
} }
@ -156,7 +149,7 @@ network::IPAddress WiFiComponent::wifi_sta_ip() {
return {}; return {};
tcpip_adapter_ip_info_t ip; tcpip_adapter_ip_info_t ip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bool WiFiComponent::wifi_apply_hostname_() { bool WiFiComponent::wifi_apply_hostname_() {
@ -614,13 +607,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
tcpip_adapter_ip_info_t info; tcpip_adapter_ip_info_t info;
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
if (manual_ip.has_value()) { if (manual_ip.has_value()) {
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
} else { } else {
info.ip.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.ip = network::IPAddress(192, 168, 4, 1);
info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.gw = network::IPAddress(192, 168, 4, 1);
info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0)); info.netmask = network::IPAddress(255, 255, 255, 0);
} }
tcpip_adapter_dhcp_status_t dhcp_status; tcpip_adapter_dhcp_status_t dhcp_status;
tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status);
@ -638,12 +631,12 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
dhcps_lease_t lease; dhcps_lease_t lease;
lease.enable = true; lease.enable = true;
network::IPAddress start_address = info.ip.addr; network::IPAddress start_address = network::IPAddress(&info.ip);
start_address[3] += 99; start_address += 99;
lease.start_ip.addr = static_cast<uint32_t>(start_address); lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
start_address[3] += 100; start_address += 100;
lease.end_ip.addr = static_cast<uint32_t>(start_address); lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
@ -702,7 +695,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
tcpip_adapter_ip_info_t ip; tcpip_adapter_ip_info_t ip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); }
@ -718,9 +711,9 @@ bssid_t WiFiComponent::wifi_bssid() {
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); }
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return network::IPAddress(WiFi.subnetMask()); }
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return network::IPAddress(WiFi.gatewayIP()); }
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return network::IPAddress(WiFi.dnsIP(num)); }
void WiFiComponent::wifi_loop_() {} void WiFiComponent::wifi_loop_() {}
} // namespace wifi } // namespace wifi

View file

@ -147,9 +147,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
#endif #endif
struct ip_info info {}; struct ip_info info {};
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
if (dhcp_status == DHCP_STARTED) { if (dhcp_status == DHCP_STARTED) {
bool dhcp_stop_ret = wifi_station_dhcpc_stop(); bool dhcp_stop_ret = wifi_station_dhcpc_stop();
@ -165,12 +165,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
} }
ip_addr_t dns; ip_addr_t dns;
if (uint32_t(manual_ip->dns1) != 0) { if (manual_ip->dns1.is_set()) {
ip_addr_set_ip4_u32_val(dns, static_cast<uint32_t>(manual_ip->dns1)); dns = manual_ip->dns1;
dns_setserver(0, &dns); dns_setserver(0, &dns);
} }
if (uint32_t(manual_ip->dns2) != 0) { if (manual_ip->dns2.is_set()) {
ip_addr_set_ip4_u32_val(dns, static_cast<uint32_t>(manual_ip->dns2)); dns = manual_ip->dns2;
dns_setserver(1, &dns); dns_setserver(1, &dns);
} }
@ -190,7 +190,7 @@ network::IPAddress WiFiComponent::wifi_sta_ip() {
return {}; return {};
struct ip_info ip {}; struct ip_info ip {};
wifi_get_ip_info(STATION_IF, &ip); wifi_get_ip_info(STATION_IF, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bool WiFiComponent::wifi_apply_hostname_() { bool WiFiComponent::wifi_apply_hostname_() {
const std::string &hostname = App.get_name(); const std::string &hostname = App.get_name();
@ -695,13 +695,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
struct ip_info info {}; struct ip_info info {};
if (manual_ip.has_value()) { if (manual_ip.has_value()) {
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
} else { } else {
info.ip.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.ip = network::IPAddress(192, 168, 4, 1);
info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.gw = network::IPAddress(192, 168, 4, 1);
info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0)); info.netmask = network::IPAddress(255, 255, 255, 0);
} }
if (wifi_softap_dhcps_status() == DHCP_STARTED) { if (wifi_softap_dhcps_status() == DHCP_STARTED) {
@ -721,12 +721,12 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
struct dhcps_lease lease {}; struct dhcps_lease lease {};
lease.enable = true; lease.enable = true;
network::IPAddress start_address = info.ip.addr; network::IPAddress start_address = network::IPAddress(&info.ip);
start_address[3] += 99; start_address += 99;
lease.start_ip.addr = static_cast<uint32_t>(start_address); lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
start_address[3] += 100; start_address += 100;
lease.end_ip.addr = static_cast<uint32_t>(start_address); lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
if (!wifi_softap_set_dhcps_lease(&lease)) { if (!wifi_softap_set_dhcps_lease(&lease)) {
ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!"); ESP_LOGV(TAG, "Setting SoftAP DHCP lease failed!");
@ -793,7 +793,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
struct ip_info ip {}; struct ip_info ip {};
wifi_get_ip_info(SOFTAP_IF, &ip); wifi_get_ip_info(SOFTAP_IF, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bssid_t WiFiComponent::wifi_bssid() { bssid_t WiFiComponent::wifi_bssid() {
bssid_t bssid{}; bssid_t bssid{};
@ -807,9 +807,9 @@ bssid_t WiFiComponent::wifi_bssid() {
std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); } std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); }
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {WiFi.dnsIP(num)}; } network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { return {(const ip_addr_t *) WiFi.dnsIP(num)}; }
void WiFiComponent::wifi_loop_() {} void WiFiComponent::wifi_loop_() {}
} // namespace wifi } // namespace wifi

View file

@ -437,9 +437,9 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
} }
esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
err = esp_netif_dhcpc_stop(s_sta_netif); err = esp_netif_dhcpc_stop(s_sta_netif);
if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err)); ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err));
@ -452,12 +452,12 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
} }
esp_netif_dns_info_t dns; esp_netif_dns_info_t dns;
if (uint32_t(manual_ip->dns1) != 0) { if (manual_ip->dns1.is_set()) {
dns.ip.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns1); dns.ip = manual_ip->dns1;
esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns); esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns);
} }
if (uint32_t(manual_ip->dns2) != 0) { if (manual_ip->dns2.is_set()) {
dns.ip.u_addr.ip4.addr = static_cast<uint32_t>(manual_ip->dns2); dns.ip = manual_ip->dns2;
esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns); esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns);
} }
@ -471,9 +471,10 @@ network::IPAddress WiFiComponent::wifi_sta_ip() {
esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
return false; // TODO: do something smarter
// return false;
} }
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bool WiFiComponent::wifi_apply_hostname_() { bool WiFiComponent::wifi_apply_hostname_() {
@ -769,13 +770,13 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
esp_netif_ip_info_t info; esp_netif_ip_info_t info;
if (manual_ip.has_value()) { if (manual_ip.has_value()) {
info.ip.addr = static_cast<uint32_t>(manual_ip->static_ip); info.ip = manual_ip->static_ip;
info.gw.addr = static_cast<uint32_t>(manual_ip->gateway); info.gw = manual_ip->gateway;
info.netmask.addr = static_cast<uint32_t>(manual_ip->subnet); info.netmask = manual_ip->subnet;
} else { } else {
info.ip.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.ip = network::IPAddress(192, 168, 4, 1);
info.gw.addr = static_cast<uint32_t>(network::IPAddress(192, 168, 4, 1)); info.gw = network::IPAddress(192, 168, 4, 1);
info.netmask.addr = static_cast<uint32_t>(network::IPAddress(255, 255, 255, 0)); info.netmask = network::IPAddress(255, 255, 255, 0);
} }
err = esp_netif_dhcpc_stop(s_sta_netif); err = esp_netif_dhcpc_stop(s_sta_netif);
@ -792,12 +793,12 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
dhcps_lease_t lease; dhcps_lease_t lease;
lease.enable = true; lease.enable = true;
network::IPAddress start_address = info.ip.addr; network::IPAddress start_address = network::IPAddress(&info.ip);
start_address[3] += 99; start_address += 99;
lease.start_ip.addr = static_cast<uint32_t>(start_address); lease.start_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease start: %s", start_address.str().c_str());
start_address[3] += 100; start_address += 100;
lease.end_ip.addr = static_cast<uint32_t>(start_address); lease.end_ip = start_address;
ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str());
err = esp_netif_dhcps_option(s_sta_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); err = esp_netif_dhcps_option(s_sta_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease));
@ -855,7 +856,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { network::IPAddress WiFiComponent::wifi_soft_ap_ip() {
esp_netif_ip_info_t ip; esp_netif_ip_info_t ip;
esp_netif_get_ip_info(s_sta_netif, &ip); esp_netif_get_ip_info(s_sta_netif, &ip);
return {ip.ip.addr}; return network::IPAddress(&ip.ip);
} }
bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); }
@ -907,7 +908,7 @@ network::IPAddress WiFiComponent::wifi_subnet_mask_() {
ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
return {}; return {};
} }
return {ip.netmask.addr}; return network::IPAddress(&ip.netmask);
} }
network::IPAddress WiFiComponent::wifi_gateway_ip_() { network::IPAddress WiFiComponent::wifi_gateway_ip_() {
esp_netif_ip_info_t ip; esp_netif_ip_info_t ip;
@ -916,15 +917,11 @@ network::IPAddress WiFiComponent::wifi_gateway_ip_() {
ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
return {}; return {};
} }
return {ip.gw.addr}; return network::IPAddress(&ip.gw);
} }
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
const ip_addr_t *dns_ip = dns_getserver(num); const ip_addr_t *dns_ip = dns_getserver(num);
#if LWIP_IPV6 return network::IPAddress(dns_ip);
return {dns_ip->u_addr.ip4.addr};
#else
return {dns_ip->addr};
#endif
} }
} // namespace wifi } // namespace wifi

View file

@ -76,9 +76,7 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
return true; return true;
} }
WiFi.config(static_cast<uint32_t>(manual_ip->static_ip), static_cast<uint32_t>(manual_ip->gateway), WiFi.config(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet, manual_ip->dns1, manual_ip->dns2);
static_cast<uint32_t>(manual_ip->subnet), static_cast<uint32_t>(manual_ip->dns1),
static_cast<uint32_t>(manual_ip->dns2));
return true; return true;
} }
@ -420,8 +418,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional<ManualIP> manual_ip) {
return false; return false;
if (manual_ip.has_value()) { if (manual_ip.has_value()) {
return WiFi.softAPConfig(static_cast<uint32_t>(manual_ip->static_ip), static_cast<uint32_t>(manual_ip->gateway), return WiFi.softAPConfig(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet);
static_cast<uint32_t>(manual_ip->subnet));
} else { } else {
return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0)); return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
} }

View file

@ -70,11 +70,11 @@ bool WiFiComponent::wifi_sta_ip_config_(optional<ManualIP> manual_ip) {
return true; return true;
} }
IPAddress ip_address = IPAddress(manual_ip->static_ip); IPAddress ip_address = manual_ip->static_ip;
IPAddress gateway = IPAddress(manual_ip->gateway); IPAddress gateway = manual_ip->gateway;
IPAddress subnet = IPAddress(manual_ip->subnet); IPAddress subnet = manual_ip->subnet;
IPAddress dns = IPAddress(manual_ip->dns1); IPAddress dns = manual_ip->dns1;
WiFi.config(ip_address, dns, gateway, subnet); WiFi.config(ip_address, dns, gateway, subnet);
return true; return true;
@ -151,7 +151,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
return true; return true;
} }
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.localIP()}; } network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {(const ip_addr_t *) WiFi.localIP()}; }
bool WiFiComponent::wifi_disconnect_() { bool WiFiComponent::wifi_disconnect_() {
int err = cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA); int err = cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA);
@ -170,16 +170,12 @@ std::string WiFiComponent::wifi_ssid() { return WiFi.SSID().c_str(); }
int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); }
int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); } int32_t WiFiComponent::wifi_channel_() { return WiFi.channel(); }
network::IPAddress WiFiComponent::wifi_sta_ip() { return {WiFi.localIP()}; } network::IPAddress WiFiComponent::wifi_sta_ip() { return {(const ip_addr_t *) WiFi.localIP()}; }
network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {(const ip_addr_t *) WiFi.subnetMask()}; }
network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {(const ip_addr_t *) WiFi.gatewayIP()}; }
network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { network::IPAddress WiFiComponent::wifi_dns_ip_(int num) {
const ip_addr_t *dns_ip = dns_getserver(num); const ip_addr_t *dns_ip = dns_getserver(num);
#ifdef PIO_FRAMEWORK_ARDUINO_ENABLE_IPV6 return network::IPAddress(dns_ip);
return {dns_ip->u_addr.ip4.addr};
#else
return {dns_ip->addr};
#endif
} }
void WiFiComponent::wifi_loop_() { void WiFiComponent::wifi_loop_() {

View file

@ -3,7 +3,7 @@ import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import spi, touchscreen from esphome.components import spi, touchscreen
from esphome.const import CONF_ID, CONF_THRESHOLD, CONF_INTERRUPT_PIN from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_IRQ_PIN, CONF_THRESHOLD
CODEOWNERS = ["@numo68", "@nielsnl68"] CODEOWNERS = ["@numo68", "@nielsnl68"]
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -26,7 +26,6 @@ CONF_SWAP_X_Y = "swap_x_y"
# obsolete Keys # obsolete Keys
CONF_DIMENSION_X = "dimension_x" CONF_DIMENSION_X = "dimension_x"
CONF_DIMENSION_Y = "dimension_y" CONF_DIMENSION_Y = "dimension_y"
CONF_IRQ_PIN = "irq_pin"
def validate_xpt2046(config): def validate_xpt2046(config):

View file

@ -51,6 +51,9 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
VALID_SUBSTITUTIONS_CHARACTERS, VALID_SUBSTITUTIONS_CHARACTERS,
@ -583,9 +586,9 @@ def only_with_framework(frameworks):
return validator_ return validator_
only_on_esp32 = only_on("esp32") only_on_esp32 = only_on(PLATFORM_ESP32)
only_on_esp8266 = only_on("esp8266") only_on_esp8266 = only_on(PLATFORM_ESP8266)
only_on_rp2040 = only_on("rp2040") only_on_rp2040 = only_on(PLATFORM_RP2040)
only_with_arduino = only_with_framework("arduino") only_with_arduino = only_with_framework("arduino")
only_with_esp_idf = only_with_framework("esp-idf") only_with_esp_idf = only_with_framework("esp-idf")
@ -1669,7 +1672,7 @@ def maybe_simple_value(*validators, **kwargs):
return validate return validate
_ENTITY_CATEGORIES = { ENTITY_CATEGORIES = {
ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE, ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE,
ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC,
@ -1677,7 +1680,7 @@ _ENTITY_CATEGORIES = {
def entity_category(value): def entity_category(value):
return enum(_ENTITY_CATEGORIES, lower=True)(value) return enum(ENTITY_CATEGORIES, lower=True)(value)
MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema( MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema(

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2023.9.3" __version__ = "2023.10.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
@ -265,6 +265,9 @@ CONF_FAN_ONLY_MODE = "fan_only_mode"
CONF_FAN_WITH_COOLING = "fan_with_cooling" CONF_FAN_WITH_COOLING = "fan_with_cooling"
CONF_FAN_WITH_HEATING = "fan_with_heating" CONF_FAN_WITH_HEATING = "fan_with_heating"
CONF_FAST_CONNECT = "fast_connect" CONF_FAST_CONNECT = "fast_connect"
CONF_FIELD_STRENGTH_X = "field_strength_x"
CONF_FIELD_STRENGTH_Y = "field_strength_y"
CONF_FIELD_STRENGTH_Z = "field_strength_z"
CONF_FILE = "file" CONF_FILE = "file"
CONF_FILES = "files" CONF_FILES = "files"
CONF_FILTER = "filter" CONF_FILTER = "filter"
@ -360,6 +363,7 @@ CONF_INVALID_COOLDOWN = "invalid_cooldown"
CONF_INVERT = "invert" CONF_INVERT = "invert"
CONF_INVERTED = "inverted" CONF_INVERTED = "inverted"
CONF_IP_ADDRESS = "ip_address" CONF_IP_ADDRESS = "ip_address"
CONF_IRQ_PIN = "irq_pin"
CONF_JS_INCLUDE = "js_include" CONF_JS_INCLUDE = "js_include"
CONF_JS_URL = "js_url" CONF_JS_URL = "js_url"
CONF_JVC = "jvc" CONF_JVC = "jvc"
@ -779,6 +783,7 @@ CONF_TRACES = "traces"
CONF_TRANSITION_LENGTH = "transition_length" CONF_TRANSITION_LENGTH = "transition_length"
CONF_TRIGGER_ID = "trigger_id" CONF_TRIGGER_ID = "trigger_id"
CONF_TRIGGER_PIN = "trigger_pin" CONF_TRIGGER_PIN = "trigger_pin"
CONF_TUNE_ANTENNA = "tune_antenna"
CONF_TURN_OFF_ACTION = "turn_off_action" CONF_TURN_OFF_ACTION = "turn_off_action"
CONF_TURN_ON_ACTION = "turn_on_action" CONF_TURN_ON_ACTION = "turn_on_action"
CONF_TVOC = "tvoc" CONF_TVOC = "tvoc"
@ -915,7 +920,7 @@ UNIT_BYTES = "B"
UNIT_CELSIUS = "°C" UNIT_CELSIUS = "°C"
UNIT_CENTIMETER = "cm" UNIT_CENTIMETER = "cm"
UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNT_DECILITRE = "/dL"
UNIT_COUNTS_PER_CUBIC_METER = "#/" UNIT_COUNTS_PER_CUBIC_CENTIMETER = "#/c"
UNIT_CUBIC_METER = "" UNIT_CUBIC_METER = ""
UNIT_CUBIC_METER_PER_HOUR = "m³/h" UNIT_CUBIC_METER_PER_HOUR = "m³/h"
UNIT_DECIBEL = "dB" UNIT_DECIBEL = "dB"

View file

@ -15,13 +15,19 @@ from esphome.const import (
KEY_CORE, KEY_CORE,
KEY_TARGET_FRAMEWORK, KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM, KEY_TARGET_PLATFORM,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
PLATFORM_RP2040,
PLATFORM_HOST,
) )
from esphome.coroutine import FakeAwaitable as _FakeAwaitable from esphome.coroutine import FakeAwaitable as _FakeAwaitable
from esphome.coroutine import FakeEventLoop as _FakeEventLoop from esphome.coroutine import FakeEventLoop as _FakeEventLoop
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.coroutine import coroutine, coroutine_with_priority # noqa from esphome.coroutine import coroutine, coroutine_with_priority # noqa
from esphome.helpers import ensure_unique_string, is_ha_addon from esphome.helpers import ensure_unique_string, get_str_env, is_ha_addon
from esphome.util import OrderedDict from esphome.util import OrderedDict
if TYPE_CHECKING: if TYPE_CHECKING:
@ -558,6 +564,8 @@ class EsphomeCore:
def data_dir(self): def data_dir(self):
if is_ha_addon(): if is_ha_addon():
return os.path.join("/data") return os.path.join("/data")
if "ESPHOME_DATA_DIR" in os.environ:
return get_str_env("ESPHOME_DATA_DIR", None)
return self.relative_config_path(".esphome") return self.relative_config_path(".esphome")
@property @property
@ -596,23 +604,23 @@ class EsphomeCore:
@property @property
def is_esp8266(self): def is_esp8266(self):
return self.target_platform == "esp8266" return self.target_platform == PLATFORM_ESP8266
@property @property
def is_esp32(self): def is_esp32(self):
return self.target_platform == "esp32" return self.target_platform == PLATFORM_ESP32
@property @property
def is_rp2040(self): def is_rp2040(self):
return self.target_platform == "rp2040" return self.target_platform == PLATFORM_RP2040
@property @property
def is_bk72xx(self): def is_bk72xx(self):
return self.target_platform == "bk72xx" return self.target_platform == PLATFORM_BK72XX
@property @property
def is_rtl87xx(self): def is_rtl87xx(self):
return self.target_platform == "rtl87xx" return self.target_platform == PLATFORM_RTL87XX
@property @property
def is_libretiny(self): def is_libretiny(self):
@ -620,7 +628,7 @@ class EsphomeCore:
@property @property
def is_host(self): def is_host(self):
return self.target_platform == "host" return self.target_platform == PLATFORM_HOST
@property @property
def target_framework(self): def target_framework(self):

View file

@ -48,6 +48,22 @@ template<typename... Ts> class NotCondition : public Condition<Ts...> {
Condition<Ts...> *condition_; Condition<Ts...> *condition_;
}; };
template<typename... Ts> class XorCondition : public Condition<Ts...> {
public:
explicit XorCondition(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
bool check(Ts... x) override {
bool xor_state = false;
for (auto *condition : this->conditions_) {
xor_state = xor_state ^ condition->check(x...);
}
return xor_state;
}
protected:
std::vector<Condition<Ts...> *> conditions_;
};
template<typename... Ts> class LambdaCondition : public Condition<Ts...> { template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
public: public:
explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {} explicit LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
@ -330,4 +346,38 @@ template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
PollingComponent *component_; PollingComponent *component_;
}; };
template<typename... Ts> class SuspendComponentAction : public Action<Ts...> {
public:
SuspendComponentAction(PollingComponent *component) : component_(component) {}
void play(Ts... x) override {
if (!this->component_->is_ready())
return;
this->component_->stop_poller();
}
protected:
PollingComponent *component_;
};
template<typename... Ts> class ResumeComponentAction : public Action<Ts...> {
public:
ResumeComponentAction(PollingComponent *component) : component_(component) {}
TEMPLATABLE_VALUE(uint32_t, update_interval)
void play(Ts... x) override {
if (!this->component_->is_ready()) {
return;
}
optional<uint32_t> update_interval = this->update_interval_.optional_value(x...);
if (update_interval.has_value()) {
this->component_->set_update_interval(update_interval.value());
}
this->component_->start_poller();
}
protected:
PollingComponent *component_;
};
} // namespace esphome } // namespace esphome

View file

@ -188,10 +188,20 @@ void PollingComponent::call_setup() {
// Let the polling component subclass setup their HW. // Let the polling component subclass setup their HW.
this->setup(); this->setup();
// init the poller
this->start_poller();
}
void PollingComponent::start_poller() {
// Register interval. // Register interval.
this->set_interval("update", this->get_update_interval(), [this]() { this->update(); }); this->set_interval("update", this->get_update_interval(), [this]() { this->update(); });
} }
void PollingComponent::stop_poller() {
// Clear the interval to suspend component
this->cancel_interval("update");
}
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; } uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }

View file

@ -308,6 +308,12 @@ class PollingComponent : public Component {
/// Get the update interval in ms of this sensor /// Get the update interval in ms of this sensor
virtual uint32_t get_update_interval() const; virtual uint32_t get_update_interval() const;
// Start the poller, used for component.suspend
void start_poller();
// Stop the poller, used for component.suspend
void stop_poller();
protected: protected:
uint32_t update_interval_; uint32_t update_interval_;
}; };

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