mirror of
https://github.com/esphome/esphome.git
synced 2024-09-28 04:09:16 +02:00
Merge branch 'dev' into opentherm-squashed
This commit is contained in:
commit
6750625fe2
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -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 }}
|
||||
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@ -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>
|
||||
|
@ -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:
|
||||
|
13
CODEOWNERS
13
CODEOWNERS
@ -152,6 +152,10 @@ esphome/components/grove_tb6612fng/* @max246
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||
esphome/components/haier/* @paveldn
|
||||
esphome/components/haier/binary_sensor/* @paveldn
|
||||
esphome/components/haier/button/* @paveldn
|
||||
esphome/components/haier/sensor/* @paveldn
|
||||
esphome/components/haier/text_sensor/* @paveldn
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
@ -163,9 +167,11 @@ 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/http_request/ota/* @oarcher
|
||||
esphome/components/htu31d/* @betterengineering
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
@ -206,6 +212,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
|
||||
@ -307,7 +314,7 @@ esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
@ -411,7 +418,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
|
||||
|
@ -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 '*'
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
FROM base AS docker
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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):
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
@ -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{};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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,7 +386,10 @@ def validate_click_timing(value):
|
||||
return value
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
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(
|
||||
@ -421,7 +425,9 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DoubleClickTrigger
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
@ -449,6 +455,7 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_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]):
|
||||
|
@ -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,7 +44,10 @@ 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(
|
||||
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,
|
||||
@ -53,6 +57,7 @@ BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_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]):
|
||||
|
@ -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,7 +151,10 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
|
||||
),
|
||||
)
|
||||
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
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),
|
||||
@ -237,6 +241,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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]):
|
||||
|
@ -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,7 +89,10 @@ CoverClosedTrigger = cover_ns.class_(
|
||||
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
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),
|
||||
@ -116,6 +120,7 @@ COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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,7 +64,10 @@ DATETIME_MODES = [
|
||||
]
|
||||
|
||||
|
||||
_DATETIME_SCHEMA = cv.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(
|
||||
{
|
||||
@ -72,7 +76,8 @@ _DATETIME_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
}
|
||||
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
@ -80,6 +80,17 @@ void DateCall::validate_() {
|
||||
|
||||
void DateCall::perform() {
|
||||
this->validate_();
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
|
||||
if (this->year_.has_value()) {
|
||||
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||
}
|
||||
if (this->month_.has_value()) {
|
||||
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||
}
|
||||
if (this->day_.has_value()) {
|
||||
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
@ -1,19 +1,16 @@
|
||||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NUM_ATTEMPTS,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_REBOOT_TIMEOUT,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_VERSION,
|
||||
KEY_PAST_SAFE_MODE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@ -28,7 +25,6 @@ CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESPHomeOTAComponent),
|
||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||
cv.SplitDefault(
|
||||
CONF_PORT,
|
||||
@ -39,10 +35,15 @@ CONFIG_SCHEMA = (
|
||||
rtl87xx=8892,
|
||||
): cv.port,
|
||||
cv.Optional(CONF_PASSWORD): cv.string,
|
||||
cv.Optional(
|
||||
CONF_REBOOT_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
cv.Optional(CONF_REBOOT_TIMEOUT): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
cv.Optional(CONF_SAFE_MODE): cv.invalid(
|
||||
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
@ -50,10 +51,8 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(50.0)
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
CORE.data[CONF_OTA] = {}
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
@ -63,10 +62,3 @@ async def to_code(config):
|
||||
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if config[CONF_SAFE_MODE]:
|
||||
condition = var.should_enter_safe_mode(
|
||||
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
||||
)
|
||||
cg.add(RawExpression(f"if ({condition}) return"))
|
||||
CORE.data[CONF_OTA][KEY_PAST_SAFE_MODE] = True
|
||||
|
@ -78,23 +78,9 @@ void ESPHomeOTAComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Password configured");
|
||||
}
|
||||
#endif
|
||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
||||
this->safe_mode_rtc_value_ != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGW(TAG, "Last reset occurred too quickly; safe mode will be invoked in %" PRIu32 " restarts",
|
||||
this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_);
|
||||
}
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::loop() {
|
||||
this->handle_();
|
||||
|
||||
if (this->has_safe_mode_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
||||
this->has_safe_mode_ = false;
|
||||
// successful boot, reset counter
|
||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||
this->clean_rtc();
|
||||
}
|
||||
}
|
||||
void ESPHomeOTAComponent::loop() { this->handle_(); }
|
||||
|
||||
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
||||
|
||||
@ -423,86 +409,4 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
||||
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
||||
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
void ESPHomeOTAComponent::set_safe_mode_pending(const bool &pending) {
|
||||
if (!this->has_safe_mode_)
|
||||
return;
|
||||
|
||||
uint32_t current_rtc = this->read_rtc_();
|
||||
|
||||
if (pending && current_rtc != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Device will enter safe mode on next boot");
|
||||
this->write_rtc_(ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC);
|
||||
}
|
||||
|
||||
if (!pending && current_rtc == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||
ESP_LOGI(TAG, "Safe mode pending has been cleared");
|
||||
this->clean_rtc();
|
||||
}
|
||||
}
|
||||
bool ESPHomeOTAComponent::get_safe_mode_pending() {
|
||||
return this->has_safe_mode_ && this->read_rtc_() == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
}
|
||||
|
||||
bool ESPHomeOTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||
this->has_safe_mode_ = true;
|
||||
this->safe_mode_start_time_ = millis();
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||
|
||||
bool is_manual_safe_mode = this->safe_mode_rtc_value_ == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
|
||||
if (is_manual_safe_mode) {
|
||||
ESP_LOGI(TAG, "Safe mode has been entered manually");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "There have been %" PRIu32 " suspected unsuccessful boot attempts", this->safe_mode_rtc_value_);
|
||||
}
|
||||
|
||||
if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
|
||||
this->clean_rtc();
|
||||
|
||||
if (!is_manual_safe_mode) {
|
||||
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode");
|
||||
}
|
||||
|
||||
this->status_set_error();
|
||||
this->set_timeout(enable_time, []() {
|
||||
ESP_LOGE(TAG, "No OTA attempt made, restarting");
|
||||
App.reboot();
|
||||
});
|
||||
|
||||
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
|
||||
delay(300); // NOLINT
|
||||
App.setup();
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for OTA attempt");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// increment counter
|
||||
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::write_rtc_(uint32_t val) {
|
||||
this->rtc_.save(&val);
|
||||
global_preferences->sync();
|
||||
}
|
||||
|
||||
uint32_t ESPHomeOTAComponent::read_rtc_() {
|
||||
uint32_t val;
|
||||
if (!this->rtc_.load(&val))
|
||||
return 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::clean_rtc() { this->write_rtc_(0); }
|
||||
|
||||
void ESPHomeOTAComponent::on_safe_shutdown() {
|
||||
if (this->has_safe_mode_ && this->read_rtc_() != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC)
|
||||
this->clean_rtc();
|
||||
}
|
||||
} // namespace esphome
|
||||
|
@ -15,17 +15,9 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
void set_auth_password(const std::string &password) { password_ = password; }
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
/// Manually set the port OTA should listen on.
|
||||
/// Manually set the port OTA should listen on
|
||||
void set_port(uint16_t port);
|
||||
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||
|
||||
/// Set to true if the next startup will enter safe mode
|
||||
void set_safe_mode_pending(const bool &pending);
|
||||
bool get_safe_mode_pending();
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
@ -33,14 +25,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
|
||||
uint16_t get_port() const;
|
||||
|
||||
void clean_rtc();
|
||||
|
||||
void on_safe_shutdown() override;
|
||||
|
||||
protected:
|
||||
void write_rtc_(uint32_t val);
|
||||
uint32_t read_rtc_();
|
||||
|
||||
void handle_();
|
||||
bool readall_(uint8_t *buf, size_t len);
|
||||
bool writeall_(const uint8_t *buf, size_t len);
|
||||
@ -53,16 +38,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
|
||||
std::unique_ptr<socket::Socket> server_;
|
||||
std::unique_ptr<socket::Socket> client_;
|
||||
|
||||
bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled
|
||||
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should be on for
|
||||
uint32_t safe_mode_rtc_value_;
|
||||
uint8_t safe_mode_num_attempts_;
|
||||
ESPPreferenceObject rtc_;
|
||||
|
||||
static const uint32_t ENTER_SAFE_MODE_MAGIC =
|
||||
0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
@ -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]))
|
||||
|
@ -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
|
||||
|
@ -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_{};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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])
|
||||
|
@ -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,7 +80,10 @@ 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(
|
||||
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(
|
||||
@ -121,12 +125,16 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
||||
),
|
||||
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
|
||||
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.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FanOscillatingSetTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
|
||||
@ -140,6 +148,7 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).exte
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_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)
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
102
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
Normal file
102
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
Normal 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
|
@ -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
|
||||
|
@ -46,7 +46,7 @@ template<typename... Ts> class BeeperOffAction : public Action<Ts...> {
|
||||
template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
|
||||
public:
|
||||
VerticalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(AirflowVerticalDirection, direction)
|
||||
TEMPLATABLE_VALUE(hon_protocol::VerticalSwingMode, direction)
|
||||
void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); }
|
||||
|
||||
protected:
|
||||
@ -56,7 +56,7 @@ template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
|
||||
template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> {
|
||||
public:
|
||||
HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(AirflowHorizontalDirection, direction)
|
||||
TEMPLATABLE_VALUE(hon_protocol::HorizontalSwingMode, direction)
|
||||
void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); }
|
||||
|
||||
protected:
|
||||
|
@ -11,6 +11,7 @@ from ..climate import (
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
|
41
esphome/components/haier/button/__init__.py
Normal file
41
esphome/components/haier/button/__init__.py
Normal file
@ -0,0 +1,41 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import button
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
haier_ns,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
|
||||
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
|
||||
|
||||
|
||||
# Haier buttons
|
||||
CONF_SELF_CLEANING = "self_cleaning"
|
||||
CONF_STERI_CLEANING = "steri_cleaning"
|
||||
|
||||
# Additional icons
|
||||
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||
SelfCleaningButton,
|
||||
icon=ICON_SPRAY_BOTTLE,
|
||||
),
|
||||
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
|
||||
SteriCleaningButton,
|
||||
icon=ICON_SPRAY_BOTTLE,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
|
||||
if conf := config.get(button_type):
|
||||
btn = await button.new_button(conf)
|
||||
await cg.register_parented(btn, config[CONF_HAIER_ID])
|
9
esphome/components/haier/button/self_cleaning.cpp
Normal file
9
esphome/components/haier/button/self_cleaning.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "self_cleaning.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); }
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
18
esphome/components/haier/button/self_cleaning.h
Normal file
18
esphome/components/haier/button/self_cleaning.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../hon_climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
class SelfCleaningButton : public button::Button, public Parented<HonClimate> {
|
||||
public:
|
||||
SelfCleaningButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
9
esphome/components/haier/button/steri_cleaning.cpp
Normal file
9
esphome/components/haier/button/steri_cleaning.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "steri_cleaning.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); }
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
18
esphome/components/haier/button/steri_cleaning.h
Normal file
18
esphome/components/haier/button/steri_cleaning.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../hon_climate.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
class SteriCleaningButton : public button::Button, public Parented<HonClimate> {
|
||||
public:
|
||||
SteriCleaningButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
@ -55,6 +55,7 @@ PROTOCOL_HON = "HON"
|
||||
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
||||
|
||||
haier_ns = cg.esphome_ns.namespace("haier")
|
||||
hon_protocol_ns = haier_ns.namespace("hon_protocol")
|
||||
HaierClimateBase = haier_ns.class_(
|
||||
"HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component
|
||||
)
|
||||
@ -63,7 +64,7 @@ Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase)
|
||||
|
||||
CONF_HAIER_ID = "haier_id"
|
||||
|
||||
AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True)
|
||||
AirflowVerticalDirection = hon_protocol_ns.enum("VerticalSwingMode", True)
|
||||
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||
"HEALTH_UP": AirflowVerticalDirection.HEALTH_UP,
|
||||
"MAX_UP": AirflowVerticalDirection.MAX_UP,
|
||||
@ -73,7 +74,7 @@ AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||
"HEALTH_DOWN": AirflowVerticalDirection.HEALTH_DOWN,
|
||||
}
|
||||
|
||||
AirflowHorizontalDirection = haier_ns.enum("AirflowHorizontalDirection", True)
|
||||
AirflowHorizontalDirection = hon_protocol_ns.enum("HorizontalSwingMode", True)
|
||||
AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
|
||||
"MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT,
|
||||
"LEFT": AirflowHorizontalDirection.LEFT,
|
||||
@ -483,4 +484,4 @@ async def to_code(config):
|
||||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
||||
)
|
||||
# https://github.com/paveldn/HaierProtocol
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.25")
|
||||
cg.add_library("pavlodn/HaierProtocol", "0.9.28")
|
||||
|
@ -234,6 +234,7 @@ void HaierClimateBase::setup() {
|
||||
this->haier_protocol_.set_default_timeout_handler(
|
||||
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
|
||||
this->set_handlers();
|
||||
this->initialization();
|
||||
}
|
||||
|
||||
void HaierClimateBase::dump_config() {
|
||||
@ -326,7 +327,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
|
||||
|
||||
void HaierClimateBase::control(const ClimateCall &call) {
|
||||
ESP_LOGD("Control", "Control call");
|
||||
if (this->protocol_phase_ < ProtocolPhases::IDLE) {
|
||||
if (!this->valid_connection()) {
|
||||
ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
|
||||
return; // cancel the control, we cant do it without a poll answer.
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class HaierClimateBase : public esphome::Component,
|
||||
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
|
||||
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
|
||||
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets);
|
||||
bool valid_connection() { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
|
||||
bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
|
||||
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
|
||||
size_t read_array(uint8_t *data, size_t len) noexcept override {
|
||||
return esphome::uart::UARTDevice::read_array(data, len) ? len : 0;
|
||||
@ -80,6 +80,7 @@ class HaierClimateBase : public esphome::Component,
|
||||
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
||||
virtual haier_protocol::HaierMessage get_control_message() = 0;
|
||||
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
|
||||
virtual void initialization(){};
|
||||
virtual bool prepare_pending_action();
|
||||
virtual void process_protocol_reset();
|
||||
esphome::climate::ClimateTraits traits() override;
|
||||
|
@ -19,38 +19,6 @@ constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
|
||||
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
|
||||
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
|
||||
|
||||
hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) {
|
||||
switch (direction) {
|
||||
case AirflowVerticalDirection::HEALTH_UP:
|
||||
return hon_protocol::VerticalSwingMode::HEALTH_UP;
|
||||
case AirflowVerticalDirection::MAX_UP:
|
||||
return hon_protocol::VerticalSwingMode::MAX_UP;
|
||||
case AirflowVerticalDirection::UP:
|
||||
return hon_protocol::VerticalSwingMode::UP;
|
||||
case AirflowVerticalDirection::DOWN:
|
||||
return hon_protocol::VerticalSwingMode::DOWN;
|
||||
case AirflowVerticalDirection::HEALTH_DOWN:
|
||||
return hon_protocol::VerticalSwingMode::HEALTH_DOWN;
|
||||
default:
|
||||
return hon_protocol::VerticalSwingMode::CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDirection direction) {
|
||||
switch (direction) {
|
||||
case AirflowHorizontalDirection::MAX_LEFT:
|
||||
return hon_protocol::HorizontalSwingMode::MAX_LEFT;
|
||||
case AirflowHorizontalDirection::LEFT:
|
||||
return hon_protocol::HorizontalSwingMode::LEFT;
|
||||
case AirflowHorizontalDirection::RIGHT:
|
||||
return hon_protocol::HorizontalSwingMode::RIGHT;
|
||||
case AirflowHorizontalDirection::MAX_RIGHT:
|
||||
return hon_protocol::HorizontalSwingMode::MAX_RIGHT;
|
||||
default:
|
||||
return hon_protocol::HorizontalSwingMode::CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
HonClimate::HonClimate()
|
||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
@ -66,17 +34,21 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
|
||||
|
||||
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
||||
|
||||
AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; };
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
|
||||
return this->current_vertical_swing_;
|
||||
};
|
||||
|
||||
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
|
||||
this->vertical_direction_ = direction;
|
||||
void HonClimate::set_vertical_airflow(hon_protocol::VerticalSwingMode direction) {
|
||||
this->pending_vertical_direction_ = direction;
|
||||
this->force_send_control_ = true;
|
||||
}
|
||||
|
||||
AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; }
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
|
||||
return this->current_horizontal_swing_;
|
||||
}
|
||||
|
||||
void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) {
|
||||
this->horizontal_direction_ = direction;
|
||||
void HonClimate::set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction) {
|
||||
this->pending_horizontal_direction_ = direction;
|
||||
this->force_send_control_ = true;
|
||||
}
|
||||
|
||||
@ -148,6 +120,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haie
|
||||
this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
|
||||
strncpy(tmp, answr->device_name, 8);
|
||||
this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->update_sub_text_sensor_(SubTextSensorType::APPLIANCE_NAME, this->hvac_hardware_info_.value().device_name_);
|
||||
this->update_sub_text_sensor_(SubTextSensorType::PROTOCOL_VERSION,
|
||||
this->hvac_hardware_info_.value().protocol_version_);
|
||||
#endif
|
||||
this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
|
||||
this->hvac_hardware_info_.value().functions_[1] =
|
||||
(answr->functions[1] & 0x02) != 0; // controller-device mode support
|
||||
@ -488,6 +465,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::initialization() {
|
||||
constexpr uint32_t restore_settings_version = 0xE834D8DCUL;
|
||||
this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
|
||||
HonSettings recovered;
|
||||
if (this->rtc_.load(&recovered)) {
|
||||
this->settings_ = recovered;
|
||||
} else {
|
||||
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER};
|
||||
}
|
||||
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
|
||||
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
|
||||
}
|
||||
|
||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
||||
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
||||
@ -560,16 +550,16 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
if (climate_control.swing_mode.has_value()) {
|
||||
switch (climate_control.swing_mode.value()) {
|
||||
case CLIMATE_SWING_OFF:
|
||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
||||
out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
|
||||
out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
|
||||
break;
|
||||
case CLIMATE_SWING_VERTICAL:
|
||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
||||
out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
|
||||
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
|
||||
break;
|
||||
case CLIMATE_SWING_HORIZONTAL:
|
||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
||||
out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
|
||||
break;
|
||||
case CLIMATE_SWING_BOTH:
|
||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
||||
@ -631,11 +621,14 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (out_data->vertical_swing_mode != (uint8_t) hon_protocol::VerticalSwingMode::AUTO)
|
||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
||||
if (out_data->horizontal_swing_mode != (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)
|
||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
||||
}
|
||||
if (this->pending_vertical_direction_.has_value()) {
|
||||
out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
|
||||
this->pending_vertical_direction_.reset();
|
||||
}
|
||||
if (this->pending_horizontal_direction_.has_value()) {
|
||||
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
|
||||
this->pending_horizontal_direction_.reset();
|
||||
}
|
||||
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
|
||||
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
||||
@ -737,6 +730,33 @@ void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t val
|
||||
}
|
||||
#endif // USE_BINARY_SENSOR
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void HonClimate::set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens) {
|
||||
this->sub_text_sensors_[(size_t) type] = sens;
|
||||
switch (type) {
|
||||
case SubTextSensorType::APPLIANCE_NAME:
|
||||
if (this->hvac_hardware_info_.has_value())
|
||||
sens->publish_state(this->hvac_hardware_info_.value().device_name_);
|
||||
break;
|
||||
case SubTextSensorType::PROTOCOL_VERSION:
|
||||
if (this->hvac_hardware_info_.has_value())
|
||||
sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
|
||||
break;
|
||||
case SubTextSensorType::CLEANING_STATUS:
|
||||
sens->publish_state(this->get_cleaning_status_text());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::string &value) {
|
||||
size_t index = (size_t) type;
|
||||
if (this->sub_text_sensors_[index] != nullptr)
|
||||
this->sub_text_sensors_[index]->publish_state(value);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
|
||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
|
||||
this->extra_control_packet_bytes_;
|
||||
@ -896,6 +916,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
|
||||
}
|
||||
this->cleaning_status_ = new_cleaning;
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
this->update_sub_text_sensor_(SubTextSensorType::CLEANING_STATUS, this->get_cleaning_status_text());
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
}
|
||||
{
|
||||
@ -941,6 +964,19 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
this->swing_mode = CLIMATE_SWING_OFF;
|
||||
}
|
||||
}
|
||||
// Saving last known non auto mode for vertical and horizontal swing
|
||||
this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
|
||||
this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
|
||||
bool save_settings = ((this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO) &&
|
||||
(this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO_SPECIAL) &&
|
||||
(this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
|
||||
((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
|
||||
(this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
|
||||
if (save_settings) {
|
||||
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
|
||||
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
|
||||
this->rtc_.save(&this->settings_);
|
||||
}
|
||||
should_publish = should_publish || (old_swing_mode != this->swing_mode);
|
||||
}
|
||||
this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
|
||||
|
@ -7,29 +7,16 @@
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/automation.h"
|
||||
#include "haier_base.h"
|
||||
#include "hon_packet.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace haier {
|
||||
|
||||
enum class AirflowVerticalDirection : uint8_t {
|
||||
HEALTH_UP = 0,
|
||||
MAX_UP = 1,
|
||||
UP = 2,
|
||||
CENTER = 3,
|
||||
DOWN = 4,
|
||||
HEALTH_DOWN = 5,
|
||||
};
|
||||
|
||||
enum class AirflowHorizontalDirection : uint8_t {
|
||||
MAX_LEFT = 0,
|
||||
LEFT = 1,
|
||||
CENTER = 2,
|
||||
RIGHT = 3,
|
||||
MAX_RIGHT = 4,
|
||||
};
|
||||
|
||||
enum class CleaningState : uint8_t {
|
||||
NO_CLEANING = 0,
|
||||
SELF_CLEAN = 1,
|
||||
@ -38,6 +25,11 @@ enum class CleaningState : uint8_t {
|
||||
|
||||
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
||||
|
||||
struct HonSettings {
|
||||
hon_protocol::VerticalSwingMode last_vertiacal_swing;
|
||||
hon_protocol::HorizontalSwingMode last_horizontal_swing;
|
||||
};
|
||||
|
||||
class HonClimate : public HaierClimateBase {
|
||||
#ifdef USE_SENSOR
|
||||
public:
|
||||
@ -80,6 +72,20 @@ class HonClimate : public HaierClimateBase {
|
||||
protected:
|
||||
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
|
||||
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
public:
|
||||
enum class SubTextSensorType {
|
||||
CLEANING_STATUS = 0,
|
||||
PROTOCOL_VERSION,
|
||||
APPLIANCE_NAME,
|
||||
SUB_TEXT_SENSOR_TYPE_COUNT,
|
||||
};
|
||||
void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens);
|
||||
|
||||
protected:
|
||||
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
|
||||
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
public:
|
||||
HonClimate();
|
||||
@ -89,10 +95,10 @@ class HonClimate : public HaierClimateBase {
|
||||
void dump_config() override;
|
||||
void set_beeper_state(bool state);
|
||||
bool get_beeper_state() const;
|
||||
AirflowVerticalDirection get_vertical_airflow() const;
|
||||
void set_vertical_airflow(AirflowVerticalDirection direction);
|
||||
AirflowHorizontalDirection get_horizontal_airflow() const;
|
||||
void set_horizontal_airflow(AirflowHorizontalDirection direction);
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
|
||||
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
|
||||
void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction);
|
||||
std::string get_cleaning_status_text() const;
|
||||
CleaningState get_cleaning_status() const;
|
||||
void start_self_cleaning();
|
||||
@ -108,6 +114,7 @@ class HonClimate : public HaierClimateBase {
|
||||
void process_phase(std::chrono::steady_clock::time_point now) override;
|
||||
haier_protocol::HaierMessage get_control_message() override;
|
||||
haier_protocol::HaierMessage get_power_message(bool state) override;
|
||||
void initialization() override;
|
||||
bool prepare_pending_action() override;
|
||||
void process_protocol_reset() override;
|
||||
bool should_get_big_data_();
|
||||
@ -147,9 +154,9 @@ class HonClimate : public HaierClimateBase {
|
||||
bool beeper_status_;
|
||||
CleaningState cleaning_status_;
|
||||
bool got_valid_outdoor_temp_;
|
||||
AirflowVerticalDirection vertical_direction_;
|
||||
AirflowHorizontalDirection horizontal_direction_;
|
||||
esphome::optional<HardwareInfo> hvac_hardware_info_;
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
|
||||
esphome::optional<HardwareInfo> hvac_hardware_info_{};
|
||||
uint8_t active_alarms_[8];
|
||||
int extra_control_packet_bytes_;
|
||||
HonControlMethod control_method_;
|
||||
@ -159,6 +166,10 @@ class HonClimate : public HaierClimateBase {
|
||||
float active_alarm_count_{NAN};
|
||||
std::chrono::steady_clock::time_point last_alarm_request_;
|
||||
int big_data_sensors_{0};
|
||||
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
|
||||
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
||||
HonSettings settings_;
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||
|
@ -13,7 +13,10 @@ enum class VerticalSwingMode : uint8_t {
|
||||
UP = 0x04,
|
||||
CENTER = 0x06,
|
||||
DOWN = 0x08,
|
||||
AUTO = 0x0C
|
||||
MAX_DOWN = 0x0A,
|
||||
AUTO = 0x0C,
|
||||
// Auto for special modes
|
||||
AUTO_SPECIAL = 0x0E
|
||||
};
|
||||
|
||||
enum class HorizontalSwingMode : uint8_t {
|
||||
|
@ -31,6 +31,7 @@ from ..climate import (
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
SensorTypeEnum = HonClimate.enum("SubSensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
|
54
esphome/components/haier/text_sensor/__init__.py
Normal file
54
esphome/components/haier/text_sensor/__init__.py
Normal file
@ -0,0 +1,54 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ENTITY_CATEGORY_NONE,
|
||||
)
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
TextSensorTypeEnum = HonClimate.enum("SubTextSensorType", True)
|
||||
|
||||
# Haier text sensors
|
||||
CONF_CLEANING_STATUS = "cleaning_status"
|
||||
CONF_PROTOCOL_VERSION = "protocol_version"
|
||||
CONF_APPLIANCE_NAME = "appliance_name"
|
||||
|
||||
# Additional icons
|
||||
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
|
||||
ICON_TEXT_BOX = "mdi:text-box-outline"
|
||||
|
||||
TEXT_SENSOR_TYPES = {
|
||||
CONF_CLEANING_STATUS: text_sensor.text_sensor_schema(
|
||||
icon=ICON_SPRAY_BOTTLE,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
CONF_PROTOCOL_VERSION: text_sensor.text_sensor_schema(
|
||||
icon=ICON_TEXT_BOX,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_APPLIANCE_NAME: text_sensor.text_sensor_schema(
|
||||
icon=ICON_TEXT_BOX,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
}
|
||||
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||
|
||||
for type, _ in TEXT_SENSOR_TYPES.items():
|
||||
if conf := config.get(type):
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
text_sensor_type = getattr(TextSensorTypeEnum, type.upper())
|
||||
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
|
@ -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);
|
||||
|
@ -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"]
|
||||
|
||||
|
||||
|
20
esphome/components/host/time/__init__.py
Normal file
20
esphome/components/host/time/__init__.py
Normal 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)
|
15
esphome/components/host/time/host_time.h
Normal file
15
esphome/components/host/time/host_time.h
Normal 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
|
189
esphome/components/http_request/ota/__init__.py
Normal file
189
esphome/components/http_request/ota/__init__.py
Normal file
@ -0,0 +1,189 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||
CONF_ID,
|
||||
CONF_PASSWORD,
|
||||
CONF_TIMEOUT,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from esphome.components import esp32
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from .. import http_request_ns
|
||||
|
||||
CODEOWNERS = ["@oarcher"]
|
||||
|
||||
AUTO_LOAD = ["md5"]
|
||||
DEPENDENCIES = ["network"]
|
||||
|
||||
CONF_MD5 = "md5"
|
||||
CONF_MD5_URL = "md5_url"
|
||||
CONF_VERIFY_SSL = "verify_ssl"
|
||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
||||
|
||||
OtaHttpRequestComponent = http_request_ns.class_(
|
||||
"OtaHttpRequestComponent", OTAComponent
|
||||
)
|
||||
OtaHttpRequestComponentArduino = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentArduino", OtaHttpRequestComponent
|
||||
)
|
||||
OtaHttpRequestComponentIDF = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentIDF", OtaHttpRequestComponent
|
||||
)
|
||||
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentFlashAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
def validate_ssl_verification(config):
|
||||
error_message = ""
|
||||
|
||||
if CORE.is_esp32:
|
||||
if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
|
||||
error_message = "ESPHome supports certificate verification only via ESP-IDF"
|
||||
|
||||
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
|
||||
error_message = "ESPHome does not support certificate verification in Arduino"
|
||||
|
||||
if (
|
||||
CORE.is_esp8266
|
||||
and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
|
||||
and config[CONF_VERIFY_SSL]
|
||||
):
|
||||
error_message = "ESPHome does not support certificate verification in Arduino"
|
||||
|
||||
if len(error_message) > 0:
|
||||
raise cv.Invalid(
|
||||
f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _declare_request_class(value):
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(OtaHttpRequestComponentIDF)(value)
|
||||
|
||||
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
||||
return cv.declare_id(OtaHttpRequestComponentArduino)(value)
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): _declare_request_class,
|
||||
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
|
||||
cv.only_on_esp8266, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
|
||||
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
|
||||
cv.positive_not_null_time_period,
|
||||
cv.positive_time_period_milliseconds,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 5, 1),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
),
|
||||
validate_ssl_verification,
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
|
||||
if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
|
||||
cg.add_define(
|
||||
"USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT",
|
||||
timeout_ms,
|
||||
)
|
||||
|
||||
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
||||
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
||||
|
||||
if CORE.is_esp32:
|
||||
if CORE.using_esp_idf:
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
|
||||
config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_TLS_INSECURE",
|
||||
not config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
|
||||
not config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
else:
|
||||
cg.add_library("WiFiClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
cg.add_library("HTTPClient", None)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
|
||||
cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
|
||||
cv.Optional(CONF_MD5): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_PASSWORD): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_USERNAME): cv.templatable(cv.string),
|
||||
cv.Required(CONF_URL): cv.templatable(cv.url),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_MD5, CONF_MD5_URL),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ota_http_request.flash",
|
||||
OtaHttpRequestComponentFlashAction,
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
|
||||
)
|
||||
async def ota_http_request_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
if md5_url := config.get(CONF_MD5_URL):
|
||||
template_ = await cg.templatable(md5_url, args, cg.std_string)
|
||||
cg.add(var.set_md5_url(template_))
|
||||
|
||||
if md5_str := config.get(CONF_MD5):
|
||||
template_ = await cg.templatable(md5_str, args, cg.std_string)
|
||||
cg.add(var.set_md5(template_))
|
||||
|
||||
if password_str := config.get(CONF_PASSWORD):
|
||||
template_ = await cg.templatable(password_str, args, cg.std_string)
|
||||
cg.add(var.set_password(template_))
|
||||
|
||||
if username_str := config.get(CONF_USERNAME):
|
||||
template_ = await cg.templatable(username_str, args, cg.std_string)
|
||||
cg.add(var.set_username(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
|
||||
return var
|
42
esphome/components/http_request/ota/automation.h
Normal file
42
esphome/components/http_request/ota/automation.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
template<typename... Ts> class OtaHttpRequestComponentFlashAction : public Action<Ts...> {
|
||||
public:
|
||||
OtaHttpRequestComponentFlashAction(OtaHttpRequestComponent *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(std::string, md5_url)
|
||||
TEMPLATABLE_VALUE(std::string, md5)
|
||||
TEMPLATABLE_VALUE(std::string, password)
|
||||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(std::string, username)
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->md5_url_.has_value()) {
|
||||
this->parent_->set_md5_url(this->md5_url_.value(x...));
|
||||
}
|
||||
if (this->md5_.has_value()) {
|
||||
this->parent_->set_md5(this->md5_.value(x...));
|
||||
}
|
||||
if (this->password_.has_value()) {
|
||||
this->parent_->set_password(this->password_.value(x...));
|
||||
}
|
||||
if (this->username_.has_value()) {
|
||||
this->parent_->set_username(this->username_.value(x...));
|
||||
}
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
|
||||
this->parent_->flash();
|
||||
// Normally never reached due to reboot
|
||||
}
|
||||
|
||||
protected:
|
||||
OtaHttpRequestComponent *parent_;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
293
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
293
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
@ -0,0 +1,293 @@
|
||||
#include "ota_http_request.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
void OtaHttpRequestComponent::setup() {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request:");
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %llus", this->timeout_ / 1000);
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
ESP_LOGCONFIG(TAG, " ESP8266 SSL support: No");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " ESP8266 SSL support: Yes");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
ESP_LOGCONFIG(TAG, " TLS server verification: Yes");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " TLS server verification: No");
|
||||
#endif
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
ESP_LOGCONFIG(TAG, " Watchdog timeout: %ds", USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT / 1000);
|
||||
#endif
|
||||
};
|
||||
|
||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->md5_url_ = url;
|
||||
this->md5_expected_.clear(); // to be retrieved later
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->url_ = url;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::check_status() {
|
||||
// status can be -1, or HTTP status code
|
||||
if (this->status_ < 100) {
|
||||
ESP_LOGE(TAG, "HTTP server did not respond (error %d)", this->status_);
|
||||
return false;
|
||||
}
|
||||
if (this->status_ >= 310) {
|
||||
ESP_LOGE(TAG, "HTTP error %d", this->status_);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "HTTP status %d", this->status_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::flash() {
|
||||
if (this->url_.empty()) {
|
||||
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting update...");
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
auto ota_status = this->do_ota_();
|
||||
|
||||
switch (ota_status) {
|
||||
case ota::OTA_RESPONSE_OK:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
|
||||
#endif
|
||||
delay(10);
|
||||
App.safe_reboot();
|
||||
break;
|
||||
|
||||
default:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
|
||||
#endif
|
||||
this->md5_computed_.clear(); // will be reset at next attempt
|
||||
this->md5_expected_.clear(); // will be reset at next attempt
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend) {
|
||||
if (this->update_started_) {
|
||||
ESP_LOGV(TAG, "Aborting OTA backend");
|
||||
backend->abort();
|
||||
}
|
||||
ESP_LOGV(TAG, "Aborting HTTP connection");
|
||||
this->http_end();
|
||||
};
|
||||
|
||||
uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
uint8_t buf[this->http_recv_buffer_ + 1];
|
||||
uint32_t last_progress = 0;
|
||||
uint32_t update_start_time = millis();
|
||||
md5::MD5Digest md5_receive;
|
||||
std::unique_ptr<char[]> md5_receive_str(new char[33]);
|
||||
|
||||
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
|
||||
return OTA_MD5_INVALID;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return OTA_BAD_URL;
|
||||
}
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||
this->http_init(url_with_auth);
|
||||
if (!this->check_status()) {
|
||||
this->http_end();
|
||||
return OTA_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
// we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it
|
||||
md5_receive.init();
|
||||
ESP_LOGV(TAG, "MD5Digest initialized");
|
||||
|
||||
ESP_LOGV(TAG, "OTA backend begin");
|
||||
auto backend = ota::make_ota_backend();
|
||||
auto error_code = backend->begin(this->body_length_);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "backend->begin error: %d", error_code);
|
||||
this->cleanup_(std::move(backend));
|
||||
return error_code;
|
||||
}
|
||||
|
||||
this->bytes_read_ = 0;
|
||||
while (this->bytes_read_ < this->body_length_) {
|
||||
// read a maximum of chunk_size bytes into buf. (real read size returned)
|
||||
int bufsize = this->http_read(buf, this->http_recv_buffer_);
|
||||
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", this->bytes_read_, this->body_length_, bufsize);
|
||||
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
|
||||
if (bufsize < 0) {
|
||||
ESP_LOGE(TAG, "Stream closed");
|
||||
this->cleanup_(std::move(backend));
|
||||
return OTA_CONNECTION_ERROR;
|
||||
} else if (bufsize > 0 && bufsize <= this->http_recv_buffer_) {
|
||||
// add read bytes to MD5
|
||||
md5_receive.add(buf, bufsize);
|
||||
|
||||
// write bytes to OTA backend
|
||||
this->update_started_ = true;
|
||||
error_code = backend->write(buf, bufsize);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
// error code explanation available at
|
||||
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
||||
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
||||
this->bytes_read_ - bufsize, this->body_length_);
|
||||
this->cleanup_(std::move(backend));
|
||||
return error_code;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
if ((now - last_progress > 1000) or (this->bytes_read_ == this->body_length_)) {
|
||||
last_progress = now;
|
||||
float percentage = this->bytes_read_ * 100.0f / this->body_length_;
|
||||
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
}
|
||||
} // while
|
||||
|
||||
ESP_LOGI(TAG, "Done in %.0f seconds", float(millis() - update_start_time) / 1000);
|
||||
|
||||
// verify MD5 is as expected and act accordingly
|
||||
md5_receive.calculate();
|
||||
md5_receive.get_hex(md5_receive_str.get());
|
||||
this->md5_computed_ = md5_receive_str.get();
|
||||
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||
this->cleanup_(std::move(backend));
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
backend->set_update_md5(md5_receive_str.get());
|
||||
}
|
||||
|
||||
this->http_end();
|
||||
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
delay(100); // NOLINT
|
||||
|
||||
error_code = backend->end();
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||
this->cleanup_(std::move(backend));
|
||||
return error_code;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Update complete");
|
||||
return ota::OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||
if (this->username_.empty() || this->password_.empty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
auto start_char = url.find("://");
|
||||
if ((start_char == std::string::npos) || (start_char < 4)) {
|
||||
ESP_LOGE(TAG, "Incorrect URL prefix");
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Using basic HTTP authentication");
|
||||
|
||||
start_char += 3; // skip '://' characters
|
||||
auto url_with_auth =
|
||||
url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
|
||||
return url_with_auth;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
if (this->md5_url_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||
this->http_init(url_with_auth);
|
||||
if (!this->check_status()) {
|
||||
this->http_end();
|
||||
return false;
|
||||
}
|
||||
int length = this->body_length_;
|
||||
if (length < 0) {
|
||||
this->http_end();
|
||||
return false;
|
||||
}
|
||||
if (length < MD5_SIZE) {
|
||||
ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE,
|
||||
this->body_length_);
|
||||
this->http_end();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->bytes_read_ = 0;
|
||||
this->md5_expected_.resize(MD5_SIZE);
|
||||
auto read_len = this->http_read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
|
||||
this->http_end();
|
||||
|
||||
return read_len == MD5_SIZE;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
|
||||
if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
|
||||
ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
72
esphome/components/http_request/ota/ota_http_request.h
Normal file
72
esphome/components/http_request/ota/ota_http_request.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.ota";
|
||||
static const uint8_t MD5_SIZE = 32;
|
||||
|
||||
enum OtaHttpRequestError : uint8_t {
|
||||
OTA_MD5_INVALID = 0x10,
|
||||
OTA_BAD_URL = 0x11,
|
||||
OTA_CONNECTION_ERROR = 0x12,
|
||||
};
|
||||
|
||||
class OtaHttpRequestComponent : public ota::OTAComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_md5_url(const std::string &md5_url);
|
||||
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
|
||||
void set_password(const std::string &password) { this->password_ = password; }
|
||||
void set_timeout(const uint64_t timeout) { this->timeout_ = timeout; }
|
||||
void set_url(const std::string &url);
|
||||
void set_username(const std::string &username) { this->username_ = username; }
|
||||
|
||||
std::string md5_computed() { return this->md5_computed_; }
|
||||
std::string md5_expected() { return this->md5_expected_; }
|
||||
|
||||
bool check_status();
|
||||
|
||||
void flash();
|
||||
|
||||
virtual void http_init(const std::string &url){};
|
||||
virtual int http_read(uint8_t *buf, size_t len) { return 0; };
|
||||
virtual void http_end(){};
|
||||
|
||||
protected:
|
||||
void cleanup_(std::unique_ptr<ota::OTABackend> backend);
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
bool secure_() { return this->url_.find("https:") != std::string::npos; };
|
||||
bool validate_url_(const std::string &url);
|
||||
|
||||
std::string md5_computed_{};
|
||||
std::string md5_expected_{};
|
||||
std::string md5_url_{};
|
||||
std::string password_{};
|
||||
std::string username_{};
|
||||
std::string url_{};
|
||||
size_t body_length_ = 0;
|
||||
size_t bytes_read_ = 0;
|
||||
int status_ = -1;
|
||||
uint64_t timeout_ = 0;
|
||||
bool update_started_ = false;
|
||||
const uint16_t http_recv_buffer_ = 256; // the firmware GET chunk size
|
||||
const uint16_t max_http_recv_buffer_ = 512; // internal max http buffer size must be > HTTP_RECV_BUFFER_ (TLS
|
||||
// overhead) and must be a power of two from 512 to 4096
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
134
esphome/components/http_request/ota/ota_http_request_arduino.cpp
Normal file
134
esphome/components/http_request/ota/ota_http_request_arduino.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "ota_http_request.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "ota_http_request_arduino.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/md5/md5.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
struct Header {
|
||||
const char *name;
|
||||
const char *value;
|
||||
};
|
||||
|
||||
void OtaHttpRequestComponentArduino::http_init(const std::string &url) {
|
||||
const char *header_keys[] = {"Content-Length", "Content-Type"};
|
||||
const size_t header_count = sizeof(header_keys) / sizeof(header_keys[0]);
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
if (this->stream_ptr_ == nullptr && this->set_stream_ptr_()) {
|
||||
ESP_LOGE(TAG, "Unable to set client");
|
||||
return;
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#ifdef USE_RP2040
|
||||
this->client_.setInsecure();
|
||||
#endif
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
this->status_ = this->client_.begin(url.c_str());
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
this->status_ = this->client_.begin(*this->stream_ptr_, url.c_str());
|
||||
#endif
|
||||
|
||||
if (!this->status_) {
|
||||
this->client_.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this->client_.setReuse(true);
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
this->client_.collectHeaders(header_keys, header_count);
|
||||
|
||||
// HTTP GET
|
||||
this->status_ = this->client_.GET();
|
||||
|
||||
this->body_length_ = (size_t) this->client_.getSize();
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
if (this->stream_ptr_ == nullptr) {
|
||||
this->set_stream_ptr_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentArduino::http_read(uint8_t *buf, const size_t max_len) {
|
||||
#ifdef USE_ESP8266
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
|
||||
if (!this->secure_()) {
|
||||
ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
|
||||
"in your YAML, or use HTTPS");
|
||||
}
|
||||
#endif // USE_ARDUINO_VERSION_CODE
|
||||
#endif // USE_ESP8266
|
||||
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
// Since arduino8266 >= 3.1 using this->stream_ptr_ is broken (https://github.com/esp8266/Arduino/issues/9035)
|
||||
WiFiClient *stream_ptr = this->client_.getStreamPtr();
|
||||
if (stream_ptr == nullptr) {
|
||||
ESP_LOGE(TAG, "Stream pointer vanished!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int available_data = stream_ptr->available();
|
||||
int bufsize = std::min((int) max_len, available_data);
|
||||
if (bufsize > 0) {
|
||||
stream_ptr->readBytes(buf, bufsize);
|
||||
this->bytes_read_ += bufsize;
|
||||
buf[bufsize] = '\0'; // not fed to ota
|
||||
}
|
||||
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponentArduino::http_end() {
|
||||
watchdog::WatchdogManager wdts;
|
||||
this->client_.end();
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentArduino::set_stream_ptr_() {
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
if (this->secure_()) {
|
||||
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||
this->stream_ptr_ = std::make_unique<WiFiClientSecure>();
|
||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(this->stream_ptr_.get());
|
||||
secure_client->setBufferSizes(this->max_http_recv_buffer_, 512);
|
||||
secure_client->setInsecure();
|
||||
} else {
|
||||
this->stream_ptr_ = std::make_unique<WiFiClient>();
|
||||
}
|
||||
#else
|
||||
ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
|
||||
if (this->secure_()) {
|
||||
ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
|
||||
return -1;
|
||||
}
|
||||
this->stream_ptr_ = std::make_unique<WiFiClient>();
|
||||
#endif // USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
this->stream_ptr_ = std::unique_ptr<WiFiClient>(this->client_.getStreamPtr());
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class OtaHttpRequestComponentArduino : public OtaHttpRequestComponent {
|
||||
public:
|
||||
void http_init(const std::string &url) override;
|
||||
int http_read(uint8_t *buf, size_t len) override;
|
||||
void http_end() override;
|
||||
|
||||
protected:
|
||||
int set_stream_ptr_();
|
||||
HTTPClient client_{};
|
||||
std::unique_ptr<WiFiClient> stream_ptr_;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
86
esphome/components/http_request/ota/ota_http_request_idf.cpp
Normal file
86
esphome/components/http_request/ota/ota_http_request_idf.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include "ota_http_request_idf.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "esp_tls.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
#include "esp_crt_bundle.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
void OtaHttpRequestComponentIDF::http_init(const std::string &url) {
|
||||
App.feed_wdt();
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
esp_http_client_config_t config = {nullptr};
|
||||
config.url = url.c_str();
|
||||
config.method = HTTP_METHOD_GET;
|
||||
config.timeout_ms = (int) this->timeout_;
|
||||
config.buffer_size = this->max_http_recv_buffer_;
|
||||
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||
config.max_authorization_retries = -1;
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
if (this->secure_()) {
|
||||
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
}
|
||||
#endif
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
watchdog::WatchdogManager wdts;
|
||||
this->client_ = esp_http_client_init(&config);
|
||||
if ((this->status_ = esp_http_client_open(this->client_, 0)) == ESP_OK) {
|
||||
this->body_length_ = esp_http_client_fetch_headers(this->client_);
|
||||
this->status_ = esp_http_client_get_status_code(this->client_);
|
||||
}
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentIDF::http_read(uint8_t *buf, const size_t max_len) {
|
||||
watchdog::WatchdogManager wdts;
|
||||
int bufsize = std::min(max_len, this->body_length_ - this->bytes_read_);
|
||||
|
||||
App.feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||
if (read_len > 0) {
|
||||
this->bytes_read_ += bufsize;
|
||||
buf[bufsize] = '\0'; // not fed to ota
|
||||
}
|
||||
|
||||
return read_len;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponentIDF::http_end() {
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
esp_http_client_close(this->client_);
|
||||
esp_http_client_cleanup(this->client_);
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
24
esphome/components/http_request/ota/ota_http_request_idf.h
Normal file
24
esphome/components/http_request/ota/ota_http_request_idf.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esp_http_client.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class OtaHttpRequestComponentIDF : public OtaHttpRequestComponent {
|
||||
public:
|
||||
void http_init(const std::string &url) override;
|
||||
int http_read(uint8_t *buf, size_t len) override;
|
||||
void http_end() override;
|
||||
|
||||
protected:
|
||||
esp_http_client_handle_t client_{};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
71
esphome/components/http_request/ota/watchdog.cpp
Normal file
71
esphome/components/http_request/ota/watchdog.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#ifdef USE_ESP32
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
#include "hardware/watchdog.h"
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
namespace watchdog {
|
||||
|
||||
static const char *const TAG = "watchdog.http_request.ota";
|
||||
|
||||
WatchdogManager::WatchdogManager() {
|
||||
this->saved_timeout_ms_ = this->get_timeout_();
|
||||
this->set_timeout_(USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT);
|
||||
}
|
||||
|
||||
WatchdogManager::~WatchdogManager() { this->set_timeout_(this->saved_timeout_ms_); }
|
||||
|
||||
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
|
||||
ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
esp_task_wdt_config_t wdt_config = {
|
||||
.timeout_ms = timeout_ms,
|
||||
.idle_core_mask = 0x03,
|
||||
.trigger_panic = true,
|
||||
};
|
||||
esp_task_wdt_reconfigure(&wdt_config);
|
||||
#else
|
||||
esp_task_wdt_init(timeout_ms, true);
|
||||
#endif // ESP_IDF_VERSION_MAJOR
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
watchdog_enable(timeout_ms, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t WatchdogManager::get_timeout_() {
|
||||
uint32_t timeout_ms = 0;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
timeout_ms = (uint32_t) CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
timeout_ms = watchdog_get_count() / 1000;
|
||||
#endif
|
||||
|
||||
ESP_LOGVV(TAG, "get_timeout: %" PRIu32 "ms", timeout_ms);
|
||||
|
||||
return timeout_ms;
|
||||
}
|
||||
|
||||
} // namespace watchdog
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
#endif
|
27
esphome/components/http_request/ota/watchdog.h
Normal file
27
esphome/components/http_request/ota/watchdog.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
namespace watchdog {
|
||||
|
||||
class WatchdogManager {
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
public:
|
||||
WatchdogManager();
|
||||
~WatchdogManager();
|
||||
|
||||
private:
|
||||
uint32_t get_timeout_();
|
||||
void set_timeout_(uint32_t timeout_ms);
|
||||
|
||||
uint32_t saved_timeout_ms_{0};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace watchdog
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -27,6 +27,11 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
this->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) {
|
||||
this->is_announcement_ = true;
|
||||
}
|
||||
|
||||
if (call.get_volume().has_value()) {
|
||||
this->volume = call.get_volume().value();
|
||||
this->set_volume_(volume);
|
||||
@ -171,9 +176,8 @@ void I2SAudioMediaPlayer::start_() {
|
||||
if (this->current_url_.has_value()) {
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (this->is_announcement_.has_value()) {
|
||||
this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (this->is_announcement_) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
@ -202,6 +206,7 @@ void I2SAudioMediaPlayer::stop_() {
|
||||
this->high_freq_.stop();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
||||
this->publish_state();
|
||||
this->is_announcement_ = false;
|
||||
}
|
||||
|
||||
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
|
||||
|
@ -78,7 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
optional<std::string> current_url_{};
|
||||
optional<bool> is_announcement_{};
|
||||
bool is_announcement_{false};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -47,6 +47,12 @@ ILI9XXXDisplay = ili9xxx_ns.class_(
|
||||
display.DisplayBuffer,
|
||||
)
|
||||
|
||||
PixelMode = ili9xxx_ns.enum("PixelMode")
|
||||
PIXEL_MODES = {
|
||||
"16bit": PixelMode.PIXEL_MODE_16,
|
||||
"18bit": PixelMode.PIXEL_MODE_18,
|
||||
}
|
||||
|
||||
ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")
|
||||
ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
@ -68,6 +74,7 @@ MODELS = {
|
||||
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
||||
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
|
||||
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
|
||||
"CUSTOM": ILI9XXXDisplay,
|
||||
}
|
||||
|
||||
COLOR_ORDERS = {
|
||||
@ -80,14 +87,37 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
|
||||
CONF_LED_PIN = "led_pin"
|
||||
CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
|
||||
CONF_INVERT_DISPLAY = "invert_display"
|
||||
CONF_PIXEL_MODE = "pixel_mode"
|
||||
CONF_INIT_SEQUENCE = "init_sequence"
|
||||
|
||||
|
||||
def cmd(c, *args):
|
||||
"""
|
||||
Create a command sequence
|
||||
:param c: The command (8 bit)
|
||||
:param args: zero or more arguments (8 bit values)
|
||||
:return: a list with the command, the argument count and the arguments
|
||||
"""
|
||||
return [c, len(args)] + list(args)
|
||||
|
||||
|
||||
def map_sequence(value):
|
||||
"""
|
||||
An initialisation sequence is a literal array of data bytes.
|
||||
The format is a repeated sequence of [CMD, <data>]
|
||||
"""
|
||||
if len(value) == 0:
|
||||
raise cv.Invalid("Empty sequence")
|
||||
return cmd(*value)
|
||||
|
||||
|
||||
def _validate(config):
|
||||
if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get(
|
||||
CONF_COLOR_PALETTE_IMAGES
|
||||
if (
|
||||
config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE"
|
||||
and CONF_COLOR_PALETTE_IMAGES not in config
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette"
|
||||
"IMAGE_ADAPTIVE palette requires at least one 'color_palette_images' entry"
|
||||
)
|
||||
if (
|
||||
config.get(CONF_COLOR_PALETTE_IMAGES)
|
||||
@ -96,7 +126,8 @@ def _validate(config):
|
||||
raise cv.Invalid(
|
||||
"Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'"
|
||||
)
|
||||
if CORE.is_esp8266 and config.get(CONF_MODEL) not in [
|
||||
model = config[CONF_MODEL]
|
||||
if CORE.is_esp8266 and model not in [
|
||||
"M5STACK",
|
||||
"TFT_2.4",
|
||||
"TFT_2.4R",
|
||||
@ -104,9 +135,12 @@ def _validate(config):
|
||||
"ILI9342",
|
||||
"ST7789V",
|
||||
]:
|
||||
raise cv.Invalid(
|
||||
"Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard"
|
||||
)
|
||||
raise cv.Invalid("Selected model can't run on ESP8266.")
|
||||
|
||||
if model == "CUSTOM":
|
||||
if CONF_INIT_SEQUENCE not in config or CONF_DIMENSIONS not in config:
|
||||
raise cv.Invalid("CUSTOM model requires init_sequence and dimensions")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@ -116,6 +150,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),
|
||||
cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"),
|
||||
cv.Optional(CONF_PIXEL_MODE): cv.enum(PIXEL_MODES),
|
||||
cv.Optional(CONF_DIMENSIONS): cv.Any(
|
||||
cv.dimensions,
|
||||
cv.Schema(
|
||||
@ -150,6 +185,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
@ -167,6 +203,14 @@ async def to_code(config):
|
||||
await spi.register_spi_device(var, config)
|
||||
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
|
||||
cg.add(var.set_dc_pin(dc))
|
||||
if init_sequences := config.get(CONF_INIT_SEQUENCE):
|
||||
sequence = []
|
||||
for seq in init_sequences:
|
||||
sequence.extend(seq)
|
||||
cg.add(var.add_init_sequence(sequence))
|
||||
|
||||
if pixel_mode := config.get(CONF_PIXEL_MODE):
|
||||
cg.add(var.set_pixel_mode(pixel_mode))
|
||||
if CONF_COLOR_ORDER in config:
|
||||
cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
|
||||
if CONF_TRANSFORM in config:
|
||||
|
@ -34,7 +34,26 @@ void ILI9XXXDisplay::setup() {
|
||||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||
|
||||
this->setup_pins_();
|
||||
this->init_lcd_();
|
||||
this->init_lcd_(this->init_sequence_);
|
||||
this->init_lcd_(this->extra_init_sequence_.data());
|
||||
switch (this->pixel_mode_) {
|
||||
case PIXEL_MODE_16:
|
||||
if (this->is_18bitdisplay_) {
|
||||
this->command(ILI9XXX_PIXFMT);
|
||||
this->data(0x55);
|
||||
this->is_18bitdisplay_ = false;
|
||||
}
|
||||
break;
|
||||
case PIXEL_MODE_18:
|
||||
if (!this->is_18bitdisplay_) {
|
||||
this->command(ILI9XXX_PIXFMT);
|
||||
this->data(0x66);
|
||||
this->is_18bitdisplay_ = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this->set_madctl();
|
||||
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
|
||||
@ -203,7 +222,6 @@ void ILI9XXXDisplay::update() {
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::display_() {
|
||||
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
||||
// check if something was displayed
|
||||
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
||||
return;
|
||||
@ -231,6 +249,7 @@ void ILI9XXXDisplay::display_() {
|
||||
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Doing multiple write");
|
||||
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
||||
size_t rem = h * w; // remaining number of pixels to write
|
||||
set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
|
||||
size_t idx = 0; // index into transfer_buffer
|
||||
@ -247,7 +266,7 @@ void ILI9XXXDisplay::display_() {
|
||||
display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_));
|
||||
break;
|
||||
default: // case BITS_16:
|
||||
color_val = (buffer_[pos * 2] << 8) + buffer_[pos * 2 + 1];
|
||||
color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1];
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
@ -259,7 +278,7 @@ void ILI9XXXDisplay::display_() {
|
||||
put16_be(transfer_buffer + idx, color_val);
|
||||
idx += 2;
|
||||
}
|
||||
if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) {
|
||||
if (idx == sizeof(transfer_buffer)) {
|
||||
this->write_array(transfer_buffer, idx);
|
||||
idx = 0;
|
||||
App.feed_wdt();
|
||||
@ -293,22 +312,52 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
|
||||
// if color mapping or software rotation is required, hand this off to the parent implementation. This will
|
||||
// do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
|
||||
// configured the renderer well.
|
||||
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian ||
|
||||
this->is_18bitdisplay_) {
|
||||
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
|
||||
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||
x_pad);
|
||||
}
|
||||
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
|
||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||
auto stride = x_offset + w + x_pad;
|
||||
if (!this->is_18bitdisplay_) {
|
||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||
// we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother
|
||||
this->write_array(ptr, w * h * 2);
|
||||
} else {
|
||||
auto stride = x_offset + w + x_pad;
|
||||
for (size_t y = 0; y != h; y++) {
|
||||
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 18 bit mode
|
||||
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4];
|
||||
ESP_LOGV(TAG, "Doing multiple write");
|
||||
size_t rem = h * w; // remaining number of pixels to write
|
||||
size_t idx = 0; // index into transfer_buffer
|
||||
size_t pixel = 0; // pixel number offset
|
||||
ptr += (y_offset * stride + x_offset) * 2;
|
||||
while (rem-- != 0) {
|
||||
uint8_t hi_byte = *ptr++;
|
||||
uint8_t lo_byte = *ptr++;
|
||||
transfer_buffer[idx++] = hi_byte & 0xF8; // Blue
|
||||
transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green
|
||||
transfer_buffer[idx++] = lo_byte << 3; // Red
|
||||
if (idx == sizeof(transfer_buffer)) {
|
||||
this->write_array(transfer_buffer, idx);
|
||||
idx = 0;
|
||||
App.feed_wdt();
|
||||
}
|
||||
// end of line? Skip to the next.
|
||||
if (++pixel == w) {
|
||||
pixel = 0;
|
||||
ptr += (x_pad + x_offset) * 2;
|
||||
}
|
||||
}
|
||||
// flush any balance.
|
||||
if (idx != 0) {
|
||||
this->write_array(transfer_buffer, idx);
|
||||
}
|
||||
}
|
||||
this->end_data_();
|
||||
}
|
||||
|
||||
@ -356,10 +405,11 @@ void ILI9XXXDisplay::reset_() {
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::init_lcd_() {
|
||||
void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
|
||||
if (addr == nullptr)
|
||||
return;
|
||||
uint8_t cmd, x, num_args;
|
||||
const uint8_t *addr = this->init_sequence_;
|
||||
while ((cmd = *addr++) > 0) {
|
||||
while ((cmd = *addr++) != 0) {
|
||||
x = *addr++;
|
||||
num_args = x & 0x7F;
|
||||
this->send_command(cmd, addr, num_args);
|
||||
|
@ -17,6 +17,12 @@ enum ILI9XXXColorMode {
|
||||
BITS_16 = 0x10,
|
||||
};
|
||||
|
||||
enum PixelMode {
|
||||
PIXEL_MODE_UNSPECIFIED,
|
||||
PIXEL_MODE_16,
|
||||
PIXEL_MODE_18,
|
||||
};
|
||||
|
||||
class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
|
||||
@ -52,6 +58,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
void add_init_sequence(const std::vector<uint8_t> &sequence) { this->extra_init_sequence_ = sequence; }
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
float get_setup_priority() const override;
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
@ -73,6 +80,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
|
||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||
void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
|
||||
void set_pixel_mode(PixelMode mode) { this->pixel_mode_ = mode; }
|
||||
|
||||
void update() override;
|
||||
|
||||
@ -99,11 +107,12 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
|
||||
virtual void set_madctl();
|
||||
void display_();
|
||||
void init_lcd_();
|
||||
void init_lcd_(const uint8_t *addr);
|
||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
||||
void reset_();
|
||||
|
||||
uint8_t const *init_sequence_{};
|
||||
std::vector<uint8_t> extra_init_sequence_;
|
||||
int16_t width_{0}; ///< Display width as modified by current rotation
|
||||
int16_t height_{0}; ///< Display height as modified by current rotation
|
||||
int16_t offset_x_{0};
|
||||
@ -112,7 +121,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
uint16_t y_low_{0};
|
||||
uint16_t x_high_{0};
|
||||
uint16_t y_high_{0};
|
||||
const uint8_t *palette_;
|
||||
const uint8_t *palette_{};
|
||||
|
||||
ILI9XXXColorMode buffer_color_mode_{BITS_16};
|
||||
|
||||
@ -133,6 +142,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||
bool prossing_update_ = false;
|
||||
bool need_update_ = false;
|
||||
bool is_18bitdisplay_ = false;
|
||||
PixelMode pixel_mode_{};
|
||||
bool pre_invertcolors_ = false;
|
||||
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
|
||||
bool swap_xy_{};
|
||||
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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 &_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;
|
||||
|
@ -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());
|
||||
|
@ -52,12 +52,12 @@ float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
|
||||
}
|
||||
|
||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
|
||||
ESP_LOGV(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
|
||||
for (int i = MAX_RES_BITS; i >= 1; i--) {
|
||||
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
|
||||
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
|
||||
if (min_frequency <= frequency && frequency <= max_frequency) {
|
||||
ESP_LOGD(TAG, "Resolution calculated as %d", i);
|
||||
ESP_LOGV(TAG, "Resolution calculated as %d", i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -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,10 +57,15 @@ RESTORE_MODES = {
|
||||
"RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON,
|
||||
}
|
||||
|
||||
LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
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.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="_"
|
||||
),
|
||||
@ -79,6 +85,7 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -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,7 +31,10 @@ 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(
|
||||
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(
|
||||
@ -44,6 +48,7 @@ LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ext
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -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]):
|
||||
|
@ -112,11 +112,18 @@ HARDWARE_UART_TO_UART_SELECTION = {
|
||||
}
|
||||
|
||||
HARDWARE_UART_TO_SERIAL = {
|
||||
PLATFORM_ESP8266: {
|
||||
UART0: cg.global_ns.Serial,
|
||||
UART0_SWAP: cg.global_ns.Serial,
|
||||
UART1: cg.global_ns.Serial1,
|
||||
UART2: cg.global_ns.Serial2,
|
||||
DEFAULT: cg.global_ns.Serial,
|
||||
},
|
||||
PLATFORM_RP2040: {
|
||||
UART0: cg.global_ns.Serial1,
|
||||
UART1: cg.global_ns.Serial2,
|
||||
USB_CDC: cg.global_ns.Serial,
|
||||
},
|
||||
}
|
||||
|
||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
@ -244,8 +251,14 @@ async def to_code(config):
|
||||
is_at_least_very_verbose = this_severity >= very_verbose_severity
|
||||
has_serial_logging = baud_rate != 0
|
||||
|
||||
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
|
||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
|
||||
if (
|
||||
(CORE.is_esp8266 or CORE.is_rp2040)
|
||||
and has_serial_logging
|
||||
and is_at_least_verbose
|
||||
):
|
||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[CORE.target_platform][
|
||||
config.get(CONF_HARDWARE_UART)
|
||||
]
|
||||
cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
|
||||
cg.add_build_flag("-DLWIP_DEBUG")
|
||||
DEBUG_COMPONENTS = {
|
||||
|
@ -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"
|
||||
|
1
esphome/components/ltr_als_ps/__init__.py
Normal file
1
esphome/components/ltr_als_ps/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@latonita"]
|
519
esphome/components/ltr_als_ps/ltr_als_ps.cpp
Normal file
519
esphome/components/ltr_als_ps/ltr_als_ps.cpp
Normal 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
|
184
esphome/components/ltr_als_ps/ltr_als_ps.h
Normal file
184
esphome/components/ltr_als_ps/ltr_als_ps.h
Normal 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
|
275
esphome/components/ltr_als_ps/ltr_definitions.h
Normal file
275
esphome/components/ltr_als_ps/ltr_definitions.h
Normal 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
|
271
esphome/components/ltr_als_ps/sensor.py
Normal file
271
esphome/components/ltr_als_ps/sensor.py
Normal 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]))
|
@ -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 4–11 (data bits D0–D7)
|
||||
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4-11 (data bits D0-D7)
|
||||
};
|
||||
|
||||
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,7 +109,7 @@ 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,
|
||||
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) {
|
||||
@ -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);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user