Merge pull request #5555 from esphome/bump-2023.10.0

2023.10.0
This commit is contained in:
Jesse Hills 2023-10-18 17:38:50 +13:00 committed by GitHub
commit b973238323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
134 changed files with 2353 additions and 622 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+deb11u10 \
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,12 @@ 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;
VOICE_ASSISTANT_TTS_STREAM_START = 98;
VOICE_ASSISTANT_TTS_STREAM_END = 99;
} }
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,18 @@ 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";
case enums::VOICE_ASSISTANT_TTS_STREAM_START:
return "VOICE_ASSISTANT_TTS_STREAM_START";
case enums::VOICE_ASSISTANT_TTS_STREAM_END:
return "VOICE_ASSISTANT_TTS_STREAM_END";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@ -6344,6 +6370,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 +6427,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 +6440,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 +6451,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 +6466,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,12 @@ 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,
VOICE_ASSISTANT_TTS_STREAM_START = 98,
VOICE_ASSISTANT_TTS_STREAM_END = 99,
}; };
enum AlarmControlPanelState : uint32_t { enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0, ALARM_STATE_DISARMED = 0,
@ -1651,11 +1662,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();
@ -35,10 +37,14 @@ void BP1658CJ::loop() {
uint8_t data[12]; uint8_t data[12];
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) { this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
// Off / Sleep
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_STANDBY;
for (int i = 1; i < 12; i++) for (int i = 1; i < 12; i++)
data[i] = 0; data[i] = 0;
// First turn all channels off
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_5CH;
this->write_buffer_(data, 12);
// Then sleep
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_STANDBY;
this->write_buffer_(data, 12); this->write_buffer_(data, 12);
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && } else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) { (this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
@ -81,27 +87,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

@ -17,12 +17,16 @@ static const uint8_t BP5758D_ADDR_START_2CH = 0b00100000;
static const uint8_t BP5758D_ADDR_START_5CH = 0b00110000; static const uint8_t BP5758D_ADDR_START_5CH = 0b00110000;
static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111; static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111;
static const uint8_t BP5758D_DELAY = 2;
void BP5758D::setup() { void BP5758D::setup() {
ESP_LOGCONFIG(TAG, "Setting up BP5758D Output Component..."); ESP_LOGCONFIG(TAG, "Setting up BP5758D Output Component...");
this->data_pin_->setup(); this->data_pin_->setup();
this->data_pin_->digital_write(false); this->data_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
this->clock_pin_->setup(); this->clock_pin_->setup();
this->clock_pin_->digital_write(false); this->clock_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
this->channel_current_.resize(5, 0); this->channel_current_.resize(5, 0);
this->pwm_amounts_.resize(5, 0); this->pwm_amounts_.resize(5, 0);
} }
@ -39,11 +43,11 @@ void BP5758D::loop() {
uint8_t data[17]; uint8_t data[17];
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) { this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
for (int i = 1; i < 16; i++) for (int i = 1; i < 17; i++)
data[i] = 0; data[i] = 0;
// First turn all channels off // First turn all channels off
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_3CH; data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_5CH;
this->write_buffer_(data, 17); this->write_buffer_(data, 17);
// Then sleep // Then sleep
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY; data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY;
@ -123,28 +127,42 @@ void BP5758D::set_channel_value_(uint8_t channel, uint16_t value) {
void BP5758D::set_channel_current_(uint8_t channel, uint8_t current) { this->channel_current_[channel] = current; } void BP5758D::set_channel_current_(uint8_t channel, uint8_t current) { this->channel_current_[channel] = current; }
void BP5758D::write_bit_(bool value) { void BP5758D::write_bit_(bool value) {
this->clock_pin_->digital_write(false);
this->data_pin_->digital_write(value); this->data_pin_->digital_write(value);
delayMicroseconds(BP5758D_DELAY);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(BP5758D_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
} }
void BP5758D::write_byte_(uint8_t data) { void BP5758D::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);
} }
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(BP5758D_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
} }
void BP5758D::write_buffer_(uint8_t *buffer, uint8_t size) { void BP5758D::write_buffer_(uint8_t *buffer, uint8_t size) {
this->data_pin_->digital_write(false); this->data_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(BP5758D_DELAY);
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]);
} }
this->clock_pin_->digital_write(false);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(BP5758D_DELAY);
this->data_pin_->digital_write(true); this->data_pin_->digital_write(true);
delayMicroseconds(BP5758D_DELAY);
} }
} // namespace bp5758d } // namespace bp5758d

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

