Merge branch 'dev' into dev

This commit is contained in:
Neil Ségard 2024-06-05 11:04:34 +02:00 committed by GitHub
commit e76b992978
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
137 changed files with 3257 additions and 944 deletions

View File

@ -96,12 +96,12 @@ jobs:
uses: docker/setup-qemu-action@v3.0.0
- name: Log in to docker hub
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@ -188,13 +188,13 @@ jobs:
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}

View File

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

View File

@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0
rev: 24.4.2
hooks:
- id: black
args:

View File

@ -167,7 +167,8 @@ esphome/components/homeassistant/* @OttoWinter
esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
esphome/components/host/* @esphome/core
esphome/components/host/* @clydebarrow @esphome/core
esphome/components/host/time/* @clydebarrow
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M
esphome/components/htu31d/* @betterengineering
@ -210,6 +211,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny
esphome/components/ltr_als_ps/* @latonita
esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger
@ -414,7 +416,7 @@ esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @willwill2will54
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra

View File

@ -100,6 +100,9 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini --libraries
# Avoid unsafe git error when container user and file config volume permissions don't match
RUN git config --system --add safe.directory '/config/*'
# ======================= docker-type image =======================
FROM base AS docker

View File

@ -4,11 +4,11 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
ICON_ARROW_EXPAND_VERTICAL,
DEVICE_CLASS_DISTANCE,
UNIT_MILLIMETER,
)
CODEOWNERS = ["@TH-Braemer"]
DEPENDENCIES = ["uart"]
UNIT_MILLIMETERS = "mm"
a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw")
A02yyuwComponent = a02yyuw_ns.class_(
@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_(
CONFIG_SCHEMA = sensor.sensor_schema(
A02yyuwComponent,
unit_of_measurement=UNIT_MILLIMETERS,
unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_ARROW_EXPAND_VERTICAL,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,

View File

@ -11,6 +11,8 @@
#include "ade7880_registers.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace ade7880 {
@ -156,7 +158,7 @@ void ADE7880::update() {
});
}
ESP_LOGD(TAG, "update took %u ms", millis() - start);
ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start);
}
void ADE7880::dump_config() {
@ -176,9 +178,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
}
@ -192,9 +194,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
}
@ -208,9 +210,9 @@ void ADE7880::dump_config() {
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration);
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
}
@ -218,7 +220,7 @@ void ADE7880::dump_config() {
ESP_LOGCONFIG(TAG, " Neutral:");
LOG_SENSOR(" ", "Current", this->channel_n_->current);
ESP_LOGCONFIG(TAG, " Calibration:");
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration);
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration);
}
LOG_I2C_DEVICE(this);

View File

@ -1,6 +1,8 @@
#include "ade7953_base.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace ade7953_base {
@ -105,7 +107,7 @@ void ADE7953::update() {
this->last_update_ = now;
// prevent DIV/0
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf);
ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf);
}
// Apparent power

View File

@ -1,5 +1,7 @@
#include "ags10.h"
#include <cinttypes>
namespace esphome {
namespace ags10 {
static const char *const TAG = "ags10";
@ -35,7 +37,7 @@ void AGS10Component::setup() {
auto resistance = this->read_resistance_();
if (resistance) {
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance);
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance);
if (this->resistance_ != nullptr) {
this->resistance_->publish_state(*resistance);
}

View File

@ -1,5 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import web_server
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.core import CORE, coroutine_with_priority
@ -8,6 +9,7 @@ from esphome.const import (
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_CODE,
CONF_WEB_SERVER_ID,
)
from esphome.cpp_helpers import setup_entity
@ -76,6 +78,8 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
)
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
web_server.WEBSERVER_SORTING_SCHEMA
).extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
@ -185,6 +189,9 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_alarm_control_panel(var, config):

View File

@ -1517,6 +1517,25 @@ message VoiceAssistantAudio {
bool end = 2;
}
enum VoiceAssistantTimerEvent {
VOICE_ASSISTANT_TIMER_STARTED = 0;
VOICE_ASSISTANT_TIMER_UPDATED = 1;
VOICE_ASSISTANT_TIMER_CANCELLED = 2;
VOICE_ASSISTANT_TIMER_FINISHED = 3;
}
message VoiceAssistantTimerEventResponse {
option (id) = 115;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT";
VoiceAssistantTimerEvent event_type = 1;
string timer_id = 2;
string name = 3;
uint32 total_seconds = 4;
uint32 seconds_left = 5;
bool is_active = 6;
}
// ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState {

View File

@ -1193,6 +1193,15 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
voice_assistant::global_voice_assistant->on_audio(msg);
}
};
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_timer_event(msg);
}
};
#endif

View File

@ -150,6 +150,7 @@ class APIConnection : public APIServerConnection {
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL

View File

@ -475,6 +475,22 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::VoiceAssistantTimerEvent>(enums::VoiceAssistantTimerEvent value) {
switch (value) {
case enums::VOICE_ASSISTANT_TIMER_STARTED:
return "VOICE_ASSISTANT_TIMER_STARTED";
case enums::VOICE_ASSISTANT_TIMER_UPDATED:
return "VOICE_ASSISTANT_TIMER_UPDATED";
case enums::VOICE_ASSISTANT_TIMER_CANCELLED:
return "VOICE_ASSISTANT_TIMER_CANCELLED";
case enums::VOICE_ASSISTANT_TIMER_FINISHED:
return "VOICE_ASSISTANT_TIMER_FINISHED";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
switch (value) {
case enums::ALARM_STATE_DISARMED:
@ -6857,6 +6873,82 @@ void VoiceAssistantAudio::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->event_type = value.as_enum<enums::VoiceAssistantTimerEvent>();
return true;
}
case 4: {
this->total_seconds = value.as_uint32();
return true;
}
case 5: {
this->seconds_left = value.as_uint32();
return true;
}
case 6: {
this->is_active = value.as_bool();
return true;
}
default:
return false;
}
}
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->timer_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
default:
return false;
}
}
void VoiceAssistantTimerEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::VoiceAssistantTimerEvent>(1, this->event_type);
buffer.encode_string(2, this->timer_id);
buffer.encode_string(3, this->name);
buffer.encode_uint32(4, this->total_seconds);
buffer.encode_uint32(5, this->seconds_left);
buffer.encode_bool(6, this->is_active);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantTimerEventResponse {\n");
out.append(" event_type: ");
out.append(proto_enum_to_string<enums::VoiceAssistantTimerEvent>(this->event_type));
out.append("\n");
out.append(" timer_id: ");
out.append("'").append(this->timer_id).append("'");
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" total_seconds: ");
sprintf(buffer, "%" PRIu32, this->total_seconds);
out.append(buffer);
out.append("\n");
out.append(" seconds_left: ");
sprintf(buffer, "%" PRIu32, this->seconds_left);
out.append(buffer);
out.append("\n");
out.append(" is_active: ");
out.append(YESNO(this->is_active));
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {

View File

@ -191,6 +191,12 @@ enum VoiceAssistantEvent : uint32_t {
VOICE_ASSISTANT_TTS_STREAM_START = 98,
VOICE_ASSISTANT_TTS_STREAM_END = 99,
};
enum VoiceAssistantTimerEvent : uint32_t {
VOICE_ASSISTANT_TIMER_STARTED = 0,
VOICE_ASSISTANT_TIMER_UPDATED = 1,
VOICE_ASSISTANT_TIMER_CANCELLED = 2,
VOICE_ASSISTANT_TIMER_FINISHED = 3,
};
enum AlarmControlPanelState : uint32_t {
ALARM_STATE_DISARMED = 0,
ALARM_STATE_ARMED_HOME = 1,
@ -1775,6 +1781,23 @@ class VoiceAssistantAudio : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class VoiceAssistantTimerEventResponse : public ProtoMessage {
public:
enums::VoiceAssistantTimerEvent event_type{};
std::string timer_id{};
std::string name{};
uint32_t total_seconds{0};
uint32_t seconds_left{0};
bool is_active{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public:
std::string object_id{};

View File

@ -484,6 +484,8 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud
return this->send_message_<VoiceAssistantAudio>(msg, 106);
}
#endif
#ifdef USE_VOICE_ASSISTANT
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) {
@ -1093,6 +1095,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
#endif
this->on_date_time_command_request(msg);
#endif
break;
}
case 115: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantTimerEventResponse msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_timer_event_response(msg);
#endif
break;
}

View File

@ -244,6 +244,9 @@ class APIServerConnectionBase : public ProtoService {
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif

View File

@ -105,7 +105,7 @@ class CustomAPIDevice {
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
*
* Usage:
*å
*
* ```cpp
* void setup() override {
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");

View File

@ -4,7 +4,7 @@ from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from esphome import automation, core
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DELAY,
CONF_DEVICE_CLASS,
@ -27,6 +27,7 @@ from esphome.const import (
CONF_TIMING,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_CARBON_MONOXIDE,
@ -385,70 +386,76 @@ def validate_click_timing(value):
return value
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTBinarySensorComponent
),
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
}
),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
BINARY_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTBinarySensorComponent
),
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
cv.Required(CONF_TIMING): cv.All(
[parse_multi_click_timing_str], validate_multi_click_timing
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(
CONF_INVALID_COOLDOWN, default="1s"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
validate_click_timing,
),
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DoubleClickTrigger
),
cv.Optional(
CONF_MIN_LENGTH, default="50ms"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_MAX_LENGTH, default="350ms"
): cv.positive_time_period_milliseconds,
}
),
validate_click_timing,
),
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
cv.Required(CONF_TIMING): cv.All(
[parse_multi_click_timing_str], validate_multi_click_timing
),
cv.Optional(
CONF_INVALID_COOLDOWN, default="1s"
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
)
_UNDEF = object()
@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_binary_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
@ -43,16 +44,20 @@ ButtonPressTrigger = button_ns.class_(
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
BUTTON_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
}
)
)
_UNDEF = object()
@ -92,6 +97,10 @@ async def setup_button_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_button(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.cpp_helpers import setup_entity
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ACTION_STATE_TOPIC,
CONF_AWAY,
@ -44,6 +44,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
@ -150,93 +151,97 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
),
)
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
{
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
}
),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
CLIMATE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
{
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
}
),
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
}
)
)
@ -403,6 +408,10 @@ async def setup_climate_core_(var, config):
trigger, [(ClimateCall.operator("ref"), "x")], conf
)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_climate(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_DEVICE_CLASS,
@ -16,6 +16,7 @@ from esphome.const import (
CONF_TILT_STATE_TOPIC,
CONF_STOP,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_TRIGGER_ID,
DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND,
@ -88,34 +89,38 @@ CoverClosedTrigger = cover_ns.class_(
CONF_ON_CLOSED = "on_closed"
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
}
COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
}
),
}
)
)
@ -132,6 +137,10 @@ async def setup_cover_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)

View File

@ -1,6 +1,7 @@
#include "ct_clamp_sensor.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include <cmath>
namespace esphome {
@ -37,8 +38,8 @@ void CTClampSensor::update() {
float rms_ac = 0;
if (rms_ac_squared > 0)
rms_ac = std::sqrt(rms_ac_squared);
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %" PRIu32 " different samples (%" PRIu32 " SPS)",
this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
this->publish_state(rms_ac);
});

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt, time
from esphome.components import mqtt, web_server, time
from esphome.const import (
CONF_ID,
CONF_ON_TIME,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_DATE,
CONF_DATETIME,
CONF_TIME,
@ -63,16 +64,20 @@ DATETIME_MODES = [
]
_DATETIME_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
}
),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
_DATETIME_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
}
),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
)
)
def date_schema(class_: MockObjClass) -> cv.Schema:
@ -128,6 +133,9 @@ async def setup_datetime_core_(var, config):
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)

View File

@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
void DebugComponent::get_device_info_(std::string &device_info) {
reset_reason = get_reset_reason_();
str::string reset_reason = get_reset_reason_();
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());

View File

@ -86,9 +86,14 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (this->model_ == DHT_MODEL_DHT11) {
delayMicroseconds(18000);
} else if (this->model_ == DHT_MODEL_SI7021) {
#ifdef USE_ESP8266
delayMicroseconds(500);
this->pin_->digital_write(true);
delayMicroseconds(40);
#else
delayMicroseconds(400);
this->pin_->digital_write(true);
#endif
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
delayMicroseconds(2000);
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {

View File

@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
from esphome.const import (
CONF_DOMAIN,
CONF_ID,
CONF_VALUE,
CONF_MANUAL_IP,
CONF_STATIC_IP,
CONF_TYPE,
@ -26,6 +27,8 @@ from esphome.const import (
CONF_INTERRUPT_PIN,
CONF_RESET_PIN,
CONF_SPI,
CONF_PAGE_ID,
CONF_ADDRESS,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.components.network import IPAddress
@ -36,11 +39,13 @@ DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["network"]
ethernet_ns = cg.esphome_ns.namespace("ethernet")
PHYRegister = ethernet_ns.struct("PHYRegister")
CONF_PHY_ADDR = "phy_addr"
CONF_MDC_PIN = "mdc_pin"
CONF_MDIO_PIN = "mdio_pin"
CONF_CLK_MODE = "clk_mode"
CONF_POWER_PIN = "power_pin"
CONF_PHY_REGISTERS = "phy_registers"
CONF_CLOCK_SPEED = "clock_speed"
@ -117,6 +122,13 @@ BASE_SCHEMA = cv.Schema(
}
).extend(cv.COMPONENT_SCHEMA)
PHY_REGISTER_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_int,
cv.Required(CONF_VALUE): cv.hex_int,
cv.Optional(CONF_PAGE_ID): cv.hex_int,
}
)
RMII_SCHEMA = BASE_SCHEMA.extend(
cv.Schema(
{
@ -127,6 +139,7 @@ RMII_SCHEMA = BASE_SCHEMA.extend(
),
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
}
)
)
@ -198,6 +211,15 @@ def manual_ip(config):
)
def phy_register(address: int, value: int, page: int):
return cg.StructInitializer(
PHYRegister,
("address", address),
("value", value),
("page", page),
)
@coroutine_with_priority(60.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
@ -225,6 +247,13 @@ async def to_code(config):
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
if CONF_POWER_PIN in config:
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
for register_value in config.get(CONF_PHY_REGISTERS, []):
reg = phy_register(
register_value.get(CONF_ADDRESS),
register_value.get(CONF_VALUE),
register_value.get(CONF_PAGE_ID),
)
cg.add(var.add_phy_register(reg))
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))

View File

@ -28,6 +28,13 @@ EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-
return; \
}
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
if ((err) != ESP_OK) { \
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
this->mark_failed(); \
return ret; \
}
EthernetComponent::EthernetComponent() { global_eth_component = this; }
void EthernetComponent::setup() {
@ -98,11 +105,15 @@ void EthernetComponent::setup() {
.post_cb = nullptr,
};
#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5)
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
#else
spi_device_handle_t spi_handle = nullptr;
err = spi_bus_add_device(host, &devcfg, &spi_handle);
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
#endif
w5500_config.int_gpio_num = this->interrupt_pin_;
phy_config.phy_addr = this->phy_addr_spi_;
phy_config.reset_gpio_num = this->reset_pin_;
@ -184,9 +195,9 @@ void EthernetComponent::setup() {
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
this->ksz8081_set_clock_reference_(mac);
}
if (this->type_ == ETHERNET_TYPE_RTL8201 && this->clk_mode_ == EMAC_CLK_EXT_IN) {
// Change in default behavior of RTL8201FI may require register setting to enable external clock
this->rtl8201_set_rmii_mode_(mac);
for (const auto &phy_register : this->phy_registers_) {
this->write_phy_register_(mac, phy_register);
}
#endif
@ -406,7 +417,7 @@ void EthernetComponent::start_connect_() {
global_eth_component->ipv6_count_ = 0;
#endif /* USE_NETWORK_IPV6 */
this->connect_begin_ = millis();
this->status_set_warning();
this->status_set_warning("waiting for IP configuration");
esp_err_t err;
err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
@ -494,22 +505,9 @@ void EthernetComponent::dump_connect_params_() {
}
#endif /* USE_NETWORK_IPV6 */
esp_err_t err;
uint8_t mac[6];
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, &mac);
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
eth_duplex_t duplex_mode;
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_DUPLEX_MODE error");
ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(duplex_mode == ETH_DUPLEX_FULL));
eth_speed_t speed;
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_SPEED error");
ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10);
ESP_LOGCONFIG(TAG, " MAC Address: %s", this->get_eth_mac_address_pretty().c_str());
ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL));
ESP_LOGCONFIG(TAG, " Link Speed: %u", this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
}
#ifdef USE_ETHERNET_SPI
@ -529,6 +527,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_
this->clk_mode_ = clk_mode;
this->clk_gpio_ = clk_gpio;
}
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
#endif
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
@ -542,6 +541,34 @@ std::string EthernetComponent::get_use_address() const {
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
esp_err_t err;
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
}
std::string EthernetComponent::get_eth_mac_address_pretty() {
uint8_t mac[6];
get_mac_address_raw(mac);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
eth_duplex_t EthernetComponent::get_duplex_mode() {
esp_err_t err;
eth_duplex_t duplex_mode;
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
return duplex_mode;
}
eth_speed_t EthernetComponent::get_link_speed() {
esp_err_t err;
eth_speed_t speed;
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
return speed;
}
bool EthernetComponent::powerdown() {
ESP_LOGI(TAG, "Powering down ethernet PHY");
if (this->phy_ == nullptr) {
@ -572,11 +599,11 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
* KSZ8081RNA:
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII 25 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
* KSZ8081RND:
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII 50 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII 25 MHz clock mode.
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
*/
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
phy_control_2 |= 1 << 7;
@ -587,44 +614,27 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
}
}
constexpr uint8_t RTL8201_RMSR_REG_ADDR = 0x10;
void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) {
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
esp_err_t err;
uint32_t phy_rmii_mode;
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x07);
ESPHL_ERROR_CHECK(err, "Setting Page 7 failed");
constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
/*
* RTL8201 RMII Mode Setting Register (RMSR)
* Page 7 Register 16
*
* bit 0 Reserved 0
* bit 1 Rg_rmii_rxdsel 1 (default)
* bit 2 Rg_rmii_rxdv_sel: 0 (default)
* bit 3 RMII Mode: 1 (RMII Mode)
* bit 4~7 Rg_rmii_rx_offset: 1111 (default)
* bit 8~11 Rg_rmii_tx_offset: 1111 (default)
* bit 12 Rg_rmii_clkdir: 1 (Input)
* bit 13~15 Reserved 000
*
* Binary: 0001 1111 1111 1010
* Hex: 0x1FFA
*
*/
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
}
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode);
ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address);
ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value);
err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA);
ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed");
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode);
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0);
ESPHL_ERROR_CHECK(err, "Setting Page 0 failed");
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
}
}
#endif