@ -2,9 +2,9 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "ble_uuid.h"
#include <cstring>
#include <cstdio> #include <cstdio>
#include <cstring>
#include "ble_uuid.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -16,8 +16,8 @@ BLEAdvertising::BLEAdvertising() {
this->advertising_data_.set_scan_rsp = false; this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = true; this->advertising_data_.include_name = true;
this->advertising_data_.include_txpower = true; this->advertising_data_.include_txpower = true;
this->advertising_data_.min_interval = 0x20; this->advertising_data_.min_interval = 0;
this->advertising_data_.max_interval = 0x40; this->advertising_data_.max_interval = 0;
this->advertising_data_.appearance = 0x00; this->advertising_data_.appearance = 0x00;
this->advertising_data_.manufacturer_len = 0; this->advertising_data_.manufacturer_len = 0;
this->advertising_data_.p_manufacturer_data = nullptr; this->advertising_data_.p_manufacturer_data = nullptr;
@ -42,6 +42,17 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
this->advertising_uuids_.end()); this->advertising_uuids_.end());
} }
void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
delete[] this->advertising_data_.p_service_data;
this->advertising_data_.p_service_data = nullptr;
this->advertising_data_.service_data_len = data.size();
if (!data.empty()) {
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
this->advertising_data_.p_service_data = new uint8_t[data.size()];
memcpy(this->advertising_data_.p_service_data, data.data(), data.size());
}
}
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) { void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
delete[] this->advertising_data_.p_manufacturer_data; delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr; this->advertising_data_.p_manufacturer_data = nullptr;
@ -85,8 +96,6 @@ void BLEAdvertising::start() {
this->scan_response_data_.set_scan_rsp = true; this->scan_response_data_.set_scan_rsp = true;
this->scan_response_data_.include_name = true; this->scan_response_data_.include_name = true;
this->scan_response_data_.include_txpower = true; this->scan_response_data_.include_txpower = true;
this->scan_response_data_.min_interval = 0;
this->scan_response_data_.max_interval = 0;
this->scan_response_data_.manufacturer_len = 0; this->scan_response_data_.manufacturer_len = 0;
this->scan_response_data_.appearance = 0; this->scan_response_data_.appearance = 0;
this->scan_response_data_.flag = 0; this->scan_response_data_.flag = 0;

View file

@ -21,6 +21,7 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data); void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_service_data(const std::vector<uint8_t> &data);
void start(); void start();
void stop(); void stop();

View file

@ -90,6 +90,8 @@ void BLEService::stop() {
ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err); ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err);
return; return;
} }
esp32_ble::global_ble->get_advertising()->remove_service_uuid(this->uuid_);
esp32_ble::global_ble->get_advertising()->start();
this->running_state_ = STOPPING; this->running_state_ = STOPPING;
} }

View file