View File

@ -10,6 +10,7 @@
#include "esp_eth.h"
#include "esp_eth_mac.h"
#include "esp_netif.h"
#include "esp_mac.h"
namespace esphome {
namespace ethernet {
@ -34,6 +35,12 @@ struct ManualIP {
network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default.
};
struct PHYRegister {
uint32_t address;
uint32_t value;
uint32_t page;
};
enum class EthernetComponentState {
STOPPED,
CONNECTING,
@ -65,6 +72,7 @@ class EthernetComponent : public Component {
void set_mdc_pin(uint8_t mdc_pin);
void set_mdio_pin(uint8_t mdio_pin);
void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
void add_phy_register(PHYRegister register_value);
#endif
void set_type(EthernetType type);
void set_manual_ip(const ManualIP &manual_ip);
@ -73,6 +81,10 @@ class EthernetComponent : public Component {
network::IPAddress get_dns_address(uint8_t num);
std::string get_use_address() const;
void set_use_address(const std::string &use_address);
void get_eth_mac_address_raw(uint8_t *mac);
std::string get_eth_mac_address_pretty();
eth_duplex_t get_duplex_mode();
eth_speed_t get_link_speed();
bool powerdown();
protected:
@ -86,8 +98,8 @@ class EthernetComponent : public Component {
void dump_connect_params_();
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
/// @brief Set `RMII Mode Setting Register` for RTL8201.
void rtl8201_set_rmii_mode_(esp_eth_mac_t *mac);
/// @brief Set arbitratry PHY registers from config.
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
std::string use_address_;
#ifdef USE_ETHERNET_SPI
@ -106,6 +118,7 @@ class EthernetComponent : public Component {
uint8_t mdio_pin_{18};
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
std::vector<PHYRegister> phy_registers_{};
#endif
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
optional<ManualIP> manual_ip_{};

View File

@ -10,6 +10,7 @@ static const char *const TAG = "ethernet_info";
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); }
void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); }
} // namespace ethernet_info
} // namespace esphome

View File

@ -59,6 +59,13 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text
std::string last_results_;
};
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor {
public:
void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo-mac"; }
void dump_config() override;
};
} // namespace ethernet_info
} // namespace esphome

View File

@ -4,6 +4,7 @@ from esphome.components import text_sensor
from esphome.const import (
CONF_IP_ADDRESS,
CONF_DNS_ADDRESS,
CONF_MAC_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC,
)
@ -19,6 +20,10 @@ DNSAddressEthernetInfo = ethernet_info_ns.class_(
"DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
)
MACAddressEthernetInfo = ethernet_info_ns.class_(
"MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
@ -36,6 +41,9 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s")),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
),
}
)
@ -51,3 +59,6 @@ async def to_code(config):
if conf := config.get(CONF_DNS_ADDRESS):
dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS])
await cg.register_component(dns_info, config[CONF_DNS_ADDRESS])
if conf := config.get(CONF_MAC_ADDRESS):
mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS])
await cg.register_component(mac_info, config[CONF_MAC_ADDRESS])

View File

@ -2,10 +2,11 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_OSCILLATING,
CONF_OSCILLATION_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC,
@ -79,67 +80,75 @@ FanPresetSetTrigger = fan_ns.class_(
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
}
),
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
}
),
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
}
),
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger),
}
),
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
}
),
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
}
),
}
FAN_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
}
),
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
}
),
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FanDirectionSetTrigger
),
}
),
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FanOscillatingSetTrigger
),
}
),
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
}
),
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
}
),
}
)
)
_PRESET_MODES_SCHEMA = cv.All(
@ -209,6 +218,10 @@ async def setup_fan_core_(var, config):
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf)

View File

@ -244,7 +244,7 @@ void FeedbackCover::loop() {
// update current position at requested interval, regardless of who started the movement
// so that we also update UI if there was an external movement
// don´t save intermediate positions
// don't save intermediate positions
if (now - this->last_publish_time_ > this->update_interval_) {
this->publish_state(false);
this->last_publish_time_ = now;
@ -274,7 +274,7 @@ void FeedbackCover::control(const CoverCall &call) {
if (pos == this->position) {
// already at target,
// for covers with built in end stop, if we don´t have sensors we should send the command again
// for covers with built in end stop, if we don't have sensors we should send the command again
// to make sure the assumed state is not wrong
if (this->has_built_in_endstop_ && ((pos == COVER_OPEN
#ifdef USE_BINARY_SENSOR

View File

@ -377,7 +377,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
this->write((uint8_t) (wire_length >> 8));
this->write((uint8_t) (wire_length & 0xFF));
uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND;
uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
for (auto data : *p_data_buffer) {
this->write(data);
sum += data;
@ -541,34 +541,34 @@ void FingerprintGrowComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_);
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
} else {
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
}
LOG_UPDATE_INTERVAL(this);
if (this->fingerprint_count_sensor_) {
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
}
if (this->status_sensor_) {
LOG_SENSOR(" ", "Status", this->status_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
}
if (this->capacity_sensor_) {
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
}
if (this->security_level_sensor_) {
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
}
if (this->last_finger_id_sensor_) {
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
}
if (this->last_confidence_sensor_) {
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
}
}

View File

@ -1,8 +1,9 @@
from esphome import pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, touchscreen
from esphome.const import CONF_ID
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
from .. import ft5x06_ns
FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener")
@ -16,6 +17,7 @@ FT5x06Touchscreen = ft5x06_ns.class_(
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(FT5x06Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
}
).extend(i2c.i2c_device_schema(0x48))
@ -24,3 +26,7 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await i2c.register_i2c_device(var, config)
await touchscreen.register_touchscreen(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
pin = await cg.gpio_pin_expression(interrupt_pin)
cg.add(var.set_interrupt_pin(pin))

View File

@ -0,0 +1,102 @@
#include "ft5x06_touchscreen.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ft5x06 {
static const char *const TAG = "ft5x06.touchscreen";
void FT5x06Touchscreen::setup() {
ESP_LOGCONFIG(TAG, "Setting up FT5x06 Touchscreen...");
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
// wait 200ms after reset.
this->set_timeout(200, [this] { this->continue_setup_(); });
}
void FT5x06Touchscreen::continue_setup_() {
uint8_t data[4];
if (!this->set_mode_(FT5X06_OP_MODE))
return;
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
return;
switch (data[0]) {
case FT5X06_ID_1:
case FT5X06_ID_2:
case FT5X06_ID_3:
this->vendor_id_ = (VendorId) data[0];
ESP_LOGD(TAG, "Read vendor ID 0x%X", data[0]);
break;
default:
ESP_LOGE(TAG, "Unknown vendor ID 0x%X", data[0]);
this->mark_failed();
return;
}
// reading the chip registers to get max x/y does not seem to work.
if (this->display_ != nullptr) {
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width();
}
if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = this->display_->get_native_height();
}
}
ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen setup complete");
}
void FT5x06Touchscreen::update_touches() {
uint8_t touch_cnt;
uint8_t data[MAX_TOUCHES][6];
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
ESP_LOGW(TAG, "Failed to read status");
return;
}
if (touch_cnt == 0)
return;
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
ESP_LOGW(TAG, "Failed to read touch data");
return;
}
for (uint8_t i = 0; i != touch_cnt; i++) {
uint8_t status = data[i][0] >> 6;
uint8_t id = data[i][2] >> 3;
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
ESP_LOGD(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
if (status == 0 || status == 2) {
this->add_raw_touch_position_(id, x, y);
}
}
}
void FT5x06Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
ESP_LOGCONFIG(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
}
bool FT5x06Touchscreen::err_check_(i2c::ErrorCode err, const char *msg) {
if (err != i2c::ERROR_OK) {
this->mark_failed();
ESP_LOGE(TAG, "%s failed - err 0x%X", msg, err);
return false;
}
return true;
}
bool FT5x06Touchscreen::set_mode_(FTMode mode) {
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
}
} // namespace ft5x06
} // namespace esphome

View File

@ -3,14 +3,12 @@
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ft5x06 {
static const char *const TAG = "ft5x06.touchscreen";
enum VendorId {
FT5X06_ID_UNKNOWN = 0,
FT5X06_ID_1 = 0x51,
@ -39,91 +37,19 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public:
void setup() override {
esph_log_config(TAG, "Setting up FT5x06 Touchscreen...");
// wait 200ms after reset.
this->set_timeout(200, [this] { this->continue_setup_(); });
}
void setup() override;
void dump_config() override;
void update_touches() override;
void continue_setup_(void) {
uint8_t data[4];
if (!this->set_mode_(FT5X06_OP_MODE))
return;
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
return;
switch (data[0]) {
case FT5X06_ID_1:
case FT5X06_ID_2:
case FT5X06_ID_3:
this->vendor_id_ = (VendorId) data[0];
esph_log_d(TAG, "Read vendor ID 0x%X", data[0]);
break;
default:
esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]);
this->mark_failed();
return;
}
// reading the chip registers to get max x/y does not seem to work.
if (this->display_ != nullptr) {
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width();
}
if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = this->display_->get_native_height();
}
}
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
}
void update_touches() override {
uint8_t touch_cnt;
uint8_t data[MAX_TOUCHES][6];
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
esph_log_w(TAG, "Failed to read status");
return;
}
if (touch_cnt == 0)
return;
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
esph_log_w(TAG, "Failed to read touch data");
return;
}
for (uint8_t i = 0; i != touch_cnt; i++) {
uint8_t status = data[i][0] >> 6;
uint8_t id = data[i][2] >> 3;
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
if (status == 0 || status == 2) {
this->add_raw_touch_position_(id, x, y);
}
}
}
void dump_config() override {
esph_log_config(TAG, "FT5x06 Touchscreen:");
esph_log_config(TAG, " Address: 0x%02X", this->address_);
esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
}
void set_interrupt_pin(InternalGPIOPin *interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
protected:
bool err_check_(i2c::ErrorCode err, const char *msg) {
if (err != i2c::ERROR_OK) {
this->mark_failed();
esph_log_e(TAG, "%s failed - err 0x%X", msg, err);
return false;
}
return true;
}
bool set_mode_(FTMode mode) {
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
}
void continue_setup_();
bool err_check_(i2c::ErrorCode err, const char *msg);
bool set_mode_(FTMode mode);
VendorId vendor_id_{FT5X06_ID_UNKNOWN};
InternalGPIOPin *interrupt_pin_{nullptr};
};
} // namespace ft5x06

View File

@ -2,6 +2,8 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace he60r {
@ -124,10 +126,10 @@ void HE60rCover::process_rx_(uint8_t data) {
}
void HE60rCover::update_() {
if (toggles_needed_ != 0) {
if (this->toggles_needed_ != 0) {
if ((this->counter_++ & 0x3) == 0) {
toggles_needed_--;
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_);
this->toggles_needed_--;
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_);
this->write_byte(TOGGLE_BYTE);
} else {
this->write_byte(QUERY_BYTE);

View File

@ -16,7 +16,7 @@ from .const import KEY_HOST
# force import gpio to register pin schema
from .gpio import host_pin_to_code # noqa
CODEOWNERS = ["@esphome/core"]
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
AUTO_LOAD = ["network"]

View File

@ -0,0 +1,20 @@
import esphome.codegen as cg
from esphome.const import CONF_ID
import esphome.config_validation as cv
from esphome.components import time as time_
CODEOWNERS = ["@clydebarrow"]
time_ns = cg.esphome_ns.namespace("host")
HostTime = time_ns.class_("HostTime", time_.RealTimeClock)
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HostTime),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await time_.register_time(var, config)

View File

@ -0,0 +1,15 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace host {
class HostTime : public time::RealTimeClock {
public:
void update() override {}
};
} // namespace host
} // namespace esphome

View File

@ -12,6 +12,8 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace htu31d {
@ -204,7 +206,7 @@ uint32_t HTU31DComponent::read_serial_num_() {
return 0;
}
ESP_LOGD(TAG, "Found serial: 0x%X", serial);
ESP_LOGD(TAG, "Found serial: 0x%" PRIX32, serial);
return serial;
}

View File

@ -12,13 +12,13 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_CELSIUS,
UNIT_MILLIMETER,
ICON_THERMOMETER,
)
from . import RGModel, RG15Resolution, HydreonRGxxComponent
UNIT_INTENSITY = "intensity"
UNIT_MILLIMETERS = "mm"
UNIT_MILLIMETERS_PER_HOUR = "mm/h"
CONF_ACC = "acc"
@ -85,19 +85,19 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False),
cv.Optional(CONF_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
unit_of_measurement=UNIT_MILLIMETER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_PRECIPITATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
unit_of_measurement=UNIT_MILLIMETER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_PRECIPITATION,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLIMETERS,
unit_of_measurement=UNIT_MILLIMETER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_PRECIPITATION,
state_class=STATE_CLASS_TOTAL_INCREASING,

View File

@ -9,6 +9,10 @@ namespace i2s_audio {
static const char *const TAG = "i2s_audio";
#if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5)
static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :(
#endif
void I2SAudioComponent::setup() {
static i2s_port_t next_port_num = I2S_NUM_0;

View File

@ -57,7 +57,7 @@ void I2SAudioMicrophone::start_() {
.use_apll = this->use_apll_,
.tx_desc_auto_clear = false,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};

View File

@ -19,10 +19,27 @@ void I2SAudioSpeaker::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent));
if (this->buffer_queue_ == nullptr) {
ESP_LOGE(TAG, "Failed to create buffer queue");
this->mark_failed();
return;
}
this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent));
if (this->event_queue_ == nullptr) {
ESP_LOGE(TAG, "Failed to create event queue");
this->mark_failed();
return;
}
}
void I2SAudioSpeaker::start() { this->state_ = speaker::STATE_STARTING; }
void I2SAudioSpeaker::start() {
if (this->is_failed()) {
ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
return;
}
this->state_ = speaker::STATE_STARTING;
}
void I2SAudioSpeaker::start_() {
if (!this->parent_->try_lock()) {
return; // Waiting for another i2s component to return lock
@ -51,7 +68,7 @@ void I2SAudioSpeaker::player_task(void *params) {
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = I2S_PIN_NO_CHANGE,
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
};
#if SOC_I2S_SUPPORTS_DAC
@ -141,6 +158,8 @@ void I2SAudioSpeaker::player_task(void *params) {
}
void I2SAudioSpeaker::stop() {
if (this->is_failed())
return;
if (this->state_ == speaker::STATE_STOPPED)
return;
if (this->state_ == speaker::STATE_STARTING) {
@ -200,6 +219,10 @@ void I2SAudioSpeaker::loop() {
}
size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) {
if (this->is_failed()) {
ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup");
return 0;
}
if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
this->start();
}

View File

@ -109,6 +109,7 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
case logger::UART_SELECTION_USB_SERIAL_JTAG:
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
break;
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
default:

View File

@ -17,6 +17,7 @@
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32H2)
#include <driver/usb_serial_jtag.h>
#include <hal/usb_serial_jtag_ll.h>
#endif
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include <esp_private/usb_console.h>

View File

@ -483,7 +483,7 @@ bool INA2XX::read_power_w_(float &power_out) {
uint64_t power_reading{0};
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_POWER, 3, power_reading);
ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%d", OKFAILED(ret), (uint32_t) power_reading);
ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%" PRIu32, OKFAILED(ret), (uint32_t) power_reading);
if (ret) {
power_out = this->cfg_.power_coeff * this->current_lsb_ * (float) power_reading;
}
@ -503,8 +503,8 @@ bool INA2XX::read_energy_(double &joules_out, double &watt_hours_out) {
uint64_t previous_energy = this->energy_overflows_count_ * (((uint64_t) 1) << 40);
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_ENERGY, 5, joules_reading);
ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%d", OKFAILED(ret),
joules_reading, this->current_lsb_, this->energy_overflows_count_);
ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%" PRIu32,
OKFAILED(ret), joules_reading, this->current_lsb_, this->energy_overflows_count_);
if (ret) {
joules_out = this->cfg_.energy_coeff * this->current_lsb_ * (double) joules_reading + (double) previous_energy;
watt_hours_out = joules_out / 3600.0;
@ -528,7 +528,7 @@ bool INA2XX::read_charge_(double &coulombs_out, double &amp_hours_out) {
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_CHARGE, 5, raw);
coulombs_reading = this->two_complement_(raw, 40);
ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%d", ret, coulombs_reading,
ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%" PRIu32, ret, coulombs_reading,
this->charge_overflows_count_);
if (ret) {
coulombs_out = this->current_lsb_ * (double) coulombs_reading + (double) previous_charge;

View File

@ -2,8 +2,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2
namespace esphome {
@ -38,7 +36,7 @@ void Jsnsr04tComponent::check_buffer_() {
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
if (distance > 250) {
float meters = distance / 1000.0f;
ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters);
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.automation as auto
from esphome.components import mqtt, power_supply
from esphome.components import mqtt, power_supply, web_server
from esphome.const import (
CONF_COLOR_CORRECT,
CONF_DEFAULT_TRANSITION_LENGTH,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_GAMMA_CORRECT,
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_POWER_SUPPLY,
CONF_RESTORE_MODE,
CONF_ON_TURN_OFF,
@ -56,29 +57,35 @@ RESTORE_MODES = {
"RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON,
}
LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(LightState),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
}
),
cv.Optional(CONF_ON_STATE): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
}
),
}
LIGHT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(LightState),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTJSONLightComponent
),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.Optional(CONF_ON_TURN_ON): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
}
),
cv.Optional(CONF_ON_STATE): auto.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
}
),
}
)
)
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
@ -173,6 +180,10 @@ async def setup_light_core_(light_var, output_var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, light_var, config)
async def register_light(output_var, config):
light_var = cg.new_Pvariable(config[CONF_ID], output_var)

View File

@ -2,13 +2,14 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_ON_LOCK,
CONF_ON_UNLOCK,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
@ -30,20 +31,24 @@ LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
LOCK_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
}
)
)
@ -61,6 +66,10 @@ async def setup_lock_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -2,14 +2,15 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_AMBIENT_LIGHT,
CONF_GAIN,
CONF_ID,
CONF_LIGHT,
CONF_RESOLUTION,
UNIT_LUX,
ICON_BRIGHTNESS_5,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_LUX,
)
CODEOWNERS = ["@sjtrny"]
@ -21,7 +22,6 @@ LTR390Component = ltr390_ns.class_(
"LTR390Component", cg.PollingComponent, i2c.I2CDevice
)
CONF_AMBIENT_LIGHT = "ambient_light"
CONF_UV_INDEX = "uv_index"
CONF_UV = "uv"
CONF_WINDOW_CORRECTION_FACTOR = "window_correction_factor"

View File

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

View File