@ -7,11 +7,11 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_bt_defs.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <esp_gatt_defs.h> #include <esp_gatt_defs.h>
#include <esp_gattc_api.h> #include <esp_gattc_api.h>
#include <esp_gatts_api.h> #include <esp_gatts_api.h>
#include <esp_bt_defs.h>
namespace esphome { namespace esphome {
namespace esp32_ble_server { namespace esp32_ble_server {

View file

@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server
from esphome.const import CONF_ID from esphome.const import CONF_ID
AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"] AUTO_LOAD = ["esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_beacon"] CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]

View file

@ -18,6 +18,17 @@ ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
void ESP32ImprovComponent::setup() { void ESP32ImprovComponent::setup() {
this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true);
this->setup_characteristics(); this->setup_characteristics();
#ifdef USE_BINARY_SENSOR
if (this->authorizer_ != nullptr) {
this->authorizer_->add_on_state_callback([this](bool state) {
if (state) {
this->authorized_start_ = millis();
this->identify_start_ = 0;
}
});
}
#endif
} }
void ESP32ImprovComponent::setup_characteristics() { void ESP32ImprovComponent::setup_characteristics() {
@ -50,8 +61,10 @@ void ESP32ImprovComponent::setup_characteristics() {
BLEDescriptor *capabilities_descriptor = new BLE2902(); BLEDescriptor *capabilities_descriptor = new BLE2902();
this->capabilities_->add_descriptor(capabilities_descriptor); this->capabilities_->add_descriptor(capabilities_descriptor);
uint8_t capabilities = 0x00; uint8_t capabilities = 0x00;
#ifdef USE_OUTPUT
if (this->status_indicator_ != nullptr) if (this->status_indicator_ != nullptr)
capabilities |= improv::CAPABILITY_IDENTIFY; capabilities |= improv::CAPABILITY_IDENTIFY;
#endif
this->capabilities_->set_value(capabilities); this->capabilities_->set_value(capabilities);
this->setup_complete_ = true; this->setup_complete_ = true;
} }
@ -63,8 +76,7 @@ void ESP32ImprovComponent::loop() {
switch (this->state_) { switch (this->state_) {
case improv::STATE_STOPPED: case improv::STATE_STOPPED:
if (this->status_indicator_ != nullptr) this->set_status_indicator_state_(false);
this->status_indicator_->turn_off();
if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
if (this->service_->is_running()) { if (this->service_->is_running()) {
@ -80,18 +92,22 @@ void ESP32ImprovComponent::loop() {
} }
break; break;
case improv::STATE_AWAITING_AUTHORIZATION: { case improv::STATE_AWAITING_AUTHORIZATION: {
if (this->authorizer_ == nullptr || this->authorizer_->state) { #ifdef USE_BINARY_SENSOR
if (this->authorizer_ == nullptr ||
(this->authorized_start_ != 0 && ((now - this->authorized_start_) < this->authorized_duration_))) {
this->set_state_(improv::STATE_AUTHORIZED); this->set_state_(improv::STATE_AUTHORIZED);
this->authorized_start_ = now; } else
} else { #else
if (this->status_indicator_ != nullptr) { this->set_state_(improv::STATE_AUTHORIZED);
#endif
{
if (!this->check_identify_()) if (!this->check_identify_())
this->status_indicator_->turn_on(); this->set_status_indicator_state_(true);
}
} }
break; break;
} }
case improv::STATE_AUTHORIZED: { case improv::STATE_AUTHORIZED: {
#ifdef USE_BINARY_SENSOR
if (this->authorizer_ != nullptr) { if (this->authorizer_ != nullptr) {
if (now - this->authorized_start_ > this->authorized_duration_) { if (now - this->authorized_start_ > this->authorized_duration_) {
ESP_LOGD(TAG, "Authorization timeout"); ESP_LOGD(TAG, "Authorization timeout");
@ -99,25 +115,14 @@ void ESP32ImprovComponent::loop() {
return; return;
} }
} }
if (this->status_indicator_ != nullptr) { #endif
if (!this->check_identify_()) { if (!this->check_identify_()) {
if ((now % 1000) < 500) { this->set_status_indicator_state_((now % 1000) < 500);
this->status_indicator_->turn_on();
} else {
this->status_indicator_->turn_off();
}
}
} }
break; break;
} }
case improv::STATE_PROVISIONING: { case improv::STATE_PROVISIONING: {
if (this->status_indicator_ != nullptr) { this->set_status_indicator_state_((now % 200) < 100);
if ((now % 200) < 100) {
this->status_indicator_->turn_on();
} else {
this->status_indicator_->turn_off();
}
}
if (wifi::global_wifi_component->is_connected()) { if (wifi::global_wifi_component->is_connected()) {
wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
this->connecting_sta_.get_password()); this->connecting_sta_.get_password());
@ -142,13 +147,27 @@ void ESP32ImprovComponent::loop() {
} }
case improv::STATE_PROVISIONED: { case improv::STATE_PROVISIONED: {
this->incoming_data_.clear(); this->incoming_data_.clear();
if (this->status_indicator_ != nullptr) this->set_status_indicator_state_(false);
this->status_indicator_->turn_off();
break; break;
} }
} }
} }
void ESP32ImprovComponent::set_status_indicator_state_(bool state) {
#ifdef USE_OUTPUT
if (this->status_indicator_ == nullptr)
return;
if (this->status_indicator_state_ == state)
return;
this->status_indicator_state_ = state;
if (state) {
this->status_indicator_->turn_on();
} else {
this->status_indicator_->turn_off();
}
#endif
}
bool ESP32ImprovComponent::check_identify_() { bool ESP32ImprovComponent::check_identify_() {
uint32_t now = millis(); uint32_t now = millis();
@ -156,11 +175,7 @@ bool ESP32ImprovComponent::check_identify_() {
if (identify) { if (identify) {
uint32_t time = now % 1000; uint32_t time = now % 1000;
if (time < 600 && time % 200 < 100) { this->set_status_indicator_state_(time < 600 && time % 200 < 100);
this->status_indicator_->turn_on();
} else {
this->status_indicator_->turn_off();
}
} }
return identify; return identify;
} }
@ -174,6 +189,25 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
if (state != improv::STATE_STOPPED) if (state != improv::STATE_STOPPED)
this->status_->notify(); this->status_->notify();
} }
std::vector<uint8_t> service_data(8, 0);
service_data[0] = 0x77; // PR
service_data[1] = 0x46; // IM
service_data[2] = static_cast<uint8_t>(state);
uint8_t capabilities = 0x00;
#ifdef USE_OUTPUT
if (this->status_indicator_ != nullptr)
capabilities |= improv::CAPABILITY_IDENTIFY;
#endif
service_data[3] = capabilities;
service_data[4] = 0x00; // Reserved
service_data[5] = 0x00; // Reserved
service_data[6] = 0x00; // Reserved
service_data[7] = 0x00; // Reserved
esp32_ble::global_ble->get_advertising()->set_service_data(service_data);
esp32_ble::global_ble->get_advertising()->start();
} }
void ESP32ImprovComponent::set_error_(improv::Error error) { void ESP32ImprovComponent::set_error_(improv::Error error) {
@ -213,8 +247,12 @@ float ESP32ImprovComponent::get_setup_priority() const { return setup_priority::
void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 Improv:"); ESP_LOGCONFIG(TAG, "ESP32 Improv:");
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_); LOG_BINARY_SENSOR(" ", "Authorizer", this->authorizer_);
#endif
#ifdef USE_OUTPUT
ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr)); ESP_LOGCONFIG(TAG, " Status Indicator: '%s'", YESNO(this->status_indicator_ != nullptr));
#endif
} }
void ESP32ImprovComponent::process_incoming_data_() { void ESP32ImprovComponent::process_incoming_data_() {
@ -273,8 +311,10 @@ void ESP32ImprovComponent::process_incoming_data_() {
void ESP32ImprovComponent::on_wifi_connect_timeout_() { void ESP32ImprovComponent::on_wifi_connect_timeout_() {
this->set_error_(improv::ERROR_UNABLE_TO_CONNECT); this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
this->set_state_(improv::STATE_AUTHORIZED); this->set_state_(improv::STATE_AUTHORIZED);
#ifdef USE_BINARY_SENSOR
if (this->authorizer_ != nullptr) if (this->authorizer_ != nullptr)
this->authorized_start_ = millis(); this->authorized_start_ = millis();
#endif
ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network"); ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
wifi::global_wifi_component->clear_sta(); wifi::global_wifi_component->clear_sta();
} }

View file

@ -1,14 +1,22 @@
#pragma once #pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/wifi/wifi_component.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/components/esp32_ble_server/ble_characteristic.h"
#include "esphome/components/esp32_ble_server/ble_server.h"
#include "esphome/components/wifi/wifi_component.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_OUTPUT
#include "esphome/components/output/binary_output.h"
#endif
#include <vector> #include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -34,8 +42,12 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
void stop() override; void stop() override;
bool is_active() const { return this->state_ != improv::STATE_STOPPED; } bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
#ifdef USE_BINARY_SENSOR
void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; } void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; }
#endif
#ifdef USE_OUTPUT
void set_status_indicator(output::BinaryOutput *status_indicator) { this->status_indicator_ = status_indicator; } void set_status_indicator(output::BinaryOutput *status_indicator) { this->status_indicator_ = status_indicator; }
#endif
void set_identify_duration(uint32_t identify_duration) { this->identify_duration_ = identify_duration; } void set_identify_duration(uint32_t identify_duration) { this->identify_duration_ = identify_duration; }
void set_authorized_duration(uint32_t authorized_duration) { this->authorized_duration_ = authorized_duration; } void set_authorized_duration(uint32_t authorized_duration) { this->authorized_duration_ = authorized_duration; }
@ -58,12 +70,19 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
BLECharacteristic *rpc_response_; BLECharacteristic *rpc_response_;
BLECharacteristic *capabilities_; BLECharacteristic *capabilities_;
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *authorizer_{nullptr}; binary_sensor::BinarySensor *authorizer_{nullptr};
#endif
#ifdef USE_OUTPUT
output::BinaryOutput *status_indicator_{nullptr}; output::BinaryOutput *status_indicator_{nullptr};
#endif
improv::State state_{improv::STATE_STOPPED}; improv::State state_{improv::STATE_STOPPED};
improv::Error error_state_{improv::ERROR_NONE}; improv::Error error_state_{improv::ERROR_NONE};
bool status_indicator_state_{false};
void set_status_indicator_state_(bool state);
void set_state_(improv::State state); void set_state_(improv::State state);
void set_error_(improv::Error error); void set_error_(improv::Error error);
void send_response_(std::vector<uint8_t> &response); void send_response_(std::vector<uint8_t> &response);

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

@ -8,12 +8,15 @@ from esphome.const import (
CONF_CHANNEL, CONF_CHANNEL,
CONF_SPEED, CONF_SPEED,
CONF_DIRECTION, CONF_DIRECTION,
CONF_ADDRESS,
) )
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@max246"] CODEOWNERS = ["@max246"]
MULTI_CONF = True
grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng") grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng")
GROVE_TB6612FNG = grove_tb6612fng_ns.class_( GROVE_TB6612FNG = grove_tb6612fng_ns.class_(
"GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice "GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice
@ -33,6 +36,9 @@ GROVETB6612FNGMotorStandbyAction = grove_tb6612fng_ns.class_(
GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_( GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorNoStandbyAction", automation.Action "GROVETB6612FNGMotorNoStandbyAction", automation.Action
) )
GROVETB6612FNGMotorChangeAddressAction = grove_tb6612fng_ns.class_(
"GROVETB6612FNGMotorChangeAddressAction", automation.Action
)
DIRECTION_TYPE = { DIRECTION_TYPE = {
"FORWARD": 1, "FORWARD": 1,
@ -150,3 +156,22 @@ async def grove_tb6612fng_no_standby_to_code(config, action_id, template_arg, ar
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
return var return var
@automation.register_action(
"grove_tb6612fng.change_address",
GROVETB6612FNGMotorChangeAddressAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG),
cv.Required(CONF_ADDRESS): cv.i2c_address,
}
),
)
async def grove_tb6612fng_change_address_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_channel = await cg.templatable(config[CONF_ADDRESS], args, int)
cg.add(var.set_address(template_channel))
return var

View file

@ -84,8 +84,7 @@ class GroveMotorDriveTB6612FNG : public Component, public i2c::I2CDevice {
*************************************************************/ *************************************************************/
void set_i2c_addr(uint8_t addr); void set_i2c_addr(uint8_t addr);
/************************************************************* /***********************************change_address
Description
Drive a motor. Drive a motor.
Parameter Parameter
chl: MOTOR_CHA or MOTOR_CHB chl: MOTOR_CHA or MOTOR_CHB
@ -204,5 +203,13 @@ class GROVETB6612FNGMotorNoStandbyAction : public Action<Ts...>, public Parented
void play(Ts... x) override { this->parent_->not_standby(); } void play(Ts... x) override { this->parent_->not_standby(); }
}; };
template<typename... Ts>
class GROVETB6612FNGMotorChangeAddressAction : public Action<Ts...>, public Parented<GroveMotorDriveTB6612FNG> {
public:
TEMPLATABLE_VALUE(uint8_t, address)
void play(Ts... x) override { this->parent_->set_i2c_addr(this->address_.value(x...)); }
};
} // namespace grove_tb6612fng } // namespace grove_tb6612fng
} // namespace esphome } // namespace esphome

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

@ -76,7 +76,7 @@ void HTU21DComponent::update() {
if (this->humidity_ != nullptr) if (this->humidity_ != nullptr)
this->humidity_->publish_state(humidity); this->humidity_->publish_state(humidity);
if (this->heater_ != nullptr) if (this->heater_ != nullptr)
this->heater_->publish_state(humidity); this->heater_->publish_state(heater_level);
this->status_clear_warning(); this->status_clear_warning();
} }

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

@ -37,6 +37,8 @@ void I2SAudioMicrophone::setup() {
void I2SAudioMicrophone::start() { void I2SAudioMicrophone::start() {
if (this->is_failed()) if (this->is_failed())
return; return;
if (this->state_ == microphone::STATE_RUNNING)
return; // Already running
this->state_ = microphone::STATE_STARTING; this->state_ = microphone::STATE_STARTING;
} }
void I2SAudioMicrophone::start_() { void I2SAudioMicrophone::start_() {
@ -66,8 +68,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 +80,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 +111,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;
@ -151,17 +158,24 @@ void I2SAudioSpeaker::watch_() {
if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) { if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
switch (event.type) { switch (event.type) {
case TaskEventType::STARTING: case TaskEventType::STARTING:
ESP_LOGD(TAG, "Starting I2S Audio Speaker");
break;
case TaskEventType::STARTED: case TaskEventType::STARTED:
ESP_LOGD(TAG, "Started I2S Audio Speaker");
break;
case TaskEventType::STOPPING: case TaskEventType::STOPPING:
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
break; break;
case TaskEventType::PLAYING: case TaskEventType::PLAYING:
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_);
ESP_LOGD(TAG, "Stopped I2S Audio Speaker");
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 +191,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

@ -251,7 +251,7 @@ async def component_to_code(config):
# setup board config # setup board config
cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_LIBRETINY") cg.add_build_flag("-DUSE_LIBRETINY")
cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID]}") cg.add_build_flag(f"-DUSE_{config[CONF_COMPONENT_ID].upper()}")
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}") cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]]) cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])

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_cmp(&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

@ -106,4 +106,5 @@ async def output_set_level_to_code(config, action_id, template_arg, args):
async def to_code(config): async def to_code(config):
cg.add_define("USE_OUTPUT")
cg.add_global(output_ns.using) cg.add_global(output_ns.using)

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

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "prometheus_handler.h" #include "prometheus_handler.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
@ -89,7 +87,7 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
stream->print(obj->get_unit_of_measurement().c_str()); stream->print(obj->get_unit_of_measurement().c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
stream->print('\n'); stream->print(F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_sensor_failed{id=\"")); stream->print(F("esphome_sensor_failed{id=\""));
@ -124,7 +122,7 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print('\n'); stream->print(F("\n"));
} else { } else {
// Invalid state // Invalid state
stream->print(F("esphome_binary_sensor_failed{id=\"")); stream->print(F("esphome_binary_sensor_failed{id=\""));
@ -158,7 +156,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) {
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print('\n'); stream->print(F("\n"));
// Speed if available // Speed if available
if (obj->get_traits().supports_speed()) { if (obj->get_traits().supports_speed()) {
stream->print(F("esphome_fan_speed{id=\"")); stream->print(F("esphome_fan_speed{id=\""));
@ -167,7 +165,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) {
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->speed); stream->print(obj->speed);
stream->print('\n'); stream->print(F("\n"));
} }
// Oscillation if available // Oscillation if available
if (obj->get_traits().supports_oscillation()) { if (obj->get_traits().supports_oscillation()) {
@ -177,7 +175,7 @@ void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) {
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->oscillating); stream->print(obj->oscillating);
stream->print('\n'); stream->print(F("\n"));
} }
} }
#endif #endif
@ -281,7 +279,7 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->position); stream->print(obj->position);
stream->print('\n'); stream->print(F("\n"));
if (obj->get_traits().get_supports_tilt()) { if (obj->get_traits().get_supports_tilt()) {
stream->print(F("esphome_cover_tilt{id=\"")); stream->print(F("esphome_cover_tilt{id=\""));
stream->print(relabel_id_(obj).c_str()); stream->print(relabel_id_(obj).c_str());
@ -289,7 +287,7 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->tilt); stream->print(obj->tilt);
stream->print('\n'); stream->print(F("\n"));
} }
} else { } else {
// Invalid state // Invalid state
@ -322,7 +320,7 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print('\n'); stream->print(F("\n"));
} }
#endif #endif
@ -346,11 +344,9 @@ void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj)
stream->print(relabel_name_(obj).c_str()); stream->print(relabel_name_(obj).c_str());
stream->print(F("\"} ")); stream->print(F("\"} "));
stream->print(obj->state); stream->print(obj->state);
stream->print('\n'); stream->print(F("\n"));
} }
#endif #endif
} // namespace prometheus } // namespace prometheus
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

View file

@ -1,14 +1,12 @@
#pragma once #pragma once
#ifdef USE_ARDUINO
#include <map> #include <map>
#include <utility> #include <utility>
#include "esphome/core/entity_base.h"
#include "esphome/components/web_server_base/web_server_base.h" #include "esphome/components/web_server_base/web_server_base.h"
#include "esphome/core/controller.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
namespace esphome { namespace esphome {
namespace prometheus { namespace prometheus {
@ -119,5 +117,3 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
} // namespace prometheus } // namespace prometheus
} // namespace esphome } // namespace esphome
#endif // USE_ARDUINO

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

@ -11,6 +11,8 @@ static const uint8_t SM10BIT_ADDR_START_3CH = 0x8;
static const uint8_t SM10BIT_ADDR_START_2CH = 0x10; static const uint8_t SM10BIT_ADDR_START_2CH = 0x10;
static const uint8_t SM10BIT_ADDR_START_5CH = 0x18; static const uint8_t SM10BIT_ADDR_START_5CH = 0x18;
static const uint8_t SM10BIT_DELAY = 2;
// Power current values // Power current values
// HEX | Binary | RGB level | White level | Config value // HEX | Binary | RGB level | White level | Config value
// 0x0 | 0000 | RGB 10mA | CW 5mA | 0 // 0x0 | 0000 | RGB 10mA | CW 5mA | 0
@ -37,10 +39,13 @@ void Sm10BitBase::loop() {
uint8_t data[12]; uint8_t data[12];
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) { this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
// Off / Sleep
data[0] = this->model_id_ + SM10BIT_ADDR_STANDBY;
for (int i = 1; i < 12; i++) for (int i = 1; i < 12; i++)
data[i] = 0; data[i] = 0;
// First turn all channels off
data[0] = this->model_id_ + SM10BIT_ADDR_START_5CH;
this->write_buffer_(data, 12);
// Then sleep
data[0] = this->model_id_ + SM10BIT_ADDR_STANDBY;
this->write_buffer_(data, 12); this->write_buffer_(data, 12);
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 && } else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) { (this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
@ -84,28 +89,42 @@ void Sm10BitBase::set_channel_value_(uint8_t channel, uint16_t value) {
this->pwm_amounts_[channel] = value; this->pwm_amounts_[channel] = value;
} }
void Sm10BitBase::write_bit_(bool value) { void Sm10BitBase::write_bit_(bool value) {
this->clock_pin_->digital_write(false);
this->data_pin_->digital_write(value); this->data_pin_->digital_write(value);
delayMicroseconds(SM10BIT_DELAY);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(SM10BIT_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(SM10BIT_DELAY);
} }
void Sm10BitBase::write_byte_(uint8_t data) { void Sm10BitBase::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);
} }
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(SM10BIT_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(SM10BIT_DELAY);
this->data_pin_->pin_mode(gpio::FLAG_OUTPUT);
} }
void Sm10BitBase::write_buffer_(uint8_t *buffer, uint8_t size) { void Sm10BitBase::write_buffer_(uint8_t *buffer, uint8_t size) {
this->data_pin_->digital_write(false); this->data_pin_->digital_write(false);
delayMicroseconds(SM10BIT_DELAY);
this->clock_pin_->digital_write(false);
delayMicroseconds(SM10BIT_DELAY);
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]);
} }
this->clock_pin_->digital_write(false);
this->clock_pin_->digital_write(true); this->clock_pin_->digital_write(true);
delayMicroseconds(SM10BIT_DELAY);
this->data_pin_->digital_write(true); this->data_pin_->digital_write(true);
delayMicroseconds(SM10BIT_DELAY);
} }
} // namespace sm10bit_base } // namespace sm10bit_base

View file

@ -88,11 +88,6 @@ uint64_t bytes_to_uint(const bytes &buffer) {
for (auto const value : buffer) { for (auto const value : buffer) {
val = (val << 8) + value; val = (val << 8) + value;
} }
// Some smart meters send 24 bit signed integers. Sign extend to 64 bit if the
// 24 bit value is negative.
if (buffer.size() == 3 && buffer[0] & 0x80) {
val |= 0xFFFFFFFFFF000000;
}
return val; return val;
} }
@ -100,19 +95,15 @@ int64_t bytes_to_int(const bytes &buffer) {
uint64_t tmp = bytes_to_uint(buffer); uint64_t tmp = bytes_to_uint(buffer);
int64_t val; int64_t val;
switch (buffer.size()) { // sign extension for abbreviations of leading ones (e.g. 3 byte transmissions, see 6.2.2 of SML protocol definition)
case 1: // int8 // see https://stackoverflow.com/questions/42534749/signed-extension-from-24-bit-to-32-bit-in-c
val = (int8_t) tmp; if (buffer.size() < 8) {
break; const int bits = buffer.size() * 8;
case 2: // int16 const uint64_t m = 1u << (bits - 1);
val = (int16_t) tmp; tmp = (tmp ^ m) - m;
break;
case 4: // int32
val = (int32_t) tmp;
break;
default: // int64
val = (int64_t) tmp;
} }
val = (int64_t) tmp;
return val; return val;
} }

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,326 @@ 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", 2000, [this]() { this->speaker_->stop(); });
} else {
ESP_LOGW(TAG, "Speaker buffer full.");
}
}
if (this->wait_for_stream_end_) {
this->cancel_timeout("playing");
break; // We dont want to timeout here as the STREAM_END event will take care of that.
}
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", 2000, [this]() {
if (this->continuous_) { this->cancel_timeout("speaker-timeout");
this->request_start(true); this->set_state_(State::IDLE, State::IDLE);
}
}); });
}
break;
}
case State::RESPONSE_FINISHED: {
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
this->speaker_->stop();
this->cancel_timeout("speaker-timeout");
this->cancel_timeout("playing");
this->speaker_buffer_size_ = 0;
this->speaker_buffer_index_ = 0;
memset(this->speaker_buffer_, 0, SPEAKER_BUFFER_SIZE);
}
#endif
this->wait_for_stream_end_ = false;
this->set_state_(State::IDLE, State::IDLE);
break;
}
default:
break;
}
} }
void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { static const LogString *voice_assistant_state_to_string(State state) {
ESP_LOGD(TAG, "Starting..."); switch (state) {
case State::IDLE:
return LOG_STR("IDLE");
case State::START_MICROPHONE:
return LOG_STR("START_MICROPHONE");
case State::STARTING_MICROPHONE:
return LOG_STR("STARTING_MICROPHONE");
case State::WAIT_FOR_VAD:
return LOG_STR("WAIT_FOR_VAD");
case State::WAITING_FOR_VAD:
return LOG_STR("WAITING_FOR_VAD");
case State::START_PIPELINE:
return LOG_STR("START_PIPELINE");
case State::STARTING_PIPELINE:
return LOG_STR("STARTING_PIPELINE");
case State::STREAMING_MICROPHONE:
return LOG_STR("STREAMING_MICROPHONE");
case State::STOP_MICROPHONE:
return LOG_STR("STOP_MICROPHONE");
case State::STOPPING_MICROPHONE:
return LOG_STR("STOPPING_MICROPHONE");
case State::AWAITING_RESPONSE:
return LOG_STR("AWAITING_RESPONSE");
case State::STREAMING_RESPONSE:
return LOG_STR("STREAMING_RESPONSE");
case State::RESPONSE_FINISHED:
return LOG_STR("RESPONSE_FINISHED");
default:
return LOG_STR("UNKNOWN");
}
};
void VoiceAssistant::set_state_(State state) {
State old_state = this->state_;
this->state_ = state;
ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(voice_assistant_state_to_string(old_state)),
LOG_STR_ARG(voice_assistant_state_to_string(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 %s", LOG_STR_ARG(voice_assistant_state_to_string(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 +399,91 @@ 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:
case State::RESPONSE_FINISHED:
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 +495,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 +519,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 +538,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 +573,28 @@ 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;
}
case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: {
this->wait_for_stream_end_ = true;
break;
}
case api::enums::VOICE_ASSISTANT_TTS_STREAM_END: {
this->set_state_(State::RESPONSE_FINISHED, State::IDLE);
break;
} }
default: default:
ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type);
break; break;
} }
} }

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