@ -0,0 +1,519 @@
#include "ltr_als_ps.h"
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
using esphome::i2c::ErrorCode;
namespace esphome {
namespace ltr_als_ps {
static const char *const TAG = "ltr_als_ps";
static const uint8_t MAX_TRIES = 5;
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {
size_t i = 0;
size_t idx = -1;
while (idx == -1 && i < size) {
if (array[i] == val) {
idx = i;
break;
}
i++;
}
if (idx == -1 || i + 1 >= size)
return val;
return array[i + 1];
}
template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) {
size_t i = size - 1;
size_t idx = -1;
while (idx == -1 && i > 0) {
if (array[i] == val) {
idx = i;
break;
}
i--;
}
if (idx == -1 || i == 0)
return val;
return array[i - 1];
}
static uint16_t get_itime_ms(IntegrationTime time) {
static const uint16_t ALS_INT_TIME[8] = {100, 50, 200, 400, 150, 250, 300, 350};
return ALS_INT_TIME[time & 0b111];
}
static uint16_t get_meas_time_ms(MeasurementRepeatRate rate) {
static const uint16_t ALS_MEAS_RATE[8] = {50, 100, 200, 500, 1000, 2000, 2000, 2000};
return ALS_MEAS_RATE[rate & 0b111];
}
static float get_gain_coeff(AlsGain gain) {
static const float ALS_GAIN[8] = {1, 2, 4, 8, 0, 0, 48, 96};
return ALS_GAIN[gain & 0b111];
}
static float get_ps_gain_coeff(PsGain gain) {
static const float PS_GAIN[4] = {16, 0, 32, 64};
return PS_GAIN[gain & 0b11];
}
void LTRAlsPsComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up LTR-303/329/55x/659");
// As per datasheet we need to wait at least 100ms after power on to get ALS chip responsive
this->set_timeout(100, [this]() { this->state_ = State::DELAYED_SETUP; });
}
void LTRAlsPsComponent::dump_config() {
auto get_device_type = [](LtrType typ) {
switch (typ) {
case LtrType::LTR_TYPE_ALS_ONLY:
return "ALS only";
case LtrType::LTR_TYPE_PS_ONLY:
return "PS only";
case LtrType::LTR_TYPE_ALS_AND_PS:
return "ALS + PS";
default:
return "Unknown";
}
};
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Device type: %s", get_device_type(this->ltr_type_));
if (this->is_als_()) {
ESP_LOGCONFIG(TAG, " Automatic mode: %s", ONOFF(this->automatic_mode_enabled_));
ESP_LOGCONFIG(TAG, " Gain: %.0fx", get_gain_coeff(this->gain_));
ESP_LOGCONFIG(TAG, " Integration time: %d ms", get_itime_ms(this->integration_time_));
ESP_LOGCONFIG(TAG, " Measurement repeat rate: %d ms", get_meas_time_ms(this->repeat_rate_));
ESP_LOGCONFIG(TAG, " Glass attenuation factor: %f", this->glass_attenuation_factor_);
LOG_SENSOR(" ", "ALS calculated lux", this->ambient_light_sensor_);
LOG_SENSOR(" ", "CH1 Infrared counts", this->infrared_counts_sensor_);
LOG_SENSOR(" ", "CH0 Visible+IR counts", this->full_spectrum_counts_sensor_);
LOG_SENSOR(" ", "Actual gain", this->actual_gain_sensor_);
}
if (this->is_ps_()) {
ESP_LOGCONFIG(TAG, " Proximity gain: %.0fx", get_ps_gain_coeff(this->ps_gain_));
ESP_LOGCONFIG(TAG, " Proximity cooldown time: %d s", this->ps_cooldown_time_s_);
ESP_LOGCONFIG(TAG, " Proximity high threshold: %d", this->ps_threshold_high_);
ESP_LOGCONFIG(TAG, " Proximity low threshold: %d", this->ps_threshold_low_);
LOG_SENSOR(" ", "Proximity counts", this->proximity_counts_sensor_);
}
LOG_UPDATE_INTERVAL(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with I2C LTR-303/329/55x/659 failed!");
}
}
void LTRAlsPsComponent::update() {
ESP_LOGV(TAG, "Updating");
if (this->is_ready() && this->state_ == State::IDLE) {
ESP_LOGV(TAG, "Initiating new data collection");
this->state_ = this->automatic_mode_enabled_ ? State::COLLECTING_DATA_AUTO : State::WAITING_FOR_DATA;
this->als_readings_.ch0 = 0;
this->als_readings_.ch1 = 0;
this->als_readings_.gain = this->gain_;
this->als_readings_.integration_time = this->integration_time_;
this->als_readings_.lux = 0;
this->als_readings_.number_of_adjustments = 0;
} else {
ESP_LOGV(TAG, "Component not ready yet");
}
}
void LTRAlsPsComponent::loop() {
ErrorCode err = i2c::ERROR_OK;
static uint8_t tries{0};
switch (this->state_) {
case State::DELAYED_SETUP:
err = this->write(nullptr, 0);
if (err != i2c::ERROR_OK) {
ESP_LOGV(TAG, "i2c connection failed");
this->mark_failed();
}
this->configure_reset_();
if (this->is_als_()) {
this->configure_als_();
this->configure_integration_time_(this->integration_time_);
}
if (this->is_ps_()) {
this->configure_ps_();
}
this->state_ = State::IDLE;
break;
case State::IDLE:
if (this->is_ps_()) {
check_and_trigger_ps_();
}
break;
case State::WAITING_FOR_DATA:
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
tries = 0;
ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
get_itime_ms(this->als_readings_.integration_time));
this->read_sensor_data_(this->als_readings_);
this->state_ = State::DATA_COLLECTED;
this->apply_lux_calculation_(this->als_readings_);
} else if (tries >= MAX_TRIES) {
ESP_LOGW(TAG, "Can't get data after several tries.");
tries = 0;
this->status_set_warning();
this->state_ = State::IDLE;
return;
} else {
tries++;
}
break;
case State::COLLECTING_DATA_AUTO:
case State::DATA_COLLECTED:
// first measurement in auto mode (COLLECTING_DATA_AUTO state) require device reconfiguration
if (this->state_ == State::COLLECTING_DATA_AUTO || this->are_adjustments_required_(this->als_readings_)) {
this->state_ = State::ADJUSTMENT_IN_PROGRESS;
ESP_LOGD(TAG, "Reconfiguring sensitivity: gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
get_itime_ms(this->als_readings_.integration_time));
this->configure_integration_time_(this->als_readings_.integration_time);
this->configure_gain_(this->als_readings_.gain);
// if sensitivity adjustment needed - need to wait for first data samples after setting new parameters
this->set_timeout(2 * get_meas_time_ms(this->repeat_rate_),
[this]() { this->state_ = State::WAITING_FOR_DATA; });
} else {
this->state_ = State::READY_TO_PUBLISH;
}
break;
case State::ADJUSTMENT_IN_PROGRESS:
// nothing to be done, just waiting for the timeout
break;
case State::READY_TO_PUBLISH:
this->publish_data_part_1_(this->als_readings_);
this->state_ = State::KEEP_PUBLISHING;
break;
case State::KEEP_PUBLISHING:
this->publish_data_part_2_(this->als_readings_);
this->status_clear_warning();
this->state_ = State::IDLE;
break;
default:
break;
}
}
void LTRAlsPsComponent::check_and_trigger_ps_() {
static uint32_t last_high_trigger_time{0};
static uint32_t last_low_trigger_time{0};
uint16_t ps_data = this->read_ps_data_();
uint32_t now = millis();
if (ps_data != this->ps_readings_) {
this->ps_readings_ = ps_data;
// Higher values - object is closer to sensor
if (ps_data > this->ps_threshold_high_ && now - last_high_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
last_high_trigger_time = now;
ESP_LOGV(TAG, "Proximity high threshold triggered. Value = %d, Trigger level = %d", ps_data,
this->ps_threshold_high_);
this->on_ps_high_trigger_callback_.call();
} else if (ps_data < this->ps_threshold_low_ && now - last_low_trigger_time >= this->ps_cooldown_time_s_ * 1000) {
last_low_trigger_time = now;
ESP_LOGV(TAG, "Proximity low threshold triggered. Value = %d, Trigger level = %d", ps_data,
this->ps_threshold_low_);
this->on_ps_low_trigger_callback_.call();
}
}
}
bool LTRAlsPsComponent::check_part_number_() {
uint8_t manuf_id = this->reg((uint8_t) CommandRegisters::MANUFAC_ID).get();
if (manuf_id != 0x05) { // 0x05 is Lite-On Semiconductor Corp. ID
ESP_LOGW(TAG, "Unknown manufacturer ID: 0x%02X", manuf_id);
this->mark_failed();
return false;
}
// Things getting not really funny here, we can't identify device type by part number ID
// ======================== ========= ===== =================
// Device Part ID Rev Capabilities
// ======================== ========= ===== =================
// Ltr-329/ltr-303 0x0a 0x00 Als 16b
// Ltr-553/ltr-556/ltr-556 0x09 0x02 Als 16b + Ps 11b diff nm sens
// Ltr-659 0x09 0x02 Ps 11b and ps gain
//
// There are other devices which might potentially work with default settings,
// but registers layout is different and we can't use them properly. For ex. ltr-558
PartIdRegister part_id{0};
part_id.raw = this->reg((uint8_t) CommandRegisters::PART_ID).get();
if (part_id.part_number_id != 0x0a && part_id.part_number_id != 0x09) {
ESP_LOGW(TAG, "Unknown part number ID: 0x%02X. It might not work properly.", part_id.part_number_id);
this->status_set_warning();
return true;
}
return true;
}
void LTRAlsPsComponent::configure_reset_() {
ESP_LOGV(TAG, "Resetting");
AlsControlRegister als_ctrl{0};
als_ctrl.sw_reset = true;
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
uint8_t tries = MAX_TRIES;
do {
ESP_LOGV(TAG, "Waiting for chip to reset");
delay(2);
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
} while (als_ctrl.sw_reset && tries--); // while sw reset bit is on - keep waiting
if (als_ctrl.sw_reset) {
ESP_LOGW(TAG, "Reset timed out");
}
}
void LTRAlsPsComponent::configure_als_() {
AlsControlRegister als_ctrl{0};
als_ctrl.sw_reset = false;
als_ctrl.active_mode = true;
als_ctrl.gain = this->gain_;
ESP_LOGV(TAG, "Setting active mode and gain reg 0x%02X", als_ctrl.raw);
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(5);
uint8_t tries = MAX_TRIES;
do {
ESP_LOGV(TAG, "Waiting for device to become active...");
delay(2);
als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
} while (!als_ctrl.active_mode && tries--); // while active mode is not set - keep waiting
if (!als_ctrl.active_mode) {
ESP_LOGW(TAG, "Failed to activate device");
}
}
void LTRAlsPsComponent::configure_ps_() {
PsMeasurementRateRegister ps_meas{0};
ps_meas.ps_measurement_rate = PsMeasurementRate::PS_MEAS_RATE_50MS;
this->reg((uint8_t) CommandRegisters::PS_MEAS_RATE) = ps_meas.raw;
PsControlRegister ps_ctrl{0};
ps_ctrl.ps_mode_active = true;
ps_ctrl.ps_mode_xxx = true;
this->reg((uint8_t) CommandRegisters::PS_CONTR) = ps_ctrl.raw;
}
uint16_t LTRAlsPsComponent::read_ps_data_() {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.ps_new_data || als_status.data_invalid) {
return this->ps_readings_;
}
uint8_t ps_low = this->reg((uint8_t) CommandRegisters::PS_DATA_0).get();
PsData1Register ps_high;
ps_high.raw = this->reg((uint8_t) CommandRegisters::PS_DATA_1).get();
uint16_t val = encode_uint16(ps_high.ps_data_high, ps_low);
if (ps_high.ps_saturation_flag) {
return 0x7ff; // full 11 bit range
}
return val;
}
void LTRAlsPsComponent::configure_gain_(AlsGain gain) {
AlsControlRegister als_ctrl{0};
als_ctrl.active_mode = true;
als_ctrl.gain = gain;
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
AlsControlRegister read_als_ctrl{0};
read_als_ctrl.raw = this->reg((uint8_t) CommandRegisters::ALS_CONTR).get();
if (read_als_ctrl.gain != gain) {
ESP_LOGW(TAG, "Failed to set gain. We will try one more time.");
this->reg((uint8_t) CommandRegisters::ALS_CONTR) = als_ctrl.raw;
delay(2);
}
}
void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) {
MeasurementRateRegister meas{0};
meas.measurement_repeat_rate = this->repeat_rate_;
meas.integration_time = time;
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
delay(2);
MeasurementRateRegister read_meas{0};
read_meas.raw = this->reg((uint8_t) CommandRegisters::MEAS_RATE).get();
if (read_meas.integration_time != time) {
ESP_LOGW(TAG, "Failed to set integration time. We will try one more time.");
this->reg((uint8_t) CommandRegisters::MEAS_RATE) = meas.raw;
delay(2);
}
}
DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.als_new_data)
return DataAvail::NO_DATA;
if (als_status.data_invalid) {
ESP_LOGW(TAG, "Data available but not valid");
return DataAvail::BAD_DATA;
}
ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain));
if (data.gain != als_status.gain) {
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
return DataAvail::BAD_DATA;
}
return DataAvail::DATA_OK;
}
void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) {
data.ch1 = 0;
data.ch0 = 0;
uint8_t ch1_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_0).get();
uint8_t ch1_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH1_1).get();
uint8_t ch0_0 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_0).get();
uint8_t ch0_1 = this->reg((uint8_t) CommandRegisters::ALS_DATA_CH0_1).get();
data.ch1 = encode_uint16(ch1_1, ch1_0);
data.ch0 = encode_uint16(ch0_1, ch0_0);
ESP_LOGV(TAG, "Got sensor data: CH1 = %d, CH0 = %d", data.ch1, data.ch0);
}
bool LTRAlsPsComponent::are_adjustments_required_(AlsReadings &data) {
if (!this->automatic_mode_enabled_)
return false;
if (data.number_of_adjustments > 15) {
// sometimes sensors fail to change sensitivity. this prevents us from infinite loop
ESP_LOGW(TAG, "Too many sensitivity adjustments done. Apparently, sensor reconfiguration fails. Stopping.");
return false;
}
data.number_of_adjustments++;
// Recommended thresholds as per datasheet
static const uint16_t LOW_INTENSITY_THRESHOLD = 1000;
static const uint16_t HIGH_INTENSITY_THRESHOLD = 30000;
static const AlsGain GAINS[GAINS_COUNT] = {GAIN_1, GAIN_2, GAIN_4, GAIN_8, GAIN_48, GAIN_96};
static const IntegrationTime INT_TIMES[TIMES_COUNT] = {
INTEGRATION_TIME_50MS, INTEGRATION_TIME_100MS, INTEGRATION_TIME_150MS, INTEGRATION_TIME_200MS,
INTEGRATION_TIME_250MS, INTEGRATION_TIME_300MS, INTEGRATION_TIME_350MS, INTEGRATION_TIME_400MS};
if (data.ch0 <= LOW_INTENSITY_THRESHOLD) {
AlsGain next_gain = get_next(GAINS, data.gain);
if (next_gain != data.gain) {
data.gain = next_gain;
ESP_LOGV(TAG, "Low illuminance. Increasing gain.");
return true;
}
IntegrationTime next_time = get_next(INT_TIMES, data.integration_time);
if (next_time != data.integration_time) {
data.integration_time = next_time;
ESP_LOGV(TAG, "Low illuminance. Increasing integration time.");
return true;
}
} else if (data.ch0 >= HIGH_INTENSITY_THRESHOLD) {
AlsGain prev_gain = get_prev(GAINS, data.gain);
if (prev_gain != data.gain) {
data.gain = prev_gain;
ESP_LOGV(TAG, "High illuminance. Decreasing gain.");
return true;
}
IntegrationTime prev_time = get_prev(INT_TIMES, data.integration_time);
if (prev_time != data.integration_time) {
data.integration_time = prev_time;
ESP_LOGV(TAG, "High illuminance. Decreasing integration time.");
return true;
}
} else {
ESP_LOGD(TAG, "Illuminance is sufficient.");
return false;
}
ESP_LOGD(TAG, "Can't adjust sensitivity anymore.");
return false;
}
void LTRAlsPsComponent::apply_lux_calculation_(AlsReadings &data) {
if ((data.ch0 == 0xFFFF) || (data.ch1 == 0xFFFF)) {
ESP_LOGW(TAG, "Sensors got saturated");
data.lux = 0.0f;
return;
}
if ((data.ch0 == 0x0000) && (data.ch1 == 0x0000)) {
ESP_LOGW(TAG, "Sensors blacked out");
data.lux = 0.0f;
return;
}
float ch0 = data.ch0;
float ch1 = data.ch1;
float ratio = ch1 / (ch0 + ch1);
float als_gain = get_gain_coeff(data.gain);
float als_time = ((float) get_itime_ms(data.integration_time)) / 100.0f;
float inv_pfactor = this->glass_attenuation_factor_;
float lux = 0.0f;
if (ratio < 0.45) {
lux = (1.7743 * ch0 + 1.1059 * ch1);
} else if (ratio < 0.64 && ratio >= 0.45) {
lux = (4.2785 * ch0 - 1.9548 * ch1);
} else if (ratio < 0.85 && ratio >= 0.64) {
lux = (0.5926 * ch0 + 0.1185 * ch1);
} else {
ESP_LOGW(TAG, "Impossible ch1/(ch0 + ch1) ratio");
lux = 0.0f;
}
lux = inv_pfactor * lux / als_gain / als_time;
data.lux = lux;
ESP_LOGV(TAG, "Lux calculation: ratio %.3f, gain %.0fx, int time %.1f, inv_pfactor %.3f, lux %.3f", ratio, als_gain,
als_time, inv_pfactor, lux);
}
void LTRAlsPsComponent::publish_data_part_1_(AlsReadings &data) {
if (this->proximity_counts_sensor_ != nullptr) {
this->proximity_counts_sensor_->publish_state(this->ps_readings_);
}
if (this->ambient_light_sensor_ != nullptr) {
this->ambient_light_sensor_->publish_state(data.lux);
}
if (this->infrared_counts_sensor_ != nullptr) {
this->infrared_counts_sensor_->publish_state(data.ch1);
}
if (this->full_spectrum_counts_sensor_ != nullptr) {
this->full_spectrum_counts_sensor_->publish_state(data.ch0);
}
}
void LTRAlsPsComponent::publish_data_part_2_(AlsReadings &data) {
if (this->actual_gain_sensor_ != nullptr) {
this->actual_gain_sensor_->publish_state(get_gain_coeff(data.gain));
}
if (this->actual_integration_time_sensor_ != nullptr) {
this->actual_integration_time_sensor_->publish_state(get_itime_ms(data.integration_time));
}
}
} // namespace ltr_als_ps
} // namespace esphome

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ enum MAX6956GPIORegisters {
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
MAX6956_CURRENT_START = 0x12, // Current054
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 411 (data bits D0D7)
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4-11 (data bits D0-D7)
};
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };

View File

@ -1,6 +1,8 @@
#include "mhz19.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace mhz19 {
@ -32,7 +34,7 @@ void MHZ19Component::update() {
uint32_t now_ms = millis();
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
if (now_ms < warmup_ms) {
ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000);
ESP_LOGW(TAG, "MHZ19 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
this->status_set_warning();
return;
}
@ -110,7 +112,7 @@ void MHZ19Component::dump_config() {
ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot");
}
ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_);
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
}
} // namespace mhz19

View File

@ -329,11 +329,14 @@ async def to_code(config):
file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE]
elif model_config[CONF_TYPE] == TYPE_LOCAL:
file = model_config[CONF_PATH]
file = Path(model_config[CONF_PATH])
elif model_config[CONF_TYPE] == TYPE_HTTP:
file = _compute_local_file_path(model_config) / "manifest.json"
else:
raise ValueError("Unsupported config type: {model_config[CONF_TYPE]}")
manifest, data = _load_model_data(file)
rhs = [HexInt(x) for x in data]

View File

@ -20,6 +20,7 @@
#include <tensorflow/lite/micro/micro_interpreter.h>
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
#include <cinttypes>
#include <cmath>
namespace esphome {
@ -316,7 +317,7 @@ float MicroWakeWord::perform_streaming_inference_() {
return false;
}
ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke));
ESP_LOGV(TAG, "Streaming Inference Latency=%" PRIu32 " ms", (millis() - prior_invoke));
TfLiteTensor *output = this->streaming_interpreter_->output(0);

View File

@ -6,7 +6,7 @@ namespace mitsubishi {
static const char *const TAG = "mitsubishi.climate";
const uint32_t MITSUBISHI_OFF = 0x00;
const uint8_t MITSUBISHI_OFF = 0x00;
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
const uint8_t MITSUBISHI_MODE_COOL = 0x18;
@ -109,8 +109,8 @@ void MitsubishiClimate::transmit_state() {
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
// Byte 16: Constant 0x00
// Byte 17: Checksum: SUM[Byte0...Byte16]
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (this->mode) {
case climate::CLIMATE_MODE_HEAT:
@ -249,7 +249,7 @@ void MitsubishiClimate::transmit_state() {
data->set_carrier_frequency(38000);
// repeat twice
for (uint16_t r = 0; r < 2; r++) {
for (uint8_t r = 0; r < 2; r++) {
// Header
data->mark(MITSUBISHI_HEADER_MARK);
data->space(MITSUBISHI_HEADER_SPACE);

View File

@ -12,6 +12,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
UNIT_CELSIUS,
UNIT_MILLIMETER,
STATE_CLASS_MEASUREMENT,
CONF_BATTERY_LEVEL,
DEVICE_CLASS_BATTERY,
@ -25,8 +26,6 @@ ICON_PROPANE_TANK = "mdi:propane-tank"
TANK_TYPE_CUSTOM = "CUSTOM"
UNIT_MILLIMETER = "mm"
def small_distance(value):
"""small_distance is stored in mm"""

View File

@ -12,6 +12,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
UNIT_CELSIUS,
UNIT_MILLIMETER,
STATE_CLASS_MEASUREMENT,
CONF_BATTERY_LEVEL,
DEVICE_CLASS_BATTERY,
@ -26,8 +27,6 @@ ICON_PROPANE_TANK = "mdi:propane-tank"
TANK_TYPE_CUSTOM = "CUSTOM"
UNIT_MILLIMETER = "mm"
def small_distance(value):
"""small_distance is stored in mm"""

View File

@ -100,7 +100,7 @@ def process_calibration(value):
elif isinstance(value, list):
if len(value) != 3:
raise cv.Invalid(
"SteinhartHart Calibration must consist of exactly three values"
"Steinhart-Hart Calibration must consist of exactly three values"
)
value = cv.Schema([validate_calibration_parameter])(value)
a, b, c = calc_steinhart_hart(value)

View File

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import web_server
from esphome.const import (
CONF_ABOVE,
CONF_BELOW,
@ -18,6 +19,7 @@ from esphome.const import (
CONF_VALUE,
CONF_OPERATION,
CONF_CYCLE,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@ -167,26 +169,30 @@ NUMBER_OPERATION_OPTIONS = {
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
validate_unit_of_measurement = cv.string_strict
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
NUMBER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_MODE, default="AUTO"): cv.enum(NUMBER_MODES, upper=True),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
)
)
_UNDEF = object()
@ -248,6 +254,10 @@ async def setup_number_core_(
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_number(
var, config, *, min_value: float, max_value: float, step: float

View File

@ -1769,7 +1769,17 @@ def aeha_dumper(var, config):
pass
@register_action("aeha", AEHAAction, AEHA_SCHEMA)
@register_action(
"aeha",
AEHAAction,
AEHA_SCHEMA.extend(
{
cv.Optional(CONF_CARRIER_FREQUENCY, default="38000Hz"): cv.All(
cv.frequency, cv.int_
),
}
),
)
async def aeha_action(var, config, args):
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
cg.add(var.set_address(template_))
@ -1777,6 +1787,8 @@ async def aeha_action(var, config, args):
config[CONF_DATA], args, cg.std_vector.template(cg.uint8)
)
cg.add(var.set_data(template_))
templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32)
cg.add(var.set_carrier_frequency(templ))
# Haier

View File

@ -4,6 +4,7 @@
#include "esphome/core/helpers.h"
#include "remote_base.h"
#include <array>
#include <cinttypes>
#include <utility>
#include <vector>
@ -144,7 +145,8 @@ class ABBWelcomeData {
std::string to_string(uint8_t max_print_bytes = 255) const {
std::string info;
if (this->is_valid()) {
info = str_sprintf(this->get_three_byte_address() ? "[%06X %s %06X] Type: %02X" : "[%04X %s %04X] Type: %02X",
info = str_sprintf(this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X"
: "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X",
this->get_source_address(), this->get_retransmission() ? "»" : ">",
this->get_destination_address(), this->get_message_type());
if (this->get_data_size())

View File

@ -16,7 +16,6 @@ static const uint16_t BIT_ZERO_LOW_US = BITWISE;
static const uint16_t TRAILER = BITWISE;
void AEHAProtocol::encode(RemoteTransmitData *dst, const AEHAData &data) {
dst->set_carrier_frequency(38000);
dst->reserve(2 + 32 + (data.data.size() * 2) + 1);
dst->item(HEADER_HIGH_US, HEADER_LOW_US);

View File

@ -30,12 +30,14 @@ template<typename... Ts> class AEHAAction : public RemoteTransmitterActionBase<T
public:
TEMPLATABLE_VALUE(uint16_t, address)
TEMPLATABLE_VALUE(std::vector<uint8_t>, data)
TEMPLATABLE_VALUE(uint32_t, carrier_frequency);
void set_data(const std::vector<uint8_t> &data) { data_ = data; }
void encode(RemoteTransmitData *dst, Ts... x) override {
AEHAData data{};
data.address = this->address_.value(x...);
data.data = this->data_.value(x...);
dst->set_carrier_frequency(this->carrier_frequency_.value(x...));
AEHAProtocol().encode(dst, data);
}
};

View File

@ -1,6 +1,8 @@
#include "byronsx_protocol.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace remote_base {
@ -57,7 +59,7 @@ void ByronSXProtocol::encode(RemoteTransmitData *dst, const ByronSXData &data) {
out_data <<= NBITS_COMMAND;
out_data |= data.command;
ESP_LOGV(TAG, "Send ByronSX: out_data %03x", out_data);
ESP_LOGV(TAG, "Send ByronSX: out_data %03" PRIx32, out_data);
// Initial Mark start bit
dst->mark(1 * BIT_TIME_US);
@ -90,13 +92,16 @@ optional<ByronSXData> ByronSXProtocol::decode(RemoteReceiveData src) {
return {};
}
ESP_LOGVV(TAG, "%3d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0),
src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8),
src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15),
src.peek(16), src.peek(17), src.peek(18), src.peek(19));
ESP_LOGVV(TAG,
"%3" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
" %" PRId32 " %" PRId32 " %" PRId32,
src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
ESP_LOGVV(TAG, " %d %d %d %d %d %d", src.peek(20), src.peek(21), src.peek(22), src.peek(23), src.peek(24),
src.peek(25));
ESP_LOGVV(TAG, " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, src.peek(20),
src.peek(21), src.peek(22), src.peek(23), src.peek(24), src.peek(25));
// Read data bits
uint32_t out_data = 0;
@ -107,10 +112,10 @@ optional<ByronSXData> ByronSXProtocol::decode(RemoteReceiveData src) {
} else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) {
out_data |= 0 << bit;
} else {
ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08x", bit, out_data);
ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08" PRIx32, bit, out_data);
return {};
}
ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08x", bit, out_data);
ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08" PRIx32, bit, out_data);
}
// last bit followed by a long space

View File

@ -1,6 +1,8 @@
#include "drayton_protocol.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace remote_base {
@ -151,12 +153,12 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
// Look for sync pulse, after. If sucessful index points to space of sync symbol
while (src.size() - src.get_index() >= MIN_RX_SRC) {
ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
src.peek(1));
ESP_LOGVV(TAG, "Decode Drayton: sync search %" PRIu32 ", %" PRId32 " %" PRId32, src.size() - src.get_index(),
src.peek(), src.peek(1));
if (src.peek_mark(2 * BIT_TIME_US) &&
(src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) {
src.advance(1);
ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index());
ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %" PRIu32, src.get_index());
break;
} else {
src.advance(2);
@ -174,14 +176,16 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
// Checks next bit to leave index pointing correctly
uint32_t out_data = 0;
uint8_t bit = NDATABITS - 1;
ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2));
ESP_LOGVV(TAG, "Decode Drayton: first bit %" PRId32 " %" PRId32 ", %" PRId32, src.peek(0), src.peek(1),
src.peek(2));
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
out_data |= 0 << bit;
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
out_data |= 1 << bit;
} else {
ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1));
ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
src.peek(1));
continue;
}
@ -202,7 +206,8 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
}
if (bit > 0) {
ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1));
ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
src.peek(1));
continue;
}
@ -214,7 +219,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
continue;
}
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data);
out.channel = (uint8_t) (out_data & 0x1F);
out_data >>= NBITS_CHANNEL;

View File

@ -52,7 +52,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
// Encrypted field
out_data = data.encrypted;
ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data);
ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04" PRIx32, out_data);
for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
if (out_data & mask) {
@ -68,7 +68,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
out_data = (data.command & 0x0f);
out_data <<= NBITS_SERIAL;
out_data |= data.address;
ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data);
ESP_LOGV(TAG, "Send Keeloq: Fixed data %04" PRIx32, out_data);
for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
if (out_data & mask) {
@ -111,21 +111,24 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
return {};
}
ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0),
src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8),
src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15),
src.peek(16), src.peek(17), src.peek(18), src.peek(19));
ESP_LOGVV(TAG,
"%2" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
" %" PRId32 " %" PRId32 " %" PRId32,
src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
// Check preamble bits
int8_t bit = NBITS_PREAMBLE - 1;
while (--bit >= 0) {
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
return {};
}
}
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
return {};
}
@ -137,11 +140,11 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
out_data |= 1 << bit;
} else {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
return {};
}
}
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data);
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08" PRIx32, bit, out_data);
out.encrypted = out_data;
// Read Serial Number and Button Status
@ -152,11 +155,11 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
out_data |= 1 << bit;
} else {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
return {};
}
}
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data);
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08" PRIx32, bit, out_data);
out.command = (out_data >> 28) & 0xf;
out.address = out_data & 0xfffffff;
@ -166,7 +169,7 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
out.vlow = true;
} else {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %" PRId32, src.peek());
return {};
}
@ -176,7 +179,7 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
} else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
out.repeat = true;
} else {
ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek());
ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %" PRId32, src.peek());
return {};
}

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_OPTION,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_CYCLE,
CONF_MODE,
CONF_OPERATION,
@ -47,16 +48,20 @@ SELECT_OPERATION_OPTIONS = {
}
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger),
}
),
}
SELECT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
cv.GenerateID(): cv.declare_id(Select),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SelectStateTrigger),
}
),
}
)
)
_UNDEF = object()
@ -99,6 +104,10 @@ async def setup_select_core_(var, config, *, options: list[str]):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_select(var, config, *, options: list[str]):
if not CORE.has_id(config[CONF_ID]):

View File

@ -3,7 +3,7 @@ import math
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ABOVE,
@ -31,6 +31,7 @@ from esphome.const import (
CONF_UNIT_OF_MEASUREMENT,
CONF_WINDOW_SIZE,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_FORCE_UPDATE,
CONF_VALUE,
CONF_MIN_VALUE,
@ -252,43 +253,49 @@ validate_accuracy_decimals = cv.int_
validate_icon = cv.icon
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
cv.GenerateID(): cv.declare_id(Sensor),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_STATE_CLASS): validate_state_class,
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
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."
),
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
cv.requires_component("mqtt"),
cv.Any(None, cv.positive_time_period_milliseconds),
),
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorRawStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
}
SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSensorComponent),
cv.GenerateID(): cv.declare_id(Sensor),
cv.Optional(CONF_UNIT_OF_MEASUREMENT): validate_unit_of_measurement,
cv.Optional(CONF_ACCURACY_DECIMALS): validate_accuracy_decimals,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_STATE_CLASS): validate_state_class,
cv.Optional(CONF_ENTITY_CATEGORY): sensor_entity_category,
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."
),
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
cv.requires_component("mqtt"),
cv.Any(None, cv.positive_time_period_milliseconds),
),
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
SensorRawStateTrigger
),
}
),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
},
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
),
}
)
)
_UNDEF = object()
@ -772,6 +779,10 @@ async def setup_sensor_core_(var, config):
else:
cg.add(mqtt_.set_expire_after(expire_after))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -1,16 +1,11 @@
#include "sntp_component.h"
#include "esphome/core/log.h"
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "lwip/apps/sntp.h"
#ifdef USE_ESP_IDF
#include "esp_sntp.h"
#endif
#endif
#ifdef USE_ESP8266
#elif USE_ESP8266
#include "sntp.h"
#endif
#ifdef USE_RP2040
#else
#include "lwip/apps/sntp.h"
#endif
@ -25,16 +20,15 @@ namespace sntp {
static const char *const TAG = "sntp";
void SNTPComponent::setup() {
#ifndef USE_HOST
ESP_LOGCONFIG(TAG, "Setting up SNTP...");
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
if (sntp_enabled()) {
sntp_stop();
#if defined(USE_ESP_IDF)
if (esp_sntp_enabled()) {
esp_sntp_stop();
}
sntp_setoperatingmode(SNTP_OPMODE_POLL);
#endif
#ifdef USE_ESP8266
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
#else
sntp_stop();
sntp_setoperatingmode(SNTP_OPMODE_POLL);
#endif
sntp_setservername(0, strdup(this->server_1_.c_str()));
@ -45,11 +39,10 @@ void SNTPComponent::setup() {
sntp_setservername(2, strdup(this->server_3_.c_str()));
}
#ifdef USE_ESP_IDF
sntp_set_sync_interval(this->get_update_interval());
esp_sntp_set_sync_interval(this->get_update_interval());
#endif
sntp_init();
#endif
}
void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SNTP Time:");
@ -59,7 +52,7 @@ void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
void SNTPComponent::update() {
#if !defined(USE_ESP_IDF) && !defined(USE_HOST)
#if !defined(USE_ESP_IDF)
// force resync
if (sntp_enabled()) {
sntp_stop();

View File

@ -2,24 +2,41 @@ from esphome.components import time as time_
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.core import CORE
from esphome.const import CONF_ID, CONF_SERVERS
from esphome.const import (
CONF_ID,
CONF_SERVERS,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
PLATFORM_BK72XX,
)
DEPENDENCIES = ["network"]
sntp_ns = cg.esphome_ns.namespace("sntp")
SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock)
DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"]
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SNTPComponent),
cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All(
cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3)
),
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
time_.TIME_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SNTPComponent),
cv.Optional(CONF_SERVERS, default=DEFAULT_SERVERS): cv.All(
cv.ensure_list(cv.Any(cv.domain, cv.hostname)), cv.Length(min=1, max=3)
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
PLATFORM_BK72XX,
PLATFORM_RTL87XX,
]
),
)
async def to_code(config):

View File

@ -469,7 +469,8 @@ class LWIPRawImpl : public Socket {
}
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
// return ::sendto(fd_, buf, len, flags, to, tolen);
return 0;
errno = ENOSYS;
return -1;
}
int setblocking(bool blocking) override {
if (pcb_ == nullptr) {

View File

@ -128,7 +128,8 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
// Expected acknowledgement from rf chip
uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
uint8_t buffer[sizeof(ref_buffer)] = {0};
uint32_t pos = 0, buf_len = sizeof(ref_buffer);
uint32_t pos = 0;
size_t buf_len = sizeof(ref_buffer);
// Update the reference checksum
this->populate_checksum_(ref_buffer, sizeof(ref_buffer));

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import Condition, maybe_simple_id
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -10,6 +10,7 @@ from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_RESTORE_MODE,
@ -64,22 +65,26 @@ SwitchTurnOffTrigger = switch_ns.class_(
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True)
_SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
cv.Optional(CONF_INVERTED): cv.boolean,
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
}
),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
_SWITCH_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
cv.Optional(CONF_INVERTED): cv.boolean,
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOnTrigger),
}
),
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
}
),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
}
)
)
_UNDEF = object()
@ -151,6 +156,10 @@ async def setup_switch_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))

View File

@ -2,13 +2,14 @@ from typing import Optional
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_ID,
CONF_MODE,
CONF_ON_VALUE,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_VALUE,
)
@ -38,17 +39,21 @@ TEXT_MODES = {
"PASSWORD": TextMode.TEXT_MODE_PASSWORD, # to be implemented for keys, passwords, etc.
}
TEXT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent),
cv.GenerateID(): cv.declare_id(Text),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger),
}
),
cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True),
}
TEXT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextComponent),
cv.GenerateID(): cv.declare_id(Text),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextStateTrigger),
}
),
cv.Required(CONF_MODE): cv.enum(TEXT_MODES, upper=True),
}
)
)
@ -77,6 +82,10 @@ async def setup_text_core_(
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text(
var,

View File

@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
@ -12,6 +12,7 @@ from esphome.const import (
CONF_ON_RAW_VALUE,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
CONF_STATE,
CONF_FROM,
CONF_TO,
@ -124,25 +125,31 @@ async def map_filter_to_code(config, filter_id):
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
cv.GenerateID(): cv.declare_id(TextSensor),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TextSensorStateTrigger),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateRawTrigger
),
}
),
}
TEXT_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
cv.GenerateID(): cv.declare_id(TextSensor),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateTrigger
),
}
),
cv.Optional(CONF_ON_RAW_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
TextSensorStateRawTrigger
),
}
),
}
)
)
_UNDEF = object()
@ -205,6 +212,10 @@ async def setup_text_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text_sensor(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -189,8 +189,6 @@ CONFIG_SCHEMA = cv.All(
cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT),
validate_temperature_multipliers,
validate_cooling_values,
cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_HEATING_STATE_PIN),
cv.has_at_most_one_key(CONF_ACTIVE_STATE, CONF_COOLING_STATE_PIN),
)
@ -207,6 +205,12 @@ async def to_code(config):
if switch_datapoint := config.get(CONF_SWITCH_DATAPOINT):
cg.add(var.set_switch_id(switch_datapoint))
if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN):
heating_state_pin = await cg.gpio_pin_expression(heating_state_pin_config)
cg.add(var.set_heating_state_pin(heating_state_pin))
if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN):
cooling_state_pin = await cg.gpio_pin_expression(cooling_state_pin_config)
cg.add(var.set_cooling_state_pin(cooling_state_pin))
if active_state_config := config.get(CONF_ACTIVE_STATE):
cg.add(var.set_active_state_id(active_state_config.get(CONF_DATAPOINT)))
if (heating_value := active_state_config.get(CONF_HEATING_VALUE)) is not None:
@ -217,13 +221,6 @@ async def to_code(config):
cg.add(var.set_active_state_drying_value(drying_value))
if (fanonly_value := active_state_config.get(CONF_FANONLY_VALUE)) is not None:
cg.add(var.set_active_state_fanonly_value(fanonly_value))
else:
if heating_state_pin_config := config.get(CONF_HEATING_STATE_PIN):
heating_state_pin = await cg.gpio_pin_expression(heating_state_pin_config)
cg.add(var.set_heating_state_pin(heating_state_pin))
if cooling_state_pin_config := config.get(CONF_COOLING_STATE_PIN):
cooling_state_pin = await cg.gpio_pin_expression(cooling_state_pin_config)
cg.add(var.set_cooling_state_pin(cooling_state_pin))
if target_temperature_datapoint := config.get(CONF_TARGET_TEMPERATURE_DATAPOINT):
cg.add(var.set_target_temperature_id(target_temperature_datapoint))

View File

@ -24,6 +24,14 @@ void TuyaClimate::setup() {
this->publish_state();
});
}
if (this->heating_state_pin_ != nullptr) {
this->heating_state_pin_->setup();
this->heating_state_ = this->heating_state_pin_->digital_read();
}
if (this->cooling_state_pin_ != nullptr) {
this->cooling_state_pin_->setup();
this->cooling_state_ = this->cooling_state_pin_->digital_read();
}
if (this->active_state_id_.has_value()) {
this->parent_->register_listener(*this->active_state_id_, [this](const TuyaDatapoint &datapoint) {
ESP_LOGV(TAG, "MCU reported active state is: %u", datapoint.value_enum);
@ -31,15 +39,6 @@ void TuyaClimate::setup() {
this->compute_state_();
this->publish_state();
});
} else {
if (this->heating_state_pin_ != nullptr) {
this->heating_state_pin_->setup();
this->heating_state_ = this->heating_state_pin_->digital_read();
}
if (this->cooling_state_pin_ != nullptr) {
this->cooling_state_pin_->setup();
this->cooling_state_ = this->cooling_state_pin_->digital_read();
}
}
if (this->target_temperature_id_.has_value()) {
this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) {
@ -113,9 +112,6 @@ void TuyaClimate::setup() {
}
void TuyaClimate::loop() {
if (this->active_state_id_.has_value())
return;
bool state_changed = false;
if (this->heating_state_pin_ != nullptr) {
bool heating_state = this->heating_state_pin_->digital_read();
@ -147,14 +143,18 @@ void TuyaClimate::control(const climate::ClimateCall &call) {
this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state);
const climate::ClimateMode new_mode = *call.get_mode();
if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_);
} else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_);
} else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_);
} else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_);
if (this->active_state_id_.has_value()) {
if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_heating_value_);
} else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_cooling_value_);
} else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_drying_value_);
} else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) {
this->parent_->set_enum_datapoint_value(*this->active_state_id_, *this->active_state_fanonly_value_);
}
} else {
ESP_LOGW(TAG, "Active state (mode) datapoint not configured");
}
}
@ -422,7 +422,32 @@ void TuyaClimate::compute_state_() {
}
climate::ClimateAction target_action = climate::CLIMATE_ACTION_IDLE;
if (this->active_state_id_.has_value()) {
if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) {
// Use state from input pins
if (this->heating_state_) {
target_action = climate::CLIMATE_ACTION_HEATING;
this->mode = climate::CLIMATE_MODE_HEAT;
} else if (this->cooling_state_) {
target_action = climate::CLIMATE_ACTION_COOLING;
this->mode = climate::CLIMATE_MODE_COOL;
}
if (this->active_state_id_.has_value()) {
// Both are available, use MCU datapoint as mode
if (this->supports_heat_ && this->active_state_heating_value_.has_value() &&
this->active_state_ == this->active_state_heating_value_) {
this->mode = climate::CLIMATE_MODE_HEAT;
} else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() &&
this->active_state_ == this->active_state_cooling_value_) {
this->mode = climate::CLIMATE_MODE_COOL;
} else if (this->active_state_drying_value_.has_value() &&
this->active_state_ == this->active_state_drying_value_) {
this->mode = climate::CLIMATE_MODE_DRY;
} else if (this->active_state_fanonly_value_.has_value() &&
this->active_state_ == this->active_state_fanonly_value_) {
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
}
}
} else if (this->active_state_id_.has_value()) {
// Use state from MCU datapoint
if (this->supports_heat_ && this->active_state_heating_value_.has_value() &&
this->active_state_ == this->active_state_heating_value_) {
@ -441,15 +466,6 @@ void TuyaClimate::compute_state_() {
target_action = climate::CLIMATE_ACTION_FAN;
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
}
} else if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) {
// Use state from input pins
if (this->heating_state_) {
target_action = climate::CLIMATE_ACTION_HEATING;
this->mode = climate::CLIMATE_MODE_HEAT;
} else if (this->cooling_state_) {
target_action = climate::CLIMATE_ACTION_COOLING;
this->mode = climate::CLIMATE_MODE_COOL;
}
} else {
// Fallback to active state calc based on temp and hysteresis
const float temp_diff = this->target_temperature - this->current_temperature;

View File

@ -16,6 +16,7 @@ CONF_DIRECTION_DATAPOINT = "direction_datapoint"
CONF_POSITION_DATAPOINT = "position_datapoint"
CONF_POSITION_REPORT_DATAPOINT = "position_report_datapoint"
CONF_INVERT_POSITION = "invert_position"
CONF_INVERT_POSITION_REPORT = "invert_position_report"
TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component)
@ -47,6 +48,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_,
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_,
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
cv.Optional(CONF_INVERT_POSITION_REPORT, default=False): cv.boolean,
cv.Optional(CONF_RESTORE_MODE, default="RESTORE"): cv.enum(
RESTORE_MODES, upper=True
),
@ -71,6 +73,7 @@ async def to_code(config):
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
cg.add(var.set_invert_position_report(config[CONF_INVERT_POSITION_REPORT]))
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
paren = await cg.get_variable(config[CONF_TUYA_ID])
cg.add(var.set_tuya_parent(paren))

View File

@ -51,7 +51,7 @@ void TuyaCover::setup() {
return;
}
auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_;
this->position = 1.0f - pos;
this->position = this->invert_position_report_ ? pos : 1.0f - pos;
this->publish_state();
});
}
@ -62,7 +62,7 @@ void TuyaCover::control(const cover::CoverCall &call) {
this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_STOP);
} else {
auto pos = this->position;
pos = 1.0f - pos;
pos = this->invert_position_report_ ? pos : 1.0f - pos;
auto position_int = static_cast<uint32_t>(pos * this->value_range_);
position_int = position_int + this->min_value_;
@ -78,7 +78,7 @@ void TuyaCover::control(const cover::CoverCall &call) {
this->parent_->force_set_enum_datapoint_value(*this->control_id_, COMMAND_CLOSE);
}
} else {
pos = 1.0f - pos;
pos = this->invert_position_report_ ? pos : 1.0f - pos;
auto position_int = static_cast<uint32_t>(pos * this->value_range_);
position_int = position_int + this->min_value_;
@ -112,6 +112,9 @@ void TuyaCover::dump_config() {
ESP_LOGCONFIG(TAG, " Configured as Inverted, but direction_datapoint isn't configured");
}
}
if (this->invert_position_report_) {
ESP_LOGCONFIG(TAG, " Position Reporting Inverted");
}
if (this->control_id_.has_value()) {
ESP_LOGCONFIG(TAG, " Control has datapoint ID %u", *this->control_id_);
}

View File

@ -25,6 +25,7 @@ class TuyaCover : public cover::Cover, public Component {
void set_min_value(uint32_t min_value) { min_value_ = min_value; }
void set_max_value(uint32_t max_value) { max_value_ = max_value; }
void set_invert_position(bool invert_position) { invert_position_ = invert_position; }
void set_invert_position_report(bool invert_position_report) { invert_position_report_ = invert_position_report; }
void set_restore_mode(TuyaCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; }
protected:
@ -42,6 +43,7 @@ class TuyaCover : public cover::Cover, public Component {
uint32_t max_value_;
uint32_t value_range_;
bool invert_position_;
bool invert_position_report_;
};
} // namespace tuya

View File

@ -269,6 +269,30 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
break;
}
case TuyaCommandType::EXTENDED_SERVICES: {
uint8_t subcommand = buffer[0];
switch ((TuyaExtendedServicesCommandType) subcommand) {
case TuyaExtendedServicesCommandType::RESET_NOTIFICATION: {
this->send_command_(
TuyaCommand{.cmd = TuyaCommandType::EXTENDED_SERVICES,
.payload = std::vector<uint8_t>{
static_cast<uint8_t>(TuyaExtendedServicesCommandType::RESET_NOTIFICATION), 0x00}});
ESP_LOGV(TAG, "Reset status notification enabled");
break;
}
case TuyaExtendedServicesCommandType::MODULE_RESET: {
ESP_LOGE(TAG, "EXTENDED_SERVICES::MODULE_RESET is not handled");
break;
}
case TuyaExtendedServicesCommandType::UPDATE_IN_PROGRESS: {
ESP_LOGE(TAG, "EXTENDED_SERVICES::UPDATE_IN_PROGRESS is not handled");
break;
}
default:
ESP_LOGE(TAG, "Invalid extended services subcommand (0x%02X) received", subcommand);
}
break;
}
default:
ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
}

View File

@ -60,6 +60,13 @@ enum class TuyaCommandType : uint8_t {
WIFI_RSSI = 0x24,
VACUUM_MAP_UPLOAD = 0x28,
GET_NETWORK_STATUS = 0x2B,
EXTENDED_SERVICES = 0x34,
};
enum class TuyaExtendedServicesCommandType : uint8_t {
RESET_NOTIFICATION = 0x04,
MODULE_RESET = 0x05,
UPDATE_IN_PROGRESS = 0x0A,
};
enum class TuyaInitState : uint8_t {

View File

@ -69,7 +69,7 @@ void IDFUARTComponent::setup() {
this->mark_failed();
return;
}
this->uart_num_ = next_uart_num++;
this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_);
this->lock_ = xSemaphoreCreateMutex();

View File

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.components import mqtt, web_server
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ID,
@ -14,6 +14,7 @@ from esphome.const import (
CONF_STATE,
CONF_STOP,
CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_GAS,
DEVICE_CLASS_WATER,
@ -70,28 +71,32 @@ ValveClosedTrigger = valve_ns.class_(
CONF_ON_CLOSED = "on_closed"
VALVE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Valve),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger),
}
),
}
VALVE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Valve),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger),
}
),
}
)
)
@ -119,6 +124,10 @@ async def setup_valve_core_(var, config):
mqtt_.set_custom_position_command_topic(position_command_topic_config)
)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_valve(var, config):
if not CORE.has_id(config[CONF_ID]):

View File

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ACTUAL_GAIN,
CONF_AMBIENT_LIGHT,
CONF_AUTO_MODE,
CONF_FULL_SPECTRUM,
CONF_GAIN,
@ -11,13 +12,13 @@ from esphome.const import (
CONF_INFRARED,
CONF_INTEGRATION_TIME,
CONF_NAME,
UNIT_LUX,
UNIT_MILLISECOND,
DEVICE_CLASS_ILLUMINANCE,
ICON_BRIGHTNESS_5,
ICON_BRIGHTNESS_6,
ICON_TIMER,
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
UNIT_MILLISECOND,
)
CODEOWNERS = ["@latonita"]
@ -28,7 +29,6 @@ ICON_MULTIPLICATION = "mdi:multiplication"
ICON_BRIGHTNESS_7 = "mdi:brightness-7"
CONF_ACTUAL_INTEGRATION_TIME = "actual_integration_time"
CONF_AMBIENT_LIGHT = "ambient_light"
CONF_AMBIENT_LIGHT_COUNTS = "ambient_light_counts"
CONF_FULL_SPECTRUM_COUNTS = "full_spectrum_counts"
CONF_LUX_COMPENSATION = "lux_compensation"

View File

@ -44,6 +44,12 @@ CONF_VOLUME_MULTIPLIER = "volume_multiplier"
CONF_WAKE_WORD = "wake_word"
CONF_ON_TIMER_STARTED = "on_timer_started"
CONF_ON_TIMER_UPDATED = "on_timer_updated"
CONF_ON_TIMER_CANCELLED = "on_timer_cancelled"
CONF_ON_TIMER_FINISHED = "on_timer_finished"
CONF_ON_TIMER_TICK = "on_timer_tick"
voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant")
VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component)
@ -64,6 +70,8 @@ ConnectedCondition = voice_assistant_ns.class_(
"ConnectedCondition", automation.Condition, cg.Parented.template(VoiceAssistant)
)
Timer = voice_assistant_ns.struct("Timer")
def tts_stream_validate(config):
if CONF_SPEAKER not in config and (
@ -131,6 +139,21 @@ CONFIG_SCHEMA = cv.All(
single=True
),
cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True),
cv.Optional(CONF_ON_TIMER_STARTED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_TIMER_UPDATED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_TIMER_CANCELLED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_TIMER_FINISHED): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_TIMER_TICK): automation.validate_automation(
single=True
),
}
).extend(cv.COMPONENT_SCHEMA),
tts_stream_validate,
@ -270,6 +293,49 @@ async def to_code(config):
config[CONF_ON_IDLE],
)
has_timers = False
if on_timer_started := config.get(CONF_ON_TIMER_STARTED):
await automation.build_automation(
var.get_timer_started_trigger(),
[(Timer, "timer")],
on_timer_started,
)
has_timers = True
if on_timer_updated := config.get(CONF_ON_TIMER_UPDATED):
await automation.build_automation(
var.get_timer_updated_trigger(),
[(Timer, "timer")],
on_timer_updated,
)
has_timers = True
if on_timer_cancelled := config.get(CONF_ON_TIMER_CANCELLED):
await automation.build_automation(
var.get_timer_cancelled_trigger(),
[(Timer, "timer")],
on_timer_cancelled,
)
has_timers = True
if on_timer_finished := config.get(CONF_ON_TIMER_FINISHED):
await automation.build_automation(
var.get_timer_finished_trigger(),
[(Timer, "timer")],
on_timer_finished,
)
has_timers = True
if on_timer_tick := config.get(CONF_ON_TIMER_TICK):
await automation.build_automation(
var.get_timer_tick_trigger(),
[(cg.std_vector.template(Timer), "timers")],
on_timer_tick,
)
has_timers = True
cg.add(var.set_has_timers(has_timers))
cg.add_define("USE_VOICE_ASSISTANT")

View File

@ -4,6 +4,7 @@
#include "esphome/core/log.h"
#include <cinttypes>
#include <cstdio>
namespace esphome {
@ -17,7 +18,7 @@ static const char *const TAG = "voice_assistant";
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 = 1024 * SAMPLE_RATE_HZ / 1000;
static const size_t BUFFER_SIZE = 512 * SAMPLE_RATE_HZ / 1000;
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;
@ -622,7 +623,7 @@ void VoiceAssistant::signal_stop_() {
}
void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
ESP_LOGD(TAG, "Event Type: %d", msg.event_type);
ESP_LOGD(TAG, "Event Type: %" PRId32, msg.event_type);
switch (msg.event_type) {
case api::enums::VOICE_ASSISTANT_RUN_START:
ESP_LOGD(TAG, "Assist Pipeline running");
@ -785,7 +786,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
this->defer([this]() { this->stt_vad_end_trigger_->trigger(); });
break;
default:
ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type);
ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type);
break;
}
}
@ -797,12 +798,65 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
this->speaker_buffer_index_ += msg.data.length();
this->speaker_buffer_size_ += msg.data.length();
this->speaker_bytes_received_ += msg.data.length();
ESP_LOGD(TAG, "Received audio: %d bytes from API", msg.data.length());
} else {
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
}
#endif
}
void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) {
Timer timer = {
.id = msg.timer_id,
.name = msg.name,
.total_seconds = msg.total_seconds,
.seconds_left = msg.seconds_left,
.is_active = msg.is_active,
};
this->timers_[timer.id] = timer;
ESP_LOGD(TAG, "Timer Event");
ESP_LOGD(TAG, " Type: %" PRId32, msg.event_type);
ESP_LOGD(TAG, " %s", timer.to_string().c_str());
switch (msg.event_type) {
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
this->timer_started_trigger_->trigger(timer);
break;
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
this->timer_updated_trigger_->trigger(timer);
break;
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
this->timer_cancelled_trigger_->trigger(timer);
this->timers_.erase(timer.id);
break;
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
this->timer_finished_trigger_->trigger(timer);
this->timers_.erase(timer.id);
break;
}
if (this->timers_.empty()) {
this->cancel_interval("timer-event");
this->timer_tick_running_ = false;
} else if (!this->timer_tick_running_) {
this->set_interval("timer-event", 1000, [this]() { this->timer_tick_(); });
this->timer_tick_running_ = true;
}
}
void VoiceAssistant::timer_tick_() {
std::vector<Timer> res;
res.reserve(this->timers_.size());
for (auto &pair : this->timers_) {
auto &timer = pair.second;
if (timer.is_active && timer.seconds_left > 0) {
timer.seconds_left--;
}
res.push_back(timer);
}
this->timer_tick_trigger_->trigger(res);
}
VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace voice_assistant

View File

@ -24,6 +24,9 @@
#include <esp_vad.h>
#endif
#include <unordered_map>
#include <vector>
namespace esphome {
namespace voice_assistant {
@ -36,6 +39,7 @@ enum VoiceAssistantFeature : uint32_t {
FEATURE_VOICE_ASSISTANT = 1 << 0,
FEATURE_SPEAKER = 1 << 1,
FEATURE_API_AUDIO = 1 << 2,
FEATURE_TIMERS = 1 << 3,
};
enum class State {
@ -59,6 +63,20 @@ enum AudioMode : uint8_t {
AUDIO_MODE_API,
};
struct Timer {
std::string id;
std::string name;
uint32_t total_seconds;
uint32_t seconds_left;
bool is_active;
std::string to_string() const {
return str_sprintf("Timer(id=%s, name=%s, total_seconds=%" PRIu32 ", seconds_left=%" PRIu32 ", is_active=%s)",
this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left,
YESNO(this->is_active));
}
};
class VoiceAssistant : public Component {
public:
void setup() override;
@ -100,6 +118,11 @@ class VoiceAssistant : public Component {
flags |= VoiceAssistantFeature::FEATURE_SPEAKER;
}
#endif
if (this->has_timers_) {
flags |= VoiceAssistantFeature::FEATURE_TIMERS;
}
return flags;
}
@ -108,6 +131,7 @@ class VoiceAssistant : public Component {
void on_event(const api::VoiceAssistantEventResponse &msg);
void on_audio(const api::VoiceAssistantAudio &msg);
void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg);
bool is_running() const { return this->state_ != State::IDLE; }
void set_continuous(bool continuous) { this->continuous_ = continuous; }
@ -150,6 +174,14 @@ class VoiceAssistant : public Component {
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
Trigger<Timer> *get_timer_started_trigger() const { return this->timer_started_trigger_; }
Trigger<Timer> *get_timer_updated_trigger() const { return this->timer_updated_trigger_; }
Trigger<Timer> *get_timer_cancelled_trigger() const { return this->timer_cancelled_trigger_; }
Trigger<Timer> *get_timer_finished_trigger() const { return this->timer_finished_trigger_; }
Trigger<std::vector<Timer>> *get_timer_tick_trigger() const { return this->timer_tick_trigger_; }
void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
const std::unordered_map<std::string, Timer> &get_timers() const { return this->timers_; }
protected:
bool allocate_buffers_();
void clear_buffers_();
@ -186,6 +218,16 @@ class VoiceAssistant : public Component {
api::APIConnection *api_client_{nullptr};
std::unordered_map<std::string, Timer> timers_;
void timer_tick_();
Trigger<Timer> *timer_started_trigger_ = new Trigger<Timer>();
Trigger<Timer> *timer_finished_trigger_ = new Trigger<Timer>();
Trigger<Timer> *timer_updated_trigger_ = new Trigger<Timer>();
Trigger<Timer> *timer_cancelled_trigger_ = new Trigger<Timer>();
Trigger<std::vector<Timer>> *timer_tick_trigger_ = new Trigger<std::vector<Timer>>();
bool has_timers_{false};
bool timer_tick_running_{false};
microphone::Microphone *mic_{nullptr};
#ifdef USE_SPEAKER
void write_speaker_();

View File

@ -1 +1 @@
CODEOWNERS = ["@willwill2will54"]
CODEOWNERS = ["@willwill2will54", "@clydebarrow"]

View File

@ -2,6 +2,16 @@ import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE
DEPENDENCIES = ["network"]
def AUTO_LOAD():
if CORE.is_esp8266 or CORE.is_rp2040:
return []
return ["socket"]
CONF_TARGET_MAC_ADDRESS = "target_mac_address"
@ -9,25 +19,19 @@ wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan")
WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component)
DEPENDENCIES = ["network"]
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA = (
button.button_schema(WakeOnLanButton)
.extend(cv.COMPONENT_SCHEMA)
.extend(
cv.Schema(
{
cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address,
}
),
),
cv.only_with_arduino,
{
cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address,
}
)
)
def to_code(config):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts))
yield cg.register_component(var, config)
yield button.register_button(var, config)
cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts))
await cg.register_component(var, config)
await button.register_button(var, config)

View File

@ -1,5 +1,3 @@
#ifdef USE_ARDUINO
#include "wake_on_lan.h"
#include "esphome/core/log.h"
#include "esphome/components/network/ip_address.h"
@ -22,40 +20,68 @@ void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, ui
void WakeOnLanButton::dump_config() {
LOG_BUTTON("", "Wake-on-LAN Button", this);
ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2],
macaddr_[3], macaddr_[4], macaddr_[5]);
ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", this->macaddr_[0], this->macaddr_[1],
this->macaddr_[2], this->macaddr_[3], this->macaddr_[4], this->macaddr_[5]);
}
void WakeOnLanButton::press_action() {
if (!network::is_connected()) {
ESP_LOGW(TAG, "Network not connected");
return;
}
ESP_LOGI(TAG, "Sending Wake-on-LAN Packet...");
bool begin_status = false;
bool end_status = false;
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
struct sockaddr_storage saddr {};
auto addr_len =
socket::set_sockaddr(reinterpret_cast<sockaddr *>(&saddr), sizeof(saddr), "255.255.255.255", this->port_);
uint8_t buffer[6 + sizeof this->macaddr_ * 16];
memcpy(buffer, PREFIX, sizeof(PREFIX));
for (size_t i = 0; i != 16; i++) {
memcpy(buffer + i * sizeof(this->macaddr_) + sizeof(PREFIX), this->macaddr_, sizeof(this->macaddr_));
}
if (this->broadcast_socket_->sendto(buffer, sizeof(buffer), 0, reinterpret_cast<const sockaddr *>(&saddr),
addr_len) <= 0)
ESP_LOGW(TAG, "sendto() error %d", errno);
#else
IPAddress broadcast = IPAddress(255, 255, 255, 255);
#ifdef USE_ESP8266
for (auto ip : esphome::network::get_ip_addresses()) {
if (ip.is_ip4()) {
begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128);
break;
if (this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128) != 0) {
this->udp_client_.write(PREFIX, 6);
for (size_t i = 0; i < 16; i++) {
this->udp_client_.write(macaddr_, 6);
}
if (this->udp_client_.endPacket() != 0)
return;
ESP_LOGW(TAG, "WOL broadcast failed");
return;
}
}
}
ESP_LOGW(TAG, "No ip4 addresses to broadcast to");
#endif
#ifdef USE_ESP32
begin_status = this->udp_client_.beginPacket(broadcast, 9);
#endif
}
if (begin_status) {
this->udp_client_.write(PREFIX, 6);
for (size_t i = 0; i < 16; i++) {
this->udp_client_.write(macaddr_, 6);
}
end_status = this->udp_client_.endPacket();
void WakeOnLanButton::setup() {
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (this->broadcast_socket_ == nullptr) {
this->mark_failed();
this->status_set_error("Could not create socket");
return;
}
if (!begin_status || end_status) {
ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!");
int enable = 1;
auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) {
this->status_set_warning("Socket unable to set reuseaddr");
// we can still continue
}
err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int));
if (err != 0) {
this->status_set_warning("Socket unable to set broadcast");
}
#endif
}
} // namespace wake_on_lan
} // namespace esphome
#endif

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