Merge branch 'dev' into dev

This commit is contained in:
CptSkippy 2024-06-12 07:09:26 -07:00 committed by GitHub
commit 7d82fd7663
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
119 changed files with 2758 additions and 950 deletions

View file

@ -454,8 +454,8 @@ jobs:
matrix: matrix:
file: ${{ fromJson(needs.list-components.outputs.components) }} file: ${{ fromJson(needs.list-components.outputs.components) }}
steps: steps:
- name: Install libsodium - name: Install dependencies
run: sudo apt-get install libsodium-dev run: sudo apt-get install libsodium-dev libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.6
@ -508,8 +508,8 @@ jobs:
- name: List components - name: List components
run: echo ${{ matrix.components }} run: echo ${{ matrix.components }}
- name: Install libsodium - name: Install dependencies
run: sudo apt-get install libsodium-dev run: sudo apt-get install libsodium-dev libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.6

View file

@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1 esphome/components/dac7678/* @NickB1
esphome/components/daikin_arc/* @MagicBear esphome/components/daikin_arc/* @MagicBear
esphome/components/daikin_brc/* @hagak esphome/components/daikin_brc/* @hagak
esphome/components/dallas_temp/* @ssieb
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @jesserockz @rfdarter esphome/components/datetime/* @jesserockz @rfdarter
@ -144,6 +145,7 @@ esphome/components/gdk101/* @Szewcson
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core
esphome/components/gpio/one_wire/* @ssieb
esphome/components/gps/* @coogle esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/graphical_display_menu/* @MrMDavidson
@ -172,6 +174,7 @@ esphome/components/host/time/* @clydebarrow
esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M esphome/components/hte501/* @Stock-M
esphome/components/http_request/ota/* @oarcher esphome/components/http_request/ota/* @oarcher
esphome/components/http_request/update/* @jesserockz
esphome/components/htu31d/* @betterengineering esphome/components/htu31d/* @betterengineering
esphome/components/hydreon_rgxx/* @functionpointer esphome/components/hydreon_rgxx/* @functionpointer
esphome/components/hyt271/* @Philippe12 esphome/components/hyt271/* @Philippe12
@ -270,6 +273,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81 esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/ota/* @esphome/core esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931 esphome/components/pca6416a/* @Mat931
@ -317,6 +321,7 @@ esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny esphome/components/scd4x/* @martgras @sjtrny
esphome/components/script/* @esphome/core esphome/components/script/* @esphome/core
esphome/components/sdl/* @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu esphome/components/seeed_mr24hpc1/* @limengdu
@ -411,6 +416,7 @@ esphome/components/uart/button/* @ssieb
esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter esphome/components/ultrasonic/* @OttoWinter
esphome/components/update/* @jesserockz
esphome/components/uponor_smatrix/* @kroimon esphome/components/uponor_smatrix/* @kroimon
esphome/components/valve/* @esphome/core esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb esphome/components/vbus/* @ssieb

View file

@ -81,7 +81,8 @@ RUN \
fi; \ fi; \
pip3 install \ pip3 install \
--break-system-packages --no-cache-dir \ --break-system-packages --no-cache-dir \
platformio==6.1.13 \ # Keep platformio version in sync with requirements.txt
platformio==6.1.15 \
# Change some platformio settings # Change some platformio settings
&& platformio settings set enable_telemetry No \ && platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \ && platformio settings set check_platformio_interval 1000000 \

View file

@ -488,6 +488,15 @@ def command_run(args, config):
if exit_code != 0: if exit_code != 0:
return exit_code return exit_code
_LOGGER.info("Successfully compiled program.") _LOGGER.info("Successfully compiled program.")
if CORE.is_host:
from esphome.platformio_api import get_idedata
idedata = get_idedata(config)
if idedata is None:
return 1
program_path = idedata.raw["prog_path"]
return run_external_process(program_path)
port = choose_upload_log_host( port = choose_upload_log_host(
default=args.device, default=args.device,
check_default=None, check_default=None,

View file

@ -3,7 +3,13 @@ import logging
from esphome import automation, core from esphome import automation, core
from esphome.components import font from esphome.components import font
import esphome.components.image as espImage import esphome.components.image as espImage
from esphome.components.image import CONF_USE_TRANSPARENCY from esphome.components.image import (
CONF_USE_TRANSPARENCY,
LOCAL_SCHEMA,
WEB_SCHEMA,
SOURCE_WEB,
SOURCE_LOCAL,
)
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
@ -13,6 +19,9 @@ from esphome.const import (
CONF_REPEAT, CONF_REPEAT,
CONF_RESIZE, CONF_RESIZE,
CONF_TYPE, CONF_TYPE,
CONF_SOURCE,
CONF_PATH,
CONF_URL,
) )
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
) )
TYPED_FILE_SCHEMA = cv.typed_schema(
{
SOURCE_LOCAL: LOCAL_SCHEMA,
SOURCE_WEB: WEB_SCHEMA,
},
key=CONF_SOURCE,
)
def _file_schema(value):
if isinstance(value, str):
return validate_file_shorthand(value)
return TYPED_FILE_SCHEMA(value)
FILE_SCHEMA = cv.Schema(_file_schema)
def validate_file_shorthand(value):
value = cv.string_strict(value)
if value.startswith("http://") or value.startswith("https://"):
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_WEB,
CONF_URL: value,
}
)
return FILE_SCHEMA(
{
CONF_SOURCE: SOURCE_LOCAL,
CONF_PATH: value,
}
)
def validate_cross_dependencies(config): def validate_cross_dependencies(config):
""" """
@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
cv.All( cv.All(
{ {
cv.Required(CONF_ID): cv.declare_id(Animation_), cv.Required(CONF_ID): cv.declare_id(Animation_),
cv.Required(CONF_FILE): cv.file_, cv.Required(CONF_FILE): FILE_SCHEMA,
cv.Optional(CONF_RESIZE): cv.dimensions, cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum( cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
espImage.IMAGE_TYPE, upper=True espImage.IMAGE_TYPE, upper=True
@ -124,7 +167,11 @@ async def animation_action_to_code(config, action_id, template_arg, args):
async def to_code(config): async def to_code(config):
from PIL import Image from PIL import Image
path = CORE.relative_config_path(config[CONF_FILE]) conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
path = CORE.relative_config_path(conf_file[CONF_PATH])
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = espImage.compute_local_image_path(conf_file).as_posix()
try: try:
image = Image.open(path) image = Image.open(path)
except Exception as e: except Exception as e:

View file

@ -48,6 +48,7 @@ service APIConnection {
rpc date_command (DateCommandRequest) returns (void) {} rpc date_command (DateCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {} rpc time_command (TimeCommandRequest) returns (void) {}
rpc datetime_command (DateTimeCommandRequest) returns (void) {} rpc datetime_command (DateTimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
@ -1837,3 +1838,46 @@ message DateTimeCommandRequest {
fixed32 key = 1; fixed32 key = 1;
fixed32 epoch_seconds = 2; fixed32 epoch_seconds = 2;
} }
// ==================== UPDATE ====================
message ListEntitiesUpdateResponse {
option (id) = 116;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
}
message UpdateStateResponse {
option (id) = 117;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_UPDATE";
option (no_delay) = true;
fixed32 key = 1;
bool missing_state = 2;
bool in_progress = 3;
bool has_progress = 4;
float progress = 5;
string current_version = 6;
string latest_version = 7;
string title = 8;
string release_summary = 9;
string release_url = 10;
}
message UpdateCommandRequest {
option (id) = 118;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_UPDATE";
option (no_delay) = true;
fixed32 key = 1;
bool install = 2;
}

View file

@ -1287,6 +1287,51 @@ bool APIConnection::send_event_info(event::Event *event) {
} }
#endif #endif
#ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) {
if (!this->state_subscription_)
return false;
UpdateStateResponse resp{};
resp.key = update->get_object_id_hash();
resp.missing_state = !update->has_state();
if (update->has_state()) {
resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
if (update->update_info.has_progress) {
resp.has_progress = true;
resp.progress = update->update_info.progress;
}
resp.current_version = update->update_info.current_version;
resp.latest_version = update->update_info.latest_version;
resp.title = update->update_info.title;
resp.release_summary = update->update_info.summary;
resp.release_url = update->update_info.release_url;
}
return this->send_update_state_response(resp);
}
bool APIConnection::send_update_info(update::UpdateEntity *update) {
ListEntitiesUpdateResponse msg;
msg.key = update->get_object_id_hash();
msg.object_id = update->get_object_id();
if (update->has_own_name())
msg.name = update->get_name();
msg.unique_id = get_default_unique_id("update", update);
msg.icon = update->get_icon();
msg.disabled_by_default = update->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
msg.device_class = update->get_device_class();
return this->send_list_entities_update_response(msg);
}
void APIConnection::update_command(const UpdateCommandRequest &msg) {
update::UpdateEntity *update = App.get_update_by_key(msg.key);
if (update == nullptr)
return;
update->perform();
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) { bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level) if (this->log_subscription_ < level)
return false; return false;

View file

@ -164,6 +164,12 @@ class APIConnection : public APIServerConnection {
bool send_event_info(event::Event *event); bool send_event_info(event::Event *event);
#endif #endif
#ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update);
bool send_update_info(update::UpdateEntity *update);
void update_command(const UpdateCommandRequest &msg) override;
#endif
void on_disconnect_response(const DisconnectResponse &value) override; void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override { void on_ping_response(const PingResponse &value) override {
// we initiated ping // we initiated ping

View file

@ -8376,6 +8376,262 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesUpdateResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append("}");
}
#endif
bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->missing_state = value.as_bool();
return true;
}
case 3: {
this->in_progress = value.as_bool();
return true;
}
case 4: {
this->has_progress = value.as_bool();
return true;
}
default:
return false;
}
}
bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 6: {
this->current_version = value.as_string();
return true;
}
case 7: {
this->latest_version = value.as_string();
return true;
}
case 8: {
this->title = value.as_string();
return true;
}
case 9: {
this->release_summary = value.as_string();
return true;
}
case 10: {
this->release_url = value.as_string();
return true;
}
default:
return false;
}
}
bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 5: {
this->progress = value.as_float();
return true;
}
default:
return false;
}
}
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_bool(3, this->in_progress);
buffer.encode_bool(4, this->has_progress);
buffer.encode_float(5, this->progress);
buffer.encode_string(6, this->current_version);
buffer.encode_string(7, this->latest_version);
buffer.encode_string(8, this->title);
buffer.encode_string(9, this->release_summary);
buffer.encode_string(10, this->release_url);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void UpdateStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("UpdateStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" missing_state: ");
out.append(YESNO(this->missing_state));
out.append("\n");
out.append(" in_progress: ");
out.append(YESNO(this->in_progress));
out.append("\n");
out.append(" has_progress: ");
out.append(YESNO(this->has_progress));
out.append("\n");
out.append(" progress: ");
sprintf(buffer, "%g", this->progress);
out.append(buffer);
out.append("\n");
out.append(" current_version: ");
out.append("'").append(this->current_version).append("'");
out.append("\n");
out.append(" latest_version: ");
out.append("'").append(this->latest_version).append("'");
out.append("\n");
out.append(" title: ");
out.append("'").append(this->title).append("'");
out.append("\n");
out.append(" release_summary: ");
out.append("'").append(this->release_summary).append("'");
out.append("\n");
out.append(" release_url: ");
out.append("'").append(this->release_url).append("'");
out.append("\n");
out.append("}");
}
#endif
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->install = value.as_bool();
return true;
}
default:
return false;
}
}
bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->install);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void UpdateCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("UpdateCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" install: ");
out.append(YESNO(this->install));
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -2130,6 +2130,61 @@ class DateTimeCommandRequest : public ProtoMessage {
protected: protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
}; };
class ListEntitiesUpdateResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class UpdateStateResponse : public ProtoMessage {
public:
uint32_t key{0};
bool missing_state{false};
bool in_progress{false};
bool has_progress{false};
float progress{0.0f};
std::string current_version{};
std::string latest_version{};
std::string title{};
std::string release_summary{};
std::string release_url{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class UpdateCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
bool install{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -611,6 +611,24 @@ bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateR
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
#endif #endif
#ifdef USE_UPDATE
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
}
#endif
#ifdef USE_UPDATE
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<UpdateStateResponse>(msg, 117);
}
#endif
#ifdef USE_UPDATE
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -1106,6 +1124,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
#endif #endif
this->on_voice_assistant_timer_event_response(msg); this->on_voice_assistant_timer_event_response(msg);
#endif
break;
}
case 118: {
#ifdef USE_UPDATE
UpdateCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
#endif
this->on_update_command_request(msg);
#endif #endif
break; break;
} }
@ -1434,6 +1463,19 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
this->datetime_command(msg); this->datetime_command(msg);
} }
#endif #endif
#ifdef USE_UPDATE
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->update_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View file

@ -306,6 +306,15 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){}; virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif
#ifdef USE_UPDATE
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
#endif
#ifdef USE_UPDATE
bool send_update_state_response(const UpdateStateResponse &msg);
#endif
#ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){};
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -373,6 +382,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0; virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_UPDATE
virtual void update_command(const UpdateCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@ -471,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override; void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif #endif
#ifdef USE_UPDATE
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif

View file

@ -334,6 +334,13 @@ void APIServer::on_event(event::Event *obj, const std::string &event_type) {
} }
#endif #endif
#ifdef USE_UPDATE
void APIServer::on_update(update::UpdateEntity *obj) {
for (auto &c : this->clients_)
c->send_update_state(obj);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; } void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View file

@ -102,6 +102,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_EVENT #ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override; void on_event(event::Event *obj, const std::string &event_type) override;
#endif #endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
#endif
bool is_connected() const; bool is_connected() const;

View file

@ -98,6 +98,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
#ifdef USE_EVENT #ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); } bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
#endif #endif
#ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -75,6 +75,9 @@ class ListEntitiesIterator : public ComponentIterator {
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *event) override; bool on_event(event::Event *event) override;
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
#endif #endif
bool on_end() override; bool on_end() override;

View file

@ -77,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel); return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
} }
#endif #endif
#ifdef USE_UPDATE
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
#endif
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {} InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
} // namespace api } // namespace api

View file

@ -72,6 +72,9 @@ class InitialStateIterator : public ComponentIterator {
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; }; bool on_event(event::Event *event) override { return true; };
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
#endif #endif
protected: protected:
APIConnection *client_; APIConnection *client_;

View file

@ -6,18 +6,24 @@ namespace climate_ir_lg {
static const char *const TAG = "climate.climate_ir_lg"; static const char *const TAG = "climate.climate_ir_lg";
const uint32_t COMMAND_ON = 0x00000; // Commands
const uint32_t COMMAND_ON_AI = 0x03000; const uint32_t COMMAND_MASK = 0xFF000;
const uint32_t COMMAND_COOL = 0x08000;
const uint32_t COMMAND_HEAT = 0x0C000;
const uint32_t COMMAND_OFF = 0xC0000; const uint32_t COMMAND_OFF = 0xC0000;
const uint32_t COMMAND_SWING = 0x10000; const uint32_t COMMAND_SWING = 0x10000;
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
const uint32_t COMMAND_AUTO = 0x0B000;
const uint32_t COMMAND_DRY_FAN = 0x09000;
const uint32_t COMMAND_MASK = 0xFF000; const uint32_t COMMAND_ON_COOL = 0x00000;
const uint32_t COMMAND_ON_DRY = 0x01000;
const uint32_t COMMAND_ON_FAN_ONLY = 0x02000;
const uint32_t COMMAND_ON_AI = 0x03000;
const uint32_t COMMAND_ON_HEAT = 0x04000;
const uint32_t COMMAND_COOL = 0x08000;
const uint32_t COMMAND_DRY = 0x09000;
const uint32_t COMMAND_FAN_ONLY = 0x0A000;
const uint32_t COMMAND_AI = 0x0B000;
const uint32_t COMMAND_HEAT = 0x0C000;
// Fan speed
const uint32_t FAN_MASK = 0xF0; const uint32_t FAN_MASK = 0xF0;
const uint32_t FAN_AUTO = 0x50; const uint32_t FAN_AUTO = 0x50;
const uint32_t FAN_MIN = 0x00; const uint32_t FAN_MIN = 0x00;
@ -35,28 +41,28 @@ void LgIrClimate::transmit_state() {
uint32_t remote_state = 0x8800000; uint32_t remote_state = 0x8800000;
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_); // ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
// Set command
if (send_swing_cmd_) { if (send_swing_cmd_) {
send_swing_cmd_ = false; send_swing_cmd_ = false;
remote_state |= COMMAND_SWING; remote_state |= COMMAND_SWING;
} else { } else {
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
remote_state |= COMMAND_ON_AI;
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
remote_state |= COMMAND_ON;
this->mode = climate::CLIMATE_MODE_COOL;
} else {
switch (this->mode) { switch (this->mode) {
case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_COOL:
remote_state |= COMMAND_COOL; remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
break;
case climate::CLIMATE_MODE_HEAT:
remote_state |= COMMAND_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= COMMAND_AUTO;
break; break;
case climate::CLIMATE_MODE_DRY: case climate::CLIMATE_MODE_DRY:
remote_state |= COMMAND_DRY_FAN; remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
break;
case climate::CLIMATE_MODE_HEAT:
remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
break; break;
case climate::CLIMATE_MODE_OFF: case climate::CLIMATE_MODE_OFF:
default: default:
@ -64,14 +70,15 @@ void LgIrClimate::transmit_state() {
break; break;
} }
} }
mode_before_ = this->mode; mode_before_ = this->mode;
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode); ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
// Set fan speed
if (this->mode == climate::CLIMATE_MODE_OFF) { if (this->mode == climate::CLIMATE_MODE_OFF) {
remote_state |= FAN_AUTO; remote_state |= FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY || } else {
this->mode == climate::CLIMATE_MODE_HEAT) {
switch (this->fan_mode.value()) { switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_HIGH: case climate::CLIMATE_FAN_HIGH:
remote_state |= FAN_MAX; remote_state |= FAN_MAX;
@ -89,15 +96,12 @@ void LgIrClimate::transmit_state() {
} }
} }
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { // Set temperature
this->fan_mode = climate::CLIMATE_FAN_AUTO;
// remote_state |= FAN_MODE_AUTO_DRY;
}
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) { if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX)); auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
remote_state |= ((temp - 15) << TEMP_SHIFT); remote_state |= ((temp - 15) << TEMP_SHIFT);
} }
}
transmit_(remote_state); transmit_(remote_state);
this->publish_state(); this->publish_state();
} }
@ -125,37 +129,42 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
if ((remote_state & 0xFF00000) != 0x8800000) if ((remote_state & 0xFF00000) != 0x8800000)
return false; return false;
if ((remote_state & COMMAND_MASK) == COMMAND_ON) { // Get command
this->mode = climate::CLIMATE_MODE_COOL;
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
}
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
this->mode = climate::CLIMATE_MODE_OFF; this->mode = climate::CLIMATE_MODE_OFF;
} else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) { } else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
this->swing_mode = this->swing_mode =
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
} else { } else {
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) { switch (remote_state & COMMAND_MASK) {
this->mode = climate::CLIMATE_MODE_HEAT_COOL; case COMMAND_DRY:
} else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { case COMMAND_ON_DRY:
this->mode = climate::CLIMATE_MODE_DRY; this->mode = climate::CLIMATE_MODE_DRY;
} else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { break;
case COMMAND_FAN_ONLY:
case COMMAND_ON_FAN_ONLY:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
case COMMAND_AI:
case COMMAND_ON_AI:
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case COMMAND_HEAT:
case COMMAND_ON_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT; this->mode = climate::CLIMATE_MODE_HEAT;
} else { break;
case COMMAND_COOL:
case COMMAND_ON_COOL:
default:
this->mode = climate::CLIMATE_MODE_COOL; this->mode = climate::CLIMATE_MODE_COOL;
break;
} }
// Temperature // Get fan speed
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
// Fan Speed
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
this->fan_mode = climate::CLIMATE_FAN_AUTO; this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
this->mode == climate::CLIMATE_MODE_DRY) { this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_HEAT) {
if ((remote_state & FAN_MASK) == FAN_AUTO) { if ((remote_state & FAN_MASK) == FAN_AUTO) {
this->fan_mode = climate::CLIMATE_FAN_AUTO; this->fan_mode = climate::CLIMATE_FAN_AUTO;
} else if ((remote_state & FAN_MASK) == FAN_MIN) { } else if ((remote_state & FAN_MASK) == FAN_MIN) {
@ -166,11 +175,17 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
this->fan_mode = climate::CLIMATE_FAN_HIGH; this->fan_mode = climate::CLIMATE_FAN_HIGH;
} }
} }
// Get temperature
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
}
} }
this->publish_state(); this->publish_state();
return true; return true;
} }
void LgIrClimate::transmit_(uint32_t value) { void LgIrClimate::transmit_(uint32_t value) {
calc_checksum_(value); calc_checksum_(value);
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value); ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);

View file

@ -14,7 +14,7 @@ const uint8_t TEMP_MAX = 30; // Celsius
class LgIrClimate : public climate_ir::ClimateIR { class LgIrClimate : public climate_ir::ClimateIR {
public: public:
LgIrClimate() LgIrClimate()
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false, : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH}, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}

View file

@ -1,25 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True MULTI_CONF = True
AUTO_LOAD = ["sensor"]
dallas_ns = cg.esphome_ns.namespace("dallas") CONFIG_SCHEMA = cv.invalid(
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent) 'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DallasComponent),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))

View file

@ -1,287 +0,0 @@
#include "dallas_component.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dallas {
static const char *const TAG = "dallas.sensor";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
switch (this->resolution_) {
case 9:
return 94;
case 10:
return 188;
case 11:
return 375;
default:
return 750;
}
}
void DallasComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
pin_->setup();
// clear bus with 480µs high, otherwise initial reset in search_vec() fails
pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(480);
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
std::vector<uint64_t> raw_sensors;
raw_sensors = this->one_wire_->search_vec();
for (auto &address : raw_sensors) {
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
continue;
}
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
address8[0] != DALLAS_MODEL_DS28EA00) {
ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
continue;
}
this->found_sensors_.push_back(address);
}
for (auto *sensor : this->sensors_) {
if (sensor->get_index().has_value()) {
if (*sensor->get_index() >= this->found_sensors_.size()) {
this->status_set_error("Sensor configured by index but not found");
continue;
}
sensor->set_address(this->found_sensors_[*sensor->get_index()]);
}
if (!sensor->setup_sensor()) {
this->status_set_error();
}
}
}
void DallasComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DallasComponent:");
LOG_PIN(" Pin: ", this->pin_);
LOG_UPDATE_INTERVAL(this);
if (this->found_sensors_.empty()) {
ESP_LOGW(TAG, " Found no sensors!");
} else {
ESP_LOGD(TAG, " Found sensors:");
for (auto &address : this->found_sensors_) {
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
}
}
for (auto *sensor : this->sensors_) {
LOG_SENSOR(" ", "Device", sensor);
if (sensor->get_index().has_value()) {
ESP_LOGCONFIG(TAG, " Index %u", *sensor->get_index());
if (*sensor->get_index() >= this->found_sensors_.size()) {
ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
continue;
}
}
ESP_LOGCONFIG(TAG, " Address: %s", sensor->get_address_name().c_str());
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
}
}
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
void DallasComponent::update() {
this->status_clear_warning();
bool result;
{
InterruptLock lock;
result = this->one_wire_->reset();
}
if (!result) {
if (!this->found_sensors_.empty()) {
// Only log error if at the start sensors were found (and thus are disconnected during uptime)
ESP_LOGE(TAG, "Requesting conversion failed");
this->status_set_warning();
}
for (auto *sensor : this->sensors_) {
sensor->publish_state(NAN);
}
return;
}
{
InterruptLock lock;
this->one_wire_->skip();
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
}
for (auto *sensor : this->sensors_) {
if (sensor->get_address() == 0) {
ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
sensor->publish_state(NAN);
continue;
}
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
bool res = sensor->read_scratch_pad();
if (!res) {
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
sensor->publish_state(NAN);
this->status_set_warning();
return;
}
if (!sensor->check_scratch_pad()) {
sensor->publish_state(NAN);
this->status_set_warning();
return;
}
float tempc = sensor->get_temp_c();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
sensor->publish_state(tempc);
});
}
}
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
const std::string &DallasTemperatureSensor::get_address_name() {
if (this->address_name_.empty()) {
this->address_name_ = std::string("0x") + format_hex(this->address_);
}
return this->address_name_;
}
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
auto *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (!wire->reset()) {
return false;
}
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
for (unsigned char &i : this->scratch_pad_) {
i = wire->read8();
}
}
return true;
}
bool DallasTemperatureSensor::setup_sensor() {
bool r = this->read_scratch_pad();
if (!r) {
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
return false;
}
if (!this->check_scratch_pad())
return false;
if (this->scratch_pad_[4] == this->resolution_)
return false;
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
// DS18S20 doesn't support resolution.
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
return false;
}
switch (this->resolution_) {
case 12:
this->scratch_pad_[4] = 0x7F;
break;
case 11:
this->scratch_pad_[4] = 0x5F;
break;
case 10:
this->scratch_pad_[4] = 0x3F;
break;
case 9:
default:
this->scratch_pad_[4] = 0x1F;
break;
}
auto *wire = this->parent_->one_wire_;
{
InterruptLock lock;
if (wire->reset()) {
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
wire->write8(this->scratch_pad_[2]); // high alarm temp
wire->write8(this->scratch_pad_[3]); // low alarm temp
wire->write8(this->scratch_pad_[4]); // resolution
wire->reset();
// write value to EEPROM
wire->select(this->address_);
wire->write8(0x48);
}
}
delay(20); // allow it to finish operation
wire->reset();
return true;
}
bool DallasTemperatureSensor::check_scratch_pad() {
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
bool config_validity = false;
switch (this->get_address8()[0]) {
case DALLAS_MODEL_DS18B20:
config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
break;
default:
config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
}
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
} else if (!config_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
}
return chksum_validity && config_validity;
}
float DallasTemperatureSensor::get_temp_c() {
int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
}
return temp / 128.0f;
}
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
} // namespace dallas
} // namespace esphome

View file

@ -1,79 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esp_one_wire.h"
#include <vector>
namespace esphome {
namespace dallas {
class DallasTemperatureSensor;
class DallasComponent : public PollingComponent {
public:
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
void register_sensor(DallasTemperatureSensor *sensor);
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void update() override;
protected:
friend DallasTemperatureSensor;
InternalGPIOPin *pin_;
ESPOneWire *one_wire_;
std::vector<DallasTemperatureSensor *> sensors_;
std::vector<uint64_t> found_sensors_;
};
/// Internal class that helps us create multiple sensors for one Dallas hub.
class DallasTemperatureSensor : public sensor::Sensor {
public:
void set_parent(DallasComponent *parent) { parent_ = parent; }
/// Helper to get a pointer to the address as uint8_t.
uint8_t *get_address8();
uint64_t get_address();
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name();
/// Set the 64-bit unsigned address for this sensor.
void set_address(uint64_t address);
/// Get the index of this sensor. (0 if using address.)
optional<uint8_t> get_index() const;
/// Set the index of this sensor. If using index, address will be set after setup.
void set_index(uint8_t index);
/// Get the set resolution for this sensor.
uint8_t get_resolution() const;
/// Set the resolution for this sensor.
void set_resolution(uint8_t resolution);
/// Get the number of milliseconds we have to wait for the conversion phase.
uint16_t millis_to_wait_for_conversion() const;
bool setup_sensor();
bool read_scratch_pad();
bool check_scratch_pad();
float get_temp_c();
std::string unique_id() override;
protected:
DallasComponent *parent_;
uint64_t address_;
optional<uint8_t> index_;
uint8_t resolution_;
std::string address_name_;
uint8_t scratch_pad_[9] = {
0,
};
};
} // namespace dallas
} // namespace esphome

View file

@ -1,252 +0,0 @@
#include "esp_one_wire.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace dallas {
static const char *const TAG = "dallas.one_wire";
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const int ONE_WIRE_ROM_SEARCH = 0xF0;
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
bool HOT IRAM_ATTR ESPOneWire::reset() {
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
// Wait for communication to clear (delay G)
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
} while (!pin_.digital_read());
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Release the bus, delay I
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
// sample bus, 0=device(s) present, 1=no device present
bool r = !pin_.digital_read();
// delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// from datasheet:
// write 0 low time: t_low0: min=60µs, max=120µs
// write 1 low time: t_low1: min=1µs, max=15µs
// time slot: t_slot: min=60µs, max=120µs
// recovery time: t_rec: min=1µs
// ds18b20 appears to read the bus after roughly 14µs
uint32_t delay0 = bit ? 6 : 60;
uint32_t delay1 = bit ? 54 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// note: for reading we'll need very accurate timing, as the
// timing for the digital_read() is tight; according to the datasheet,
// we should read at the end of 16µs starting from the bus low
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
// and 29µs for a logical 0
uint32_t start = micros();
// datasheet says >1µs
delayMicroseconds(3);
// release bus, delay E
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// Unfortunately some frameworks have different characteristics than others
// esp32 arduino appears to pull the bus low only after the digital_write(false),
// whereas on esp-idf it already happens during the pin_mode(OUTPUT)
// manually correct for this with these constants.
#ifdef USE_ESP32
uint32_t timing_constant = 12;
#else
uint32_t timing_constant = 14;
#endif
// measure from start value directly, to get best accurate timing no matter
// how long pin_mode/delayMicroseconds took
while (micros() - start < timing_constant)
;
// sample bus to read bit from peer
bool r = pin_.digital_read();
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
uint32_t now = micros();
if (now - start < 60)
delayMicroseconds(60 - (now - start));
return r;
}
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit(bool((1u << i) & val));
}
}
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR ESPOneWire::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit()) << i);
}
return ret;
}
uint64_t IRAM_ATTR ESPOneWire::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit()) << i);
}
return ret;
}
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
}
void IRAM_ATTR ESPOneWire::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->rom_number_ = 0;
}
uint64_t IRAM_ATTR ESPOneWire::search() {
if (this->last_device_flag_) {
return 0u;
}
{
InterruptLock lock;
if (!this->reset()) {
// Reset failed or no devices present
this->reset_search();
return 0u;
}
}
uint8_t id_bit_number = 1;
uint8_t last_zero = 0;
uint8_t rom_byte_number = 0;
bool search_result = false;
uint8_t rom_byte_mask = 1;
{
InterruptLock lock;
// Initiate search
this->write8(ONE_WIRE_ROM_SEARCH);
do {
// read bit
bool id_bit = this->read_bit();
// read its complement
bool cmp_id_bit = this->read_bit();
if (id_bit && cmp_id_bit) {
// No devices participating in search
break;
}
bool branch;
if (id_bit != cmp_id_bit) {
// only chose one branch, the other one doesn't have any devices.
branch = id_bit;
} else {
// there are devices with both 0s and 1s at this bit
if (id_bit_number < this->last_discrepancy_) {
branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
} else {
branch = id_bit_number == this->last_discrepancy_;
}
if (!branch) {
last_zero = id_bit_number;
}
}
if (branch) {
// set bit
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
} else {
// clear bit
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
}
// choose/announce branch
this->write_bit(branch);
id_bit_number++;
rom_byte_mask <<= 1;
if (rom_byte_mask == 0u) {
// go to next byte
rom_byte_number++;
rom_byte_mask = 1;
}
} while (rom_byte_number < 8); // loop through all bytes
}
if (id_bit_number >= 65) {
this->last_discrepancy_ = last_zero;
if (this->last_discrepancy_ == 0) {
// we're at root and have no choices left, so this was the last one.
this->last_device_flag_ = true;
}
search_result = true;
}
search_result = search_result && (this->rom_number8_()[0] != 0);
if (!search_result) {
this->reset_search();
return 0u;
}
return this->rom_number_;
}
std::vector<uint64_t> ESPOneWire::search_vec() {
std::vector<uint64_t> res;
this->reset_search();
uint64_t address;
while ((address = this->search()) != 0u)
res.push_back(address);
return res;
}
void IRAM_ATTR ESPOneWire::skip() {
this->write8(0xCC); // skip ROM
}
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
} // namespace dallas
} // namespace esphome

View file

@ -1,68 +0,0 @@
#pragma once
#include "esphome/core/hal.h"
#include <vector>
namespace esphome {
namespace dallas {
extern const uint8_t ONE_WIRE_ROM_SELECT;
extern const int ONE_WIRE_ROM_SEARCH;
class ESPOneWire {
public:
explicit ESPOneWire(InternalGPIOPin *pin);
/** Reset the bus, should be done before all write operations.
*
* Takes approximately 1ms.
*
* @return Whether the operation was successful.
*/
bool reset();
/// Write a single bit to the bus, takes about 70µs.
void write_bit(bool bit);
/// Read a single bit from the bus, takes about 70µs
bool read_bit();
/// Write a word to the bus. LSB first.
void write8(uint8_t val);
/// Write a 64 bit unsigned integer to the bus. LSB first.
void write64(uint64_t val);
/// Write a command to the bus that addresses all devices by skipping the ROM.
void skip();
/// Read an 8 bit word from the bus.
uint8_t read8();
/// Read an 64-bit unsigned integer from the bus.
uint64_t read64();
/// Select a specific address on the bus for the following command.
void select(uint64_t address);
/// Reset the device search.
void reset_search();
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
uint64_t search();
/// Helper that wraps search in a std::vector.
std::vector<uint64_t> search_vec();
protected:
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
inline uint8_t *rom_number8_();
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
bool last_device_flag_{false};
uint64_t rom_number_{0};
};
} // namespace dallas
} // namespace esphome

View file

@ -1,50 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import ( CONFIG_SCHEMA = cv.invalid(
CONF_ADDRESS, 'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
CONF_DALLAS_ID,
CONF_INDEX,
CONF_RESOLUTION,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
) )
from . import DallasComponent, dallas_ns
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
DallasTemperatureSensor,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
cv.Optional(CONF_INDEX): cv.positive_int,
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
}
),
cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
)
async def to_code(config):
hub = await cg.get_variable(config[CONF_DALLAS_ID])
var = await sensor.new_sensor(config)
if CONF_ADDRESS in config:
cg.add(var.set_address(config[CONF_ADDRESS]))
else:
cg.add(var.set_index(config[CONF_INDEX]))
if CONF_RESOLUTION in config:
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
cg.add(var.set_parent(hub))
cg.add(hub.register_sensor(var))

View file

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

View file

@ -0,0 +1,172 @@
#include "dallas_temp.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dallas_temp {
static const char *const TAG = "dallas.temp.sensor";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
switch (this->resolution_) {
case 9:
return 94;
case 10:
return 188;
case 11:
return 375;
default:
return 750;
}
}
void DallasTemperatureSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
if (this->address_ == 0) {
ESP_LOGW(TAG, " Unable to select an address");
return;
}
LOG_ONE_WIRE_DEVICE(this);
ESP_LOGCONFIG(TAG, " Resolution: %u bits", this->resolution_);
LOG_UPDATE_INTERVAL(this);
}
void DallasTemperatureSensor::update() {
if (this->address_ == 0)
return;
this->status_clear_warning();
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
this->publish_state(NAN);
return;
}
float tempc = this->get_temp_c_();
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
this->publish_state(tempc);
});
}
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
for (uint8_t &i : this->scratch_pad_) {
i = this->bus_->read8();
}
}
bool DallasTemperatureSensor::read_scratch_pad_() {
bool success;
{
InterruptLock lock;
success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
if (success)
this->read_scratch_pad_int_();
}
if (!success) {
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
this->status_set_warning("bus reset failed");
}
return success;
}
void DallasTemperatureSensor::setup() {
ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
if (!this->check_address_())
return;
if (!this->read_scratch_pad_())
return;
if (!this->check_scratch_pad_())
return;
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
// DS18S20 doesn't support resolution.
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
return;
}
uint8_t res;
switch (this->resolution_) {
case 12:
res = 0x7F;
break;
case 11:
res = 0x5F;
break;
case 10:
res = 0x3F;
break;
case 9:
default:
res = 0x1F;
break;
}
if (this->scratch_pad_[4] == res)
return;
this->scratch_pad_[4] = res;
{
InterruptLock lock;
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
this->bus_->write8(this->scratch_pad_[4]); // resolution
}
// write value to EEPROM
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
}
}
bool DallasTemperatureSensor::check_scratch_pad_() {
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
crc8(this->scratch_pad_, 8));
#endif
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
this->status_set_warning("scratch pad checksum invalid");
}
return chksum_validity;
}
float DallasTemperatureSensor::get_temp_c_() {
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
if (this->scratch_pad_[7] != 0x10)
ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
} else {
switch (this->resolution_) {
case 9:
temp &= 0xfff8;
break;
case 10:
temp &= 0xfffc;
break;
case 11:
temp &= 0xfffe;
break;
case 12:
default:
break;
}
}
return temp / 16.0f;
}
} // namespace dallas_temp
} // namespace esphome

View file

@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/one_wire/one_wire.h"
namespace esphome {
namespace dallas_temp {
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
/// Set the resolution for this sensor.
void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
protected:
uint8_t resolution_;
uint8_t scratch_pad_[9] = {0};
/// Get the number of milliseconds we have to wait for the conversion phase.
uint16_t millis_to_wait_for_conversion_() const;
bool read_scratch_pad_();
void read_scratch_pad_int_();
bool check_scratch_pad_();
float get_temp_c_();
};
} // namespace dallas_temp
} // namespace esphome

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import one_wire, sensor
from esphome.const import (
CONF_RESOLUTION,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
DallasTemperatureSensor = dallas_temp_ns.class_(
"DallasTemperatureSensor",
cg.PollingComponent,
sensor.Sensor,
one_wire.OneWireDevice,
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
DallasTemperatureSensor,
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
}
)
.extend(one_wire.one_wire_device_schema())
.extend(cv.polling_component_schema("60s"))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await one_wire.register_one_wire_device(var, config)
cg.add(var.set_resolution(config[CONF_RESOLUTION]))

View file

@ -34,10 +34,12 @@ enum WakeupPinMode {
WAKEUP_PIN_MODE_INVERT_WAKEUP, WAKEUP_PIN_MODE_INVERT_WAKEUP,
}; };
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
struct Ext1Wakeup { struct Ext1Wakeup {
uint64_t mask; uint64_t mask;
esp_sleep_ext1_wakeup_mode_t wakeup_mode; esp_sleep_ext1_wakeup_mode_t wakeup_mode;
}; };
#endif
struct WakeupCauseToRunDuration { struct WakeupCauseToRunDuration {
// Run duration if woken up by timer or any other reason besides those below. // Run duration if woken up by timer or any other reason besides those below.
@ -114,7 +116,11 @@ class DeepSleepComponent : public Component {
#ifdef USE_ESP32 #ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_; InternalGPIOPin *wakeup_pin_;
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE}; WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
#if !defined(USE_ESP32_VARIANT_ESP32C3)
optional<Ext1Wakeup> ext1_wakeup_; optional<Ext1Wakeup> ext1_wakeup_;
#endif
optional<bool> touch_wakeup_; optional<bool> touch_wakeup_;
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_; optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
#endif #endif

View file

@ -0,0 +1,25 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ID, CONF_PIN
from esphome.components.one_wire import OneWireBus
from .. import gpio_ns
CODEOWNERS = ["@ssieb"]
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))

View file

@ -0,0 +1,199 @@
#include "gpio_one_wire.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace gpio {
static const char *const TAG = "gpio.one_wire";
void GPIOOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
this->search();
}
void GPIOOneWireBus::dump_config() {
ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
LOG_PIN(" Pin: ", this->t_pin_);
this->dump_devices_(TAG);
}
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
// Wait for communication to clear (delay G)
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
} while (!pin_.digital_read());
bool r;
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Release the bus, delay I
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
// sample bus, 0=device(s) present, 1=no device present
r = !pin_.digital_read();
// delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// from datasheet:
// write 0 low time: t_low0: min=60µs, max=120µs
// write 1 low time: t_low1: min=1µs, max=15µs
// time slot: t_slot: min=60µs, max=120µs
// recovery time: t_rec: min=1µs
// ds18b20 appears to read the bus after roughly 14µs
uint32_t delay0 = bit ? 6 : 60;
uint32_t delay1 = bit ? 54 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
// drive bus low
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// note: for reading we'll need very accurate timing, as the
// timing for the digital_read() is tight; according to the datasheet,
// we should read at the end of 16µs starting from the bus low
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
// and 29µs for a logical 0
uint32_t start = micros();
// datasheet says >1µs
delayMicroseconds(2);
// release bus, delay E
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// measure from start value directly, to get best accurate timing no matter
// how long pin_mode/delayMicroseconds took
delayMicroseconds(12 - (micros() - start));
// sample bus to read bit from peer
bool r = pin_.digital_read();
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
uint32_t now = micros();
if (now - start < 60)
delayMicroseconds(60 - (now - start));
return r;
}
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit_(bool((1u << i) & val));
}
}
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit_(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit_()) << i);
}
return ret;
}
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit_()) << i);
}
return ret;
}
void GPIOOneWireBus::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->address_ = 0;
}
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
if (this->last_device_flag_)
return 0u;
uint8_t last_zero = 0;
uint64_t bit_mask = 1;
uint64_t address = this->address_;
// Initiate search
for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
// read bit
bool id_bit = this->read_bit_();
// read its complement
bool cmp_id_bit = this->read_bit_();
if (id_bit && cmp_id_bit) {
// No devices participating in search
return 0;
}
bool branch;
if (id_bit != cmp_id_bit) {
// only chose one branch, the other one doesn't have any devices.
branch = id_bit;
} else {
// there are devices with both 0s and 1s at this bit
if (bit_number < this->last_discrepancy_) {
branch = (address & bit_mask) > 0;
} else {
branch = bit_number == this->last_discrepancy_;
}
if (!branch) {
last_zero = bit_number;
}
}
if (branch) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
// choose/announce branch
this->write_bit_(branch);
}
this->last_discrepancy_ = last_zero;
if (this->last_discrepancy_ == 0) {
// we're at root and have no choices left, so this was the last one.
this->last_device_flag_ = true;
}
this->address_ = address;
return address;
}
} // namespace gpio
} // namespace esphome

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/one_wire/one_wire.h"
namespace esphome {
namespace gpio {
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
public:
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_pin(InternalGPIOPin *pin) {
this->t_pin_ = pin;
this->pin_ = pin->to_isr();
}
bool reset() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
uint64_t read64() override;
protected:
InternalGPIOPin *t_pin_;
ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
bool last_device_flag_{false};
uint64_t address_;
void reset_search() override;
uint64_t search_int() override;
void write_bit_(bool bit);
bool read_bit_();
};
} // namespace gpio
} // namespace esphome

View file

@ -56,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
this->position = new_position; this->position = new_position;
this->current_operation = COVER_OPERATION_IDLE; this->current_operation = COVER_OPERATION_IDLE;
if (this->last_command_ == operation) { if (this->last_command_ == operation) {
float dur = (now - this->start_dir_time_) / 1e3f; float dur = (float) (now - this->start_dir_time_) / 1e3f;
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(), ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur); operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
} }
@ -69,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
this->current_operation = operation; this->current_operation = operation;
if (operation != COVER_OPERATION_IDLE) if (operation != COVER_OPERATION_IDLE)
this->last_recompute_time_ = millis(); this->last_recompute_time_ = millis();
this->publish_state();
} }
} }
@ -129,7 +128,7 @@ void HE60rCover::update_() {
if (this->toggles_needed_ != 0) { if (this->toggles_needed_ != 0) {
if ((this->counter_++ & 0x3) == 0) { if ((this->counter_++ & 0x3) == 0) {
this->toggles_needed_--; this->toggles_needed_--;
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_); ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
this->write_byte(TOGGLE_BYTE); this->write_byte(TOGGLE_BYTE);
} else { } else {
this->write_byte(QUERY_BYTE); this->write_byte(QUERY_BYTE);
@ -235,33 +234,30 @@ void HE60rCover::recompute_position_() {
return; return;
const uint32_t now = millis(); const uint32_t now = millis();
float dir; if (now > this->last_recompute_time_) {
float action_dur; auto diff = (unsigned) (now - last_recompute_time_);
float delta;
switch (this->current_operation) { switch (this->current_operation) {
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
dir = 1.0f; delta = (float) diff / (float) this->open_duration_;
action_dur = this->open_duration_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
dir = -1.0f; delta = -(float) diff / (float) this->close_duration_;
action_dur = this->close_duration_;
break; break;
default: default:
return; return;
} }
if (now > this->last_recompute_time_) {
auto diff = now - last_recompute_time_;
auto delta = dir * diff / action_dur;
// make sure our guesstimate never reaches full open or close. // make sure our guesstimate never reaches full open or close.
this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f); auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta, ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
this->position);
this->last_recompute_time_ = now; this->last_recompute_time_ = now;
if (this->position != new_position) {
this->position = new_position;
this->publish_state(); this->publish_state();
} }
} }
}
} // namespace he60r } // namespace he60r
} // namespace esphome } // namespace esphome

View file

@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
void control(const cover::CoverCall &call) override; void control(const cover::CoverCall &call) override;
bool is_at_target_() const; bool is_at_target_() const;
void start_direction_(cover::CoverOperation dir); void start_direction_(cover::CoverOperation dir);
void update_operation_(cover::CoverOperation dir);
void endstop_reached_(cover::CoverOperation operation); void endstop_reached_(cover::CoverOperation operation);
void recompute_position_(); void recompute_position_();
void set_current_operation_(cover::CoverOperation operation); void set_current_operation_(cover::CoverOperation operation);
void process_rx_(uint8_t data); void process_rx_(uint8_t data);
uint32_t open_duration_{0}; unsigned open_duration_{0};
uint32_t close_duration_{0}; unsigned close_duration_{0};
uint32_t toggles_needed_{0}; unsigned toggles_needed_{0};
cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE}; cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE}; cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
uint32_t last_recompute_time_{0}; uint32_t last_recompute_time_{0};

View file

@ -15,6 +15,8 @@
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {
static const char *const TAG = "http_request.ota";
void OtaHttpRequestComponent::setup() { void OtaHttpRequestComponent::setup() {
#ifdef USE_OTA_STATE_CALLBACK #ifdef USE_OTA_STATE_CALLBACK
ota::register_ota_platform(this); ota::register_ota_platform(this);

View file

@ -14,7 +14,6 @@
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {
static const char *const TAG = "http_request.ota";
static const uint8_t MD5_SIZE = 32; static const uint8_t MD5_SIZE = 32;
enum OtaHttpRequestError : uint8_t { enum OtaHttpRequestError : uint8_t {

View file

@ -0,0 +1,44 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import update
from esphome.const import (
CONF_SOURCE,
)
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
from ..ota import OtaHttpRequestComponent
AUTO_LOAD = ["json"]
CODEOWNERS = ["@jesserockz"]
DEPENDENCIES = ["ota.http_request"]
HttpRequestUpdate = http_request_ns.class_(
"HttpRequestUpdate", update.UpdateEntity, cg.PollingComponent
)
CONF_OTA_ID = "ota_id"
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
cv.Required(CONF_SOURCE): cv.url,
}
).extend(cv.polling_component_schema("6h"))
async def to_code(config):
var = await update.new_update(config)
ota_parent = await cg.get_variable(config[CONF_OTA_ID])
cg.add(var.set_ota_parent(ota_parent))
request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
cg.add(var.set_request_parent(request_parent))
cg.add(var.set_source_url(config[CONF_SOURCE]))
cg.add_define("USE_OTA_STATE_CALLBACK")
await cg.register_component(var, config)

View file

@ -0,0 +1,157 @@
#include "http_request_update.h"
#include "esphome/core/application.h"
#include "esphome/core/version.h"
#include "esphome/components/json/json_util.h"
#include "esphome/components/network/util.h"
namespace esphome {
namespace http_request {
static const char *const TAG = "http_request.update";
static const size_t MAX_READ_SIZE = 256;
void HttpRequestUpdate::setup() {
this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
if (state == ota::OTAState::OTA_IN_PROGRESS) {
this->state_ = update::UPDATE_STATE_INSTALLING;
this->update_info_.has_progress = true;
this->update_info_.progress = progress;
this->publish_state();
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error("Failed to install firmware");
this->publish_state();
}
});
}
void HttpRequestUpdate::update() {
auto container = this->request_parent_->get(this->source_url_);
if (container == nullptr) {
std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
this->status_set_error(msg.c_str());
return;
}
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
uint8_t *data = allocator.allocate(container->content_length);
if (data == nullptr) {
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
this->status_set_error(msg.c_str());
container->end();
return;
}
size_t read_index = 0;
while (container->get_bytes_read() < container->content_length) {
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
App.feed_wdt();
yield();
read_index += read_bytes;
}
std::string response((char *) data, read_index);
allocator.deallocate(data, container->content_length);
container->end();
bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
this->update_info_.title = root["name"].as<std::string>();
this->update_info_.latest_version = root["version"].as<std::string>();
for (auto build : root["builds"].as<JsonArray>()) {
if (!build.containsKey("chipFamily")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
if (build["chipFamily"] == ESPHOME_VARIANT) {
if (!build.containsKey("ota")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
auto ota = build["ota"];
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
ESP_LOGE(TAG, "Manifest does not contain required fields");
return false;
}
this->update_info_.firmware_url = ota["path"].as<std::string>();
this->update_info_.md5 = ota["md5"].as<std::string>();
if (ota.containsKey("summary"))
this->update_info_.summary = ota["summary"].as<std::string>();
if (ota.containsKey("release_url"))
this->update_info_.release_url = ota["release_url"].as<std::string>();
return true;
}
}
return false;
});
if (!valid) {
std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
this->status_set_error(msg.c_str());
return;
}
// Merge source_url_ and this->update_info_.firmware_url
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
std::string path = this->update_info_.firmware_url;
if (path[0] == '/') {
std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
this->update_info_.firmware_url = domain + path;
} else {
std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
this->update_info_.firmware_url = domain + path;
}
}
std::string current_version = this->current_version_;
if (current_version.empty()) {
#ifdef ESPHOME_PROJECT_VERSION
current_version = ESPHOME_PROJECT_VERSION;
#else
current_version = ESPHOME_VERSION;
#endif
}
this->update_info_.current_version = current_version;
if (this->update_info_.latest_version.empty()) {
this->state_ = update::UPDATE_STATE_NO_UPDATE;
} else if (this->update_info_.latest_version != this->current_version_) {
this->state_ = update::UPDATE_STATE_AVAILABLE;
}
this->update_info_.has_progress = false;
this->update_info_.progress = 0.0f;
this->status_clear_error();
this->publish_state();
}
void HttpRequestUpdate::perform() {
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
return;
}
this->state_ = update::UPDATE_STATE_INSTALLING;
this->publish_state();
this->ota_parent_->set_md5(this->update_info.md5);
this->ota_parent_->set_url(this->update_info.firmware_url);
// Flash in the next loop
this->defer([this]() { this->ota_parent_->flash(); });
}
} // namespace http_request
} // namespace esphome

View file

@ -0,0 +1,37 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/http_request/http_request.h"
#include "esphome/components/http_request/ota/ota_http_request.h"
#include "esphome/components/update/update_entity.h"
namespace esphome {
namespace http_request {
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
public:
void setup() override;
void update() override;
void perform() override;
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
void set_current_version(const std::string &current_version) { this->current_version_ = current_version; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
protected:
HttpRequestComponent *request_parent_;
OtaHttpRequestComponent *ota_parent_;
std::string source_url_;
std::string current_version_{""};
};
} // namespace http_request
} // namespace esphome

View file

@ -37,38 +37,14 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
this->set_volume_(volume); this->set_volume_(volume);
this->unmute_(); this->unmute_();
} }
if (this->i2s_state_ != I2S_STATE_RUNNING) {
return;
}
if (call.get_command().has_value()) { if (call.get_command().has_value()) {
switch (call.get_command().value()) { switch (call.get_command().value()) {
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
if (!this->audio_->isRunning())
this->audio_->pauseResume();
this->state = play_state;
break;
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
if (this->audio_->isRunning())
this->audio_->pauseResume();
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
break;
case media_player::MEDIA_PLAYER_COMMAND_STOP:
this->stop();
break;
case media_player::MEDIA_PLAYER_COMMAND_MUTE: case media_player::MEDIA_PLAYER_COMMAND_MUTE:
this->mute_(); this->mute_();
break; break;
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE: case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
this->unmute_(); this->unmute_();
break; break;
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
this->audio_->pauseResume();
if (this->audio_->isRunning()) {
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
} else {
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
}
break;
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: { case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
float new_volume = this->volume + 0.1f; float new_volume = this->volume + 0.1f;
if (new_volume > 1.0f) if (new_volume > 1.0f)
@ -85,6 +61,36 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
this->unmute_(); this->unmute_();
break; break;
} }
default:
break;
}
if (this->i2s_state_ != I2S_STATE_RUNNING) {
return;
}
switch (call.get_command().value()) {
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
if (!this->audio_->isRunning())
this->audio_->pauseResume();
this->state = play_state;
break;
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
if (this->audio_->isRunning())
this->audio_->pauseResume();
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
break;
case media_player::MEDIA_PLAYER_COMMAND_STOP:
this->stop();
break;
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
this->audio_->pauseResume();
if (this->audio_->isRunning()) {
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
} else {
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
}
break;
default:
break;
} }
} }
this->publish_state(); this->publish_state();

View file

@ -69,6 +69,7 @@ MODELS = {
"ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay), "ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
"ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay), "ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
"ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay), "ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
"ST7735": ili9xxx_ns.class_("ILI9XXXST7735", ILI9XXXDisplay),
"ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay), "ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
@ -134,6 +135,7 @@ def _validate(config):
"ILI9341", "ILI9341",
"ILI9342", "ILI9342",
"ST7789V", "ST7789V",
"ST7735",
]: ]:
raise cv.Invalid("Selected model can't run on ESP8266.") raise cv.Invalid("Selected model can't run on ESP8266.")

View file

@ -70,6 +70,7 @@ static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
static const uint8_t ILI9XXX_PWCTR3 = 0xC2; static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
static const uint8_t ILI9XXX_PWCTR4 = 0xC3; static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
static const uint8_t ILI9XXX_PWCTR5 = 0xC4; static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
static const uint8_t ILI9XXX_PWCTR6 = 0xF6;
static const uint8_t ILI9XXX_VMCTR1 = 0xC5; static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
static const uint8_t ILI9XXX_IFCTR = 0xC6; static const uint8_t ILI9XXX_IFCTR = 0xC6;
static const uint8_t ILI9XXX_VMCTR2 = 0xC7; static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
@ -91,6 +92,7 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
static const uint8_t ILI9XXX_CSCON = 0xF0; static const uint8_t ILI9XXX_CSCON = 0xF0;
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -411,13 +411,21 @@ void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
uint8_t cmd, x, num_args; uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
x = *addr++; x = *addr++;
if (cmd == ILI9XXX_DELAY) {
ESP_LOGD(TAG, "Delay %dms", x);
delay(x);
} else {
num_args = x & 0x7F; num_args = x & 0x7F;
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
this->send_command(cmd, addr, num_args); this->send_command(cmd, addr, num_args);
addr += num_args; addr += num_args;
if (x & 0x80) if (x & 0x80) {
ESP_LOGD(TAG, "Delay 150ms");
delay(150); // NOLINT delay(150); // NOLINT
} }
} }
}
}
// Tell the display controller where we want to draw pixels. // Tell the display controller where we want to draw pixels.
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {

View file

@ -35,7 +35,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F; num_args = *addr++ & 0x7F;
bits = *addr; bits = *addr;
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
switch (cmd) { switch (cmd) {
case ILI9XXX_MADCTL: { case ILI9XXX_MADCTL: {
this->swap_xy_ = (bits & MADCTL_MV) != 0; this->swap_xy_ = (bits & MADCTL_MV) != 0;
@ -51,6 +50,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
break; break;
} }
case ILI9XXX_DELAY:
continue; // no args to skip
default: default:
break; break;
} }
@ -269,5 +271,11 @@ class ILI9XXXGC9A01A : public ILI9XXXDisplay {
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
}; };
//----------- ILI9XXX_24_TFT display --------------
class ILI9XXXST7735 : public ILI9XXXDisplay {
public:
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
};
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -370,6 +370,57 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
0x00 // End of list 0x00 // End of list
}; };
static const uint8_t PROGMEM INITCMD_ST7735[] = {
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
ILI9XXX_DELAY, 10,
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
ILI9XXX_DELAY, 10,
ILI9XXX_PIXFMT , 1, 0x05,
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ILI9XXX_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
ILI9XXX_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
0x01, 0x2C, 0x2D, // Dot inversion mode
0x01, 0x2C, 0x2D, // Line inversion mode
ILI9XXX_INVCTR, 1, // 7: Display inversion control, 1 arg:
0x7, // Line inversion
ILI9XXX_PWCTR1, 3, // 7: Power control, 3 args, no delay:
0xA2,
0x02, // -4.6V
0x84, // AUTO mode
ILI9XXX_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
ILI9XXX_PWCTR3, 2, // 9: Power control, 2 args, no delay:
0x0A, // Opamp current small
0x00, // Boost frequency
ILI9XXX_PWCTR4, 2, // 10: Power control, 2 args, no delay:
0x8A, // BCLK/2,
0x2A, // opamp current small & medium low
ILI9XXX_PWCTR5, 2, // 11: Power control, 2 args, no delay:
0x8A, 0xEE,
ILI9XXX_VMCTR1, 1, // 11: Power control, 2 args + delay:
0x0E,
ILI9XXX_GMCTRP1, 16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
0x37, 0x32, 0x29, 0x2d, // accurate colors)
0x29, 0x25, 0x2B, 0x39,
0x00, 0x01, 0x03, 0x10,
ILI9XXX_GMCTRN1, 16, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
0x2E, 0x2E, 0x37, 0x3F,
0x00, 0x00, 0x02, 0x10,
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
ILI9XXX_NORON , 0,
ILI9XXX_DELAY, 10,
ILI9XXX_DISPON , 0, // Display on
ILI9XXX_DELAY, 10,
00, // endo of list
};
// clang-format on // clang-format on
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -9,8 +9,6 @@ import re
import requests import requests
from magic import Magic from magic import Magic
from PIL import Image
from esphome import core from esphome import core
from esphome.components import font from esphome.components import font
from esphome import external_files from esphome import external_files
@ -68,7 +66,7 @@ def _compute_local_icon_path(value: dict) -> Path:
return base_dir / f"{value[CONF_ICON]}.svg" return base_dir / f"{value[CONF_ICON]}.svg"
def _compute_local_image_path(value: dict) -> Path: def compute_local_image_path(value: dict) -> Path:
url = value[CONF_URL] url = value[CONF_URL]
h = hashlib.new("sha256") h = hashlib.new("sha256")
h.update(url.encode()) h.update(url.encode())
@ -117,7 +115,7 @@ def download_mdi(value):
def download_image(value): def download_image(value):
url = value[CONF_URL] url = value[CONF_URL]
path = _compute_local_image_path(value) path = compute_local_image_path(value)
download_content(url, path) download_content(url, path)
@ -267,6 +265,9 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
def load_svg_image(file: bytes, resize: tuple[int, int]): def load_svg_image(file: bytes, resize: tuple[int, int]):
# Local import only to allow "validate_pillow_installed" to run *before* importing it
from PIL import Image
# This import is only needed in case of SVG images; adding it # This import is only needed in case of SVG images; adding it
# to the top would force configurations not using SVG to also have it # to the top would force configurations not using SVG to also have it
# installed for no reason. # installed for no reason.
@ -286,6 +287,9 @@ def load_svg_image(file: bytes, resize: tuple[int, int]):
async def to_code(config): async def to_code(config):
# Local import only to allow "validate_pillow_installed" to run *before* importing it
from PIL import Image
conf_file = config[CONF_FILE] conf_file = config[CONF_FILE]
if conf_file[CONF_SOURCE] == SOURCE_LOCAL: if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
@ -295,7 +299,7 @@ async def to_code(config):
path = _compute_local_icon_path(conf_file).as_posix() path = _compute_local_icon_path(conf_file).as_posix()
elif conf_file[CONF_SOURCE] == SOURCE_WEB: elif conf_file[CONF_SOURCE] == SOURCE_WEB:
path = _compute_local_image_path(conf_file).as_posix() path = compute_local_image_path(conf_file).as_posix()
try: try:
with open(path, "rb") as f: with open(path, "rb") as f:

View file

@ -126,6 +126,7 @@ MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent) MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent) MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent)
MQTTUpdateComponent = mqtt_ns.class_("MQTTUpdateComponent", MQTTComponent)
MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent) MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent)
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")

View file

@ -137,6 +137,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm"; constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd"; constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home"; constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst";
constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc"; constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock"; constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd"; constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
@ -396,6 +397,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm"; constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed"; constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home"; constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install";
constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate"; constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock"; constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed"; constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";

View file

@ -0,0 +1,62 @@
#include "mqtt_update.h"
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_UPDATE
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.update";
using namespace esphome::update;
MQTTUpdateComponent::MQTTUpdateComponent(UpdateEntity *update) : update_(update) {}
void MQTTUpdateComponent::setup() {
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
if (payload == "INSTALL") {
this->update_->perform();
} else {
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str());
this->status_momentary_warning("state", 5000);
}
});
this->update_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
}
bool MQTTUpdateComponent::publish_state() {
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
root["installed_version"] = this->update_->update_info.current_version;
root["latest_version"] = this->update_->update_info.latest_version;
root["title"] = this->update_->update_info.title;
if (!this->update_->update_info.summary.empty())
root["release_summary"] = this->update_->update_info.summary;
if (!this->update_->update_info.release_url.empty())
root["release_url"] = this->update_->update_info.release_url;
});
}
void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
root["schema"] = "json";
root[MQTT_PAYLOAD_INSTALL] = "INSTALL";
}
bool MQTTUpdateComponent::send_initial_state() { return this->publish_state(); }
void MQTTUpdateComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Update '%s': ", this->update_->get_name().c_str());
LOG_MQTT_COMPONENT(true, true);
}
std::string MQTTUpdateComponent::component_type() const { return "update"; }
const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; }
} // namespace mqtt
} // namespace esphome
#endif // USE_UPDATE
#endif // USE_MQTT

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MQTT
#ifdef USE_UPDATE
#include "esphome/components/update/update_entity.h"
#include "mqtt_component.h"
namespace esphome {
namespace mqtt {
class MQTTUpdateComponent : public mqtt::MQTTComponent {
public:
explicit MQTTUpdateComponent(update::UpdateEntity *update);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void setup() override;
void dump_config() override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;
bool publish_state();
protected:
/// "update" component type.
std::string component_type() const override;
const EntityBase *get_entity() const override;
update::UpdateEntity *update_;
};
} // namespace mqtt
} // namespace esphome
#endif // USE_UPDATE
#endif // USE_MQTT

View file

@ -0,0 +1,40 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS
CODEOWNERS = ["@ssieb"]
IS_PLATFORM_COMPONENT = True
CONF_ONE_WIRE_ID = "one_wire_id"
one_wire_ns = cg.esphome_ns.namespace("one_wire")
OneWireBus = one_wire_ns.class_("OneWireBus")
OneWireDevice = one_wire_ns.class_("OneWireDevice")
def one_wire_device_schema():
"""Create a schema for a 1-wire device.
:return: The 1-wire device schema, `extend` this in your config schema.
"""
schema = cv.Schema(
{
cv.GenerateID(CONF_ONE_WIRE_ID): cv.use_id(OneWireBus),
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
}
)
return schema
async def register_one_wire_device(var, config):
"""Register an 1-wire device with the given config.
Sets the 1-wire bus to use and the 1-wire address.
This is a coroutine, you need to await it with a 'yield' expression!
"""
parent = await cg.get_variable(config[CONF_ONE_WIRE_ID])
cg.add(var.set_one_wire_bus(parent))
if (address := config.get(CONF_ADDRESS)) is not None:
cg.add(var.set_address(address))

View file

@ -0,0 +1,40 @@
#include "one_wire.h"
namespace esphome {
namespace one_wire {
static const char *const TAG = "one_wire";
const std::string &OneWireDevice::get_address_name() {
if (this->address_name_.empty())
this->address_name_ = std::string("0x") + format_hex(this->address_);
return this->address_name_;
}
std::string OneWireDevice::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
bool OneWireDevice::send_command_(uint8_t cmd) {
if (!this->bus_->select(this->address_))
return false;
this->bus_->write8(cmd);
return true;
}
bool OneWireDevice::check_address_() {
if (this->address_ != 0)
return true;
auto devices = this->bus_->get_devices();
if (devices.empty()) {
ESP_LOGE(TAG, "No devices, can't auto-select address");
return false;
}
if (devices.size() > 1) {
ESP_LOGE(TAG, "More than one device, can't auto-select address");
return false;
}
this->address_ = devices[0];
return true;
}
} // namespace one_wire
} // namespace esphome

View file

@ -0,0 +1,44 @@
#pragma once
#include "one_wire_bus.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace one_wire {
#define LOG_ONE_WIRE_DEVICE(this) \
ESP_LOGCONFIG(TAG, " Address: %s (%s)", this->get_address_name().c_str(), \
LOG_STR_ARG(this->bus_->get_model_str(this->address_ & 0xff)));
class OneWireDevice {
public:
/// @brief store the address of the device
/// @param address of the device
void set_address(uint64_t address) { this->address_ = address; }
/// @brief store the pointer to the OneWireBus to use
/// @param bus pointer to the OneWireBus object
void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; }
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
const std::string &get_address_name();
std::string unique_id();
protected:
uint64_t address_{0};
OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance
std::string address_name_;
/// @brief find an address if necessary
/// should be called from setup
bool check_address_();
/// @brief send command on the bus
/// @param cmd command to send
bool send_command_(uint8_t cmd);
};
} // namespace one_wire
} // namespace esphome

View file

@ -0,0 +1,88 @@
#include "one_wire_bus.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace one_wire {
static const char *const TAG = "one_wire";
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const uint8_t ONE_WIRE_ROM_SEARCH = 0xF0;
const std::vector<uint64_t> &OneWireBus::get_devices() { return this->devices_; }
bool IRAM_ATTR OneWireBus::select(uint64_t address) {
if (!this->reset())
return false;
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
return true;
}
void OneWireBus::search() {
this->devices_.clear();
this->reset_search();
uint64_t address;
while (true) {
{
InterruptLock lock;
if (!this->reset()) {
// Reset failed or no devices present
return;
}
this->write8(ONE_WIRE_ROM_SEARCH);
address = this->search_int();
}
if (address == 0)
break;
auto *address8 = reinterpret_cast<uint8_t *>(&address);
if (crc8(address8, 7) != address8[7]) {
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
} else {
this->devices_.push_back(address);
}
}
}
void OneWireBus::skip() {
this->write8(0xCC); // skip ROM
}
const LogString *OneWireBus::get_model_str(uint8_t model) {
switch (model) {
case DALLAS_MODEL_DS18S20:
return LOG_STR("DS18S20");
case DALLAS_MODEL_DS1822:
return LOG_STR("DS1822");
case DALLAS_MODEL_DS18B20:
return LOG_STR("DS18B20");
case DALLAS_MODEL_DS1825:
return LOG_STR("DS1825");
case DALLAS_MODEL_DS28EA00:
return LOG_STR("DS28EA00");
default:
return LOG_STR("Unknown");
}
}
void OneWireBus::dump_devices_(const char *tag) {
if (this->devices_.empty()) {
ESP_LOGW(tag, " Found no devices!");
} else {
ESP_LOGCONFIG(tag, " Found devices:");
for (auto &address : this->devices_) {
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
}
}
}
} // namespace one_wire
} // namespace esphome

View file

@ -0,0 +1,61 @@
#pragma once
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <vector>
namespace esphome {
namespace one_wire {
class OneWireBus {
public:
/** Reset the bus, should be done before all write operations.
*
* Takes approximately 1ms.
*
* @return Whether the operation was successful.
*/
virtual bool reset() = 0;
/// Write a word to the bus. LSB first.
virtual void write8(uint8_t val) = 0;
/// Write a 64 bit unsigned integer to the bus. LSB first.
virtual void write64(uint64_t val) = 0;
/// Write a command to the bus that addresses all devices by skipping the ROM.
void skip();
/// Read an 8 bit word from the bus.
virtual uint8_t read8() = 0;
/// Read an 64-bit unsigned integer from the bus.
virtual uint64_t read64() = 0;
/// Select a specific address on the bus for the following command.
bool select(uint64_t address);
/// Return the list of found devices.
const std::vector<uint64_t> &get_devices();
/// Search for 1-Wire devices on the bus.
void search();
/// Get the description string for this model.
const LogString *get_model_str(uint8_t model);
protected:
std::vector<uint64_t> devices_;
/// log the found devices
void dump_devices_(const char *tag);
/// Reset the device search.
virtual void reset_search() = 0;
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
virtual uint64_t search_int() = 0;
};
} // namespace one_wire
} // namespace esphome

View file

@ -16,6 +16,7 @@ from esphome import automation
CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"] CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"]
CONF_BOOT_IS_GOOD_AFTER = "boot_is_good_after"
CONF_ON_SAFE_MODE = "on_safe_mode" CONF_ON_SAFE_MODE = "on_safe_mode"
safe_mode_ns = cg.esphome_ns.namespace("safe_mode") safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
@ -34,6 +35,9 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(SafeModeComponent), cv.GenerateID(): cv.declare_id(SafeModeComponent),
cv.Optional(
CONF_BOOT_IS_GOOD_AFTER, default="1min"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_DISABLED, default=False): cv.boolean, cv.Optional(CONF_DISABLED, default=False): cv.boolean,
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int, cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
cv.Optional( cv.Optional(
@ -63,7 +67,9 @@ async def to_code(config):
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
condition = var.should_enter_safe_mode( condition = var.should_enter_safe_mode(
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT] config[CONF_NUM_ATTEMPTS],
config[CONF_REBOOT_TIMEOUT],
config[CONF_BOOT_IS_GOOD_AFTER],
) )
cg.add(RawExpression(f"if ({condition}) return")) cg.add(RawExpression(f"if ({condition}) return"))
CORE.data[CONF_SAFE_MODE] = {} CORE.data[CONF_SAFE_MODE] = {}

View file

@ -16,6 +16,8 @@ static const char *const TAG = "safe_mode";
void SafeModeComponent::dump_config() { void SafeModeComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Safe Mode:"); ESP_LOGCONFIG(TAG, "Safe Mode:");
ESP_LOGCONFIG(TAG, " Boot considered successful after %" PRIu32 " seconds",
this->safe_mode_boot_is_good_after_ / 1000); // because milliseconds
ESP_LOGCONFIG(TAG, " Invoke after %u boot attempts", this->safe_mode_num_attempts_); ESP_LOGCONFIG(TAG, " Invoke after %u boot attempts", this->safe_mode_num_attempts_);
ESP_LOGCONFIG(TAG, " Remain in safe mode for %" PRIu32 " seconds", ESP_LOGCONFIG(TAG, " Remain in safe mode for %" PRIu32 " seconds",
this->safe_mode_enable_time_ / 1000); // because milliseconds this->safe_mode_enable_time_ / 1000); // because milliseconds
@ -34,7 +36,7 @@ void SafeModeComponent::dump_config() {
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; } float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void SafeModeComponent::loop() { void SafeModeComponent::loop() {
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) { if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_boot_is_good_after_) {
// successful boot, reset counter // successful boot, reset counter
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter"); ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
this->clean_rtc(); this->clean_rtc();
@ -60,9 +62,11 @@ bool SafeModeComponent::get_safe_mode_pending() {
return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC; return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
} }
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) { bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time,
uint32_t boot_is_good_after) {
this->safe_mode_start_time_ = millis(); this->safe_mode_start_time_ = millis();
this->safe_mode_enable_time_ = enable_time; this->safe_mode_enable_time_ = enable_time;
this->safe_mode_boot_is_good_after_ = boot_is_good_after;
this->safe_mode_num_attempts_ = num_attempts; this->safe_mode_num_attempts_ = num_attempts;
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false); this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
this->safe_mode_rtc_value_ = this->read_rtc_(); this->safe_mode_rtc_value_ = this->read_rtc_();

View file

@ -11,7 +11,7 @@ namespace safe_mode {
/// SafeModeComponent provides a safe way to recover from repeated boot failures /// SafeModeComponent provides a safe way to recover from repeated boot failures
class SafeModeComponent : public Component { class SafeModeComponent : public Component {
public: public:
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time); bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after);
/// Set to true if the next startup will enter safe mode /// Set to true if the next startup will enter safe mode
void set_safe_mode_pending(const bool &pending); void set_safe_mode_pending(const bool &pending);
@ -34,10 +34,11 @@ class SafeModeComponent : public Component {
uint32_t read_rtc_(); uint32_t read_rtc_();
bool boot_successful_{false}; ///< set to true after boot is considered successful bool boot_successful_{false}; ///< set to true after boot is considered successful
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled uint32_t safe_mode_boot_is_good_after_{60000}; ///< The amount of time after which the boot is considered successful
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for
uint32_t safe_mode_rtc_value_; uint32_t safe_mode_rtc_value_{0};
uint8_t safe_mode_num_attempts_; uint32_t safe_mode_start_time_{0}; ///< stores when safe mode was enabled
uint8_t safe_mode_num_attempts_{0};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
CallbackManager<void()> safe_mode_callback_{}; CallbackManager<void()> safe_mode_callback_{};

View file

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

View file

@ -0,0 +1,72 @@
import subprocess
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import display
from esphome.const import (
CONF_ID,
CONF_DIMENSIONS,
CONF_WIDTH,
CONF_HEIGHT,
CONF_LAMBDA,
PLATFORM_HOST,
)
sdl_ns = cg.esphome_ns.namespace("sdl")
Sdl = sdl_ns.class_("Sdl", display.Display, cg.Component)
CONF_SDL_OPTIONS = "sdl_options"
CONF_SDL_ID = "sdl_id"
def get_sdl_options(value):
if value != "":
return value
try:
return subprocess.check_output(["sdl2-config", "--cflags", "--libs"]).decode()
except Exception as e:
raise cv.Invalid("Unable to run sdl2-config - have you installed sdl2?") from e
CONFIG_SCHEMA = cv.All(
display.FULL_DISPLAY_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Sdl),
cv.Optional(CONF_SDL_OPTIONS, default=""): get_sdl_options,
cv.Required(CONF_DIMENSIONS): cv.Any(
cv.dimensions,
cv.Schema(
{
cv.Required(CONF_WIDTH): cv.int_,
cv.Required(CONF_HEIGHT): cv.int_,
}
),
),
}
)
),
cv.only_on(PLATFORM_HOST),
)
async def to_code(config):
for option in config[CONF_SDL_OPTIONS].split():
cg.add_build_flag(option)
cg.add_build_flag("-DSDL_BYTEORDER=4321")
var = cg.new_Pvariable(config[CONF_ID])
await display.register_display(var, config)
dimensions = config[CONF_DIMENSIONS]
if isinstance(dimensions, dict):
cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
else:
(width, height) = dimensions
cg.add(var.set_dimensions(width, height))
if lamb := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda(
lamb, [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))

View file

@ -0,0 +1,96 @@
#ifdef USE_HOST
#include "sdl_esphome.h"
#include "esphome/components/display/display_color_utils.h"
namespace esphome {
namespace sdl {
void Sdl::setup() {
ESP_LOGD(TAG, "Starting setup");
SDL_Init(SDL_INIT_VIDEO);
this->window_ = SDL_CreateWindow(App.get_name().c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
this->width_, this->height_, 0);
this->renderer_ = SDL_CreateRenderer(this->window_, -1, SDL_RENDERER_SOFTWARE);
this->texture_ =
SDL_CreateTexture(this->renderer_, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STATIC, this->width_, this->height_);
SDL_SetTextureBlendMode(this->texture_, SDL_BLENDMODE_BLEND);
ESP_LOGD(TAG, "Setup Complete");
}
void Sdl::update() {
this->do_update_();
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_))
return;
SDL_Rect rect{this->x_low_, this->y_low_, this->x_high_ + 1 - this->x_low_, this->y_high_ + 1 - this->y_low_};
this->x_low_ = this->width_;
this->y_low_ = this->height_;
this->x_high_ = 0;
this->y_high_ = 0;
SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect);
SDL_RenderPresent(this->renderer_);
}
void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
SDL_Rect rect{x_start, y_start, w, h};
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || big_endian) {
display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
} else {
auto stride = x_offset + w + x_pad;
auto data = ptr + (stride * y_offset + x_offset) * 2;
SDL_UpdateTexture(this->texture_, &rect, data, stride * 2);
}
SDL_RenderCopy(this->renderer_, this->texture_, &rect, &rect);
SDL_RenderPresent(this->renderer_);
}
void Sdl::draw_pixel_at(int x, int y, Color color) {
SDL_Rect rect{x, y, 1, 1};
auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB));
SDL_UpdateTexture(this->texture_, &rect, &data, 2);
if (x < this->x_low_)
this->x_low_ = x;
if (y < this->y_low_)
this->y_low_ = y;
if (x > this->x_high_)
this->x_high_ = x;
if (y > this->y_high_)
this->y_high_ = y;
}
void Sdl::loop() {
SDL_Event e;
if (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_QUIT:
exit(0);
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (e.button.button == 1) {
this->mouse_x = e.button.x;
this->mouse_y = e.button.y;
this->mouse_down = e.button.state != 0;
}
break;
case SDL_MOUSEMOTION:
if (e.motion.state & 1) {
this->mouse_x = e.button.x;
this->mouse_y = e.button.y;
this->mouse_down = true;
} else {
this->mouse_down = false;
}
break;
default:
ESP_LOGV(TAG, "Event %d", e.type);
break;
}
}
}
} // namespace sdl
} // namespace esphome
#endif

View file

@ -0,0 +1,54 @@
#pragma once
#ifdef USE_HOST
#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/display/display.h"
#define SDL_MAIN_HANDLED
#include "SDL.h"
namespace esphome {
namespace sdl {
constexpr static const char *const TAG = "sdl";
class Sdl : public display::Display {
public:
display::DisplayType get_display_type() override { return display::DISPLAY_TYPE_COLOR; }
void update() override;
void loop() override;
void setup() override;
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
void draw_pixel_at(int x, int y, Color color) override;
void set_dimensions(uint16_t width, uint16_t height) {
this->width_ = width;
this->height_ = height;
}
int get_width() override { return this->width_; }
int get_height() override { return this->height_; }
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void dump_config() override { LOG_DISPLAY("", "SDL", this); }
int mouse_x{};
int mouse_y{};
bool mouse_down{};
protected:
int get_width_internal() override { return this->width_; }
int get_height_internal() override { return this->height_; }
int width_{};
int height_{};
SDL_Renderer *renderer_{};
SDL_Window *window_{};
SDL_Texture *texture_{};
uint16_t x_low_{0};
uint16_t y_low_{0};
uint16_t x_high_{0};
uint16_t y_high_{0};
};
} // namespace sdl
} // namespace esphome
#endif

View file

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.components import touchscreen
from ..display import Sdl, sdl_ns, CONF_SDL_ID
SdlTouchscreen = sdl_ns.class_("SdlTouchscreen", touchscreen.Touchscreen)
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SdlTouchscreen),
cv.GenerateID(CONF_SDL_ID): cv.use_id(Sdl),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_parented(var, config[CONF_SDL_ID])
await touchscreen.register_touchscreen(var, config)

View file

@ -0,0 +1,26 @@
#pragma once
#ifdef USE_HOST
#include "../sdl_esphome.h"
#include "esphome/components/touchscreen/touchscreen.h"
namespace esphome {
namespace sdl {
class SdlTouchscreen : public touchscreen::Touchscreen, public Parented<Sdl> {
public:
void setup() override {
this->x_raw_max_ = this->display_->get_width();
this->y_raw_max_ = this->display_->get_height();
}
void update_touches() override {
if (this->parent_->mouse_down) {
add_raw_touch_position_(0, this->parent_->mouse_x, this->parent_->mouse_y);
}
}
};
} // namespace sdl
} // namespace esphome
#endif

View file

@ -0,0 +1,108 @@
from esphome import automation
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ID,
CONF_MQTT_ID,
CONF_WEB_SERVER_ID,
DEVICE_CLASS_FIRMWARE,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@jesserockz"]
IS_PLATFORM_COMPONENT = True
update_ns = cg.esphome_ns.namespace("update")
UpdateEntity = update_ns.class_("UpdateEntity", cg.EntityBase)
UpdateInfo = update_ns.struct("UpdateInfo")
PerformAction = update_ns.class_("PerformAction", automation.Action)
IsAvailableCondition = update_ns.class_("IsAvailableCondition", automation.Condition)
DEVICE_CLASSES = [
DEVICE_CLASS_FIRMWARE,
]
CONF_ON_UPDATE_AVAILABLE = "on_update_available"
UPDATE_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.MQTTUpdateComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_ON_UPDATE_AVAILABLE): automation.validate_automation(
single=True
),
}
)
)
async def setup_update_core_(var, config):
await setup_entity(var, config)
if device_class_config := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(device_class_config))
if on_update_available := config.get(CONF_ON_UPDATE_AVAILABLE):
await automation.build_automation(
var.get_update_available_trigger(),
[(UpdateInfo.operator("ref").operator("const"), "x")],
on_update_available,
)
if mqtt_id_config := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id_config, var)
await mqtt.register_mqtt_component(mqtt_, config)
if web_server_id_config := config.get(CONF_WEB_SERVER_ID):
web_server_ = cg.get_variable(web_server_id_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_update(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_update(var))
await setup_update_core_(var, config)
async def new_update(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_update(var, config)
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_UPDATE")
cg.add_global(update_ns.using)
UPDATE_AUTOMATION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(UpdateEntity),
}
)
@automation.register_action("update.perform", PerformAction, UPDATE_AUTOMATION_SCHEMA)
async def update_perform_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, paren, paren)
@automation.register_condition(
"update.is_available", IsAvailableCondition, UPDATE_AUTOMATION_SCHEMA
)
async def update_is_available_condition_to_code(
config, condition_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(condition_id, paren, paren)

View file

@ -0,0 +1,12 @@
#include "update_entity.h"
namespace esphome {
namespace update {
void UpdateEntity::publish_state() {
this->has_state_ = true;
this->state_callback_.call();
}
} // namespace update
} // namespace esphome

View file

@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
namespace esphome {
namespace update {
struct UpdateInfo {
std::string latest_version;
std::string current_version;
std::string title;
std::string summary;
std::string release_url;
std::string firmware_url;
std::string md5;
bool has_progress{false};
float progress;
};
enum UpdateState : uint8_t {
UPDATE_STATE_UNKNOWN,
UPDATE_STATE_NO_UPDATE,
UPDATE_STATE_AVAILABLE,
UPDATE_STATE_INSTALLING,
};
class UpdateEntity : public EntityBase, public EntityBase_DeviceClass {
public:
bool has_state() const { return this->has_state_; }
void publish_state();
virtual void perform() = 0;
const UpdateInfo &update_info = update_info_;
const UpdateState &state = state_;
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
protected:
UpdateState state_{UPDATE_STATE_UNKNOWN};
UpdateInfo update_info_;
bool has_state_{false};
CallbackManager<void()> state_callback_{};
};
} // namespace update
} // namespace esphome

View file

@ -94,6 +94,9 @@ WaveshareEPaper2P13InV2 = waveshare_epaper_ns.class_(
WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_( WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_(
"WaveshareEPaper2P13InV3", WaveshareEPaper "WaveshareEPaper2P13InV3", WaveshareEPaper
) )
WaveshareEPaper13P3InK = waveshare_epaper_ns.class_(
"WaveshareEPaper13P3InK", WaveshareEPaper
)
GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper)
WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel")
@ -133,6 +136,7 @@ MODELS = {
"2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE),
"2.13inv3": ("c", WaveshareEPaper2P13InV3), "2.13inv3": ("c", WaveshareEPaper2P13InV3),
"1.54in-m5coreink-m09": ("c", GDEW0154M09), "1.54in-m5coreink-m09": ("c", GDEW0154M09),
"13.3in-k": ("b", WaveshareEPaper13P3InK),
} }
RESET_PIN_REQUIRED_MODELS = ("2.13inv2", "2.13in-ttgo-b74") RESET_PIN_REQUIRED_MODELS = ("2.13inv2", "2.13in-ttgo-b74")

View file

@ -2963,5 +2963,88 @@ void WaveshareEPaper2P13InDKE::set_full_update_every(uint32_t full_update_every)
this->full_update_every_ = full_update_every; this->full_update_every_ = full_update_every;
} }
// ========================================================
// 13.3in (K version)
// Datasheet/Specification/Reference:
// - https://files.waveshare.com/wiki/13.3inch-e-Paper-HAT-(K)/13.3-inch-e-Paper-(K)-user-manual.pdf
// - https://github.com/waveshareteam/e-Paper/tree/master/Arduino/epd13in3k
// ========================================================
// using default wait_until_idle_() function
void WaveshareEPaper13P3InK::initialize() {
this->wait_until_idle_();
this->command(0x12); // SWRESET
this->wait_until_idle_();
this->command(0x0c); // set soft start
this->data(0xae);
this->data(0xc7);
this->data(0xc3);
this->data(0xc0);
this->data(0x80);
this->command(0x01); // driver output control
this->data((get_height_internal() - 1) % 256); // Y
this->data((get_height_internal() - 1) / 256); // Y
this->data(0x00);
this->command(0x11); // data entry mode
this->data(0x03);
// SET WINDOWS
// XRAM_START_AND_END_POSITION
this->command(0x44);
this->data(0 & 0xFF);
this->data((0 >> 8) & 0x03);
this->data((get_width_internal() - 1) & 0xFF);
this->data(((get_width_internal() - 1) >> 8) & 0x03);
// YRAM_START_AND_END_POSITION
this->command(0x45);
this->data(0 & 0xFF);
this->data((0 >> 8) & 0x03);
this->data((get_height_internal() - 1) & 0xFF);
this->data(((get_height_internal() - 1) >> 8) & 0x03);
this->command(0x3C); // Border setting
this->data(0x01);
this->command(0x18); // use the internal temperature sensor
this->data(0x80);
// SET CURSOR
// XRAM_ADDRESS
this->command(0x4E);
this->data(0 & 0xFF);
this->data((0 >> 8) & 0x03);
// YRAM_ADDRESS
this->command(0x4F);
this->data(0 & 0xFF);
this->data((0 >> 8) & 0x03);
}
void HOT WaveshareEPaper13P3InK::display() {
// do single full update
this->command(0x24);
this->start_data_();
this->write_array(this->buffer_, this->get_buffer_length_());
this->end_data_();
// COMMAND DISPLAY REFRESH
this->command(0x22);
this->data(0xF7);
this->command(0x20);
}
int WaveshareEPaper13P3InK::get_width_internal() { return 960; }
int WaveshareEPaper13P3InK::get_height_internal() { return 680; }
uint32_t WaveshareEPaper13P3InK::idle_timeout_() { return 10000; }
void WaveshareEPaper13P3InK::dump_config() {
LOG_DISPLAY("", "Waveshare E-Paper", this);
ESP_LOGCONFIG(TAG, " Model: 13.3inK");
LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_);
LOG_UPDATE_INTERVAL(this);
}
} // namespace waveshare_epaper } // namespace waveshare_epaper
} // namespace esphome } // namespace esphome

View file

@ -163,6 +163,7 @@ enum WaveshareEPaperTypeBModel {
WAVESHARE_EPAPER_7_5_IN, WAVESHARE_EPAPER_7_5_IN,
WAVESHARE_EPAPER_7_5_INV2, WAVESHARE_EPAPER_7_5_INV2,
WAVESHARE_EPAPER_7_5_IN_B_V2, WAVESHARE_EPAPER_7_5_IN_B_V2,
WAVESHARE_EPAPER_13_3_IN_K,
}; };
class WaveshareEPaper2P7In : public WaveshareEPaper { class WaveshareEPaper2P7In : public WaveshareEPaper {
@ -769,5 +770,28 @@ class WaveshareEPaper2P13InV3 : public WaveshareEPaper {
bool is_busy_{false}; bool is_busy_{false};
void write_lut_(const uint8_t *lut); void write_lut_(const uint8_t *lut);
}; };
class WaveshareEPaper13P3InK : public WaveshareEPaper {
public:
void initialize() override;
void display() override;
void dump_config() override;
void deep_sleep() override {
// COMMAND DEEP SLEEP
this->command(0x10);
this->data(0x01);
}
protected:
int get_width_internal() override;
int get_height_internal() override;
uint32_t idle_timeout_() override;
};
} // namespace waveshare_epaper } // namespace waveshare_epaper
} // namespace esphome } // namespace esphome

View file

@ -177,5 +177,14 @@ bool ListEntitiesIterator::on_event(event::Event *event) {
} }
#endif #endif
#ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->update_json(update, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
} // namespace web_server } // namespace web_server
} // namespace esphome } // namespace esphome

View file

@ -68,6 +68,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_EVENT #ifdef USE_EVENT
bool on_event(event::Event *event) override; bool on_event(event::Event *event) override;
#endif #endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
#endif
protected: protected:
WebServer *web_server_; WebServer *web_server_;

View file

@ -1501,6 +1501,65 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
} }
#endif #endif
#ifdef USE_UPDATE
void WebServer::on_update(update::UpdateEntity *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->update_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (update::UpdateEntity *obj : App.get_updates()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->update_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
}
if (match.method != "install") {
request->send(404);
return;
}
this->schedule_([obj]() mutable { obj->perform(); });
request->send(200);
return;
}
request->send(404);
}
std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "update-" + obj->get_object_id(), start_config);
root["value"] = obj->update_info.latest_version;
switch (obj->state) {
case update::UPDATE_STATE_NO_UPDATE:
root["state"] = "NO UPDATE";
break;
case update::UPDATE_STATE_AVAILABLE:
root["state"] = "UPDATE AVAILABLE";
break;
case update::UPDATE_STATE_INSTALLING:
root["state"] = "INSTALLING";
break;
default:
root["state"] = "UNKNOWN";
break;
}
if (start_config == DETAIL_ALL) {
root["current_version"] = obj->update_info.current_version;
root["title"] = obj->update_info.title;
root["summary"] = obj->update_info.summary;
root["release_url"] = obj->update_info.release_url;
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
}
}
});
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) { bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/") if (request->url() == "/")
return true; return true;
@ -1620,6 +1679,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true; return true;
#endif #endif
#ifdef USE_UPDATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update")
return true;
#endif
return false; return false;
} }
void WebServer::handleRequest(AsyncWebServerRequest *request) { void WebServer::handleRequest(AsyncWebServerRequest *request) {
@ -1777,6 +1841,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return; return;
} }
#endif #endif
#ifdef USE_UPDATE
if (match.domain == "update") {
this->handle_update_request(request, match);
return;
}
#endif
} }
bool WebServer::isRequestHandlerTrivial() { return false; } bool WebServer::isRequestHandlerTrivial() { return false; }

View file

@ -7,8 +7,8 @@
#include "esphome/core/controller.h" #include "esphome/core/controller.h"
#include "esphome/core/entity_base.h" #include "esphome/core/entity_base.h"
#include <vector>
#include <map> #include <map>
#include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
@ -319,6 +319,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config); std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config);
#endif #endif
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
/// Handle a update request under '/update/<id>'.
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the update state with its value as a JSON string.
std::string update_json(update::UpdateEntity *obj, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method. /// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override; bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method. /// Override the web handler's handleRequest method.

View file

@ -8,6 +8,7 @@ from esphome.const import (
CONF_INC_PIN, CONF_INC_PIN,
CONF_UD_PIN, CONF_UD_PIN,
CONF_INITIAL_VALUE, CONF_INITIAL_VALUE,
CONF_STEP_DELAY,
) )
CODEOWNERS = ["@EtienneMD"] CODEOWNERS = ["@EtienneMD"]
@ -26,6 +27,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INITIAL_VALUE, default=1.0): cv.float_range( cv.Optional(CONF_INITIAL_VALUE, default=1.0): cv.float_range(
min=0.01, max=1.0 min=0.01, max=1.0
), ),
cv.Optional(CONF_STEP_DELAY, default=1): cv.int_range(min=1, max=100),
} }
) )
) )
@ -44,3 +46,4 @@ async def to_code(config):
cg.add(var.set_ud_pin(ud_pin)) cg.add(var.set_ud_pin(ud_pin))
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
cg.add(var.set_step_delay(config[CONF_STEP_DELAY]))

View file

@ -22,9 +22,9 @@ void X9cOutput::trim_value(int change_amount) {
for (int i = 0; i < abs(change_amount); i++) { // Move wiper for (int i = 0; i < abs(change_amount); i++) { // Move wiper
this->inc_pin_->digital_write(true); this->inc_pin_->digital_write(true);
delayMicroseconds(1); delayMicroseconds(this->step_delay_);
this->inc_pin_->digital_write(false); this->inc_pin_->digital_write(false);
delayMicroseconds(1); delayMicroseconds(this->step_delay_);
} }
delayMicroseconds(100); // Let value settle delayMicroseconds(100); // Let value settle
@ -69,6 +69,7 @@ void X9cOutput::dump_config() {
LOG_PIN(" Increment Pin: ", this->inc_pin_); LOG_PIN(" Increment Pin: ", this->inc_pin_);
LOG_PIN(" Up/Down Pin: ", this->ud_pin_); LOG_PIN(" Up/Down Pin: ", this->ud_pin_);
ESP_LOGCONFIG(TAG, " Initial Value: %f", this->initial_value_); ESP_LOGCONFIG(TAG, " Initial Value: %f", this->initial_value_);
ESP_LOGCONFIG(TAG, " Step Delay: %d", this->step_delay_);
LOG_FLOAT_OUTPUT(this); LOG_FLOAT_OUTPUT(this);
} }

View file

@ -13,6 +13,7 @@ class X9cOutput : public output::FloatOutput, public Component {
void set_inc_pin(InternalGPIOPin *pin) { inc_pin_ = pin; } void set_inc_pin(InternalGPIOPin *pin) { inc_pin_ = pin; }
void set_ud_pin(InternalGPIOPin *pin) { ud_pin_ = pin; } void set_ud_pin(InternalGPIOPin *pin) { ud_pin_ = pin; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; } void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_step_delay(int step_delay) { step_delay_ = step_delay; }
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
@ -26,6 +27,7 @@ class X9cOutput : public output::FloatOutput, public Component {
InternalGPIOPin *ud_pin_; InternalGPIOPin *ud_pin_;
float initial_value_; float initial_value_;
float pot_value_; float pot_value_;
int step_delay_;
}; };
} // namespace x9c } // namespace x9c

View file

@ -23,7 +23,7 @@ from esphome.const import (
CONF_EXTERNAL_COMPONENTS, CONF_EXTERNAL_COMPONENTS,
TARGET_PLATFORMS, TARGET_PLATFORMS,
) )
from esphome.core import CORE, EsphomeError from esphome.core import CORE, EsphomeError, DocumentRange
from esphome.helpers import indent from esphome.helpers import indent
from esphome.util import safe_print, OrderedDict from esphome.util import safe_print, OrderedDict
@ -139,7 +139,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
) )
def run_validation_steps(self): def run_validation_steps(self):
while self._validation_tasks: while self._validation_tasks and not self.errors:
task = heapq.heappop(self._validation_tasks) task = heapq.heappop(self._validation_tasks)
task.step.run(self) task.step.run(self)
@ -184,7 +184,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
def get_deepest_document_range_for_path( def get_deepest_document_range_for_path(
self, path: ConfigPath, get_key: bool = False self, path: ConfigPath, get_key: bool = False
) -> ESPHomeDataBase | None: ) -> DocumentRange | None:
data = self data = self
doc_range = None doc_range = None
for index, path_item in enumerate(path): for index, path_item in enumerate(path):
@ -1123,4 +1123,4 @@ def read_config(command_line_substitutions):
safe_print("") safe_print("")
return None return None
return OrderedDict(res) return res

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2024.6.0-dev" __version__ = "2024.7.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
@ -785,6 +785,7 @@ CONF_STATIC_IP = "static_ip"
CONF_STATUS = "status" CONF_STATUS = "status"
CONF_STB_PIN = "stb_pin" CONF_STB_PIN = "stb_pin"
CONF_STEP = "step" CONF_STEP = "step"
CONF_STEP_DELAY = "step_delay"
CONF_STEP_MODE = "step_mode" CONF_STEP_MODE = "step_mode"
CONF_STEP_PIN = "step_pin" CONF_STEP_PIN = "step_pin"
CONF_STOP = "stop" CONF_STOP = "stop"
@ -1084,6 +1085,7 @@ DEVICE_CLASS_DURATION = "duration"
DEVICE_CLASS_EMPTY = "" DEVICE_CLASS_EMPTY = ""
DEVICE_CLASS_ENERGY = "energy" DEVICE_CLASS_ENERGY = "energy"
DEVICE_CLASS_ENERGY_STORAGE = "energy_storage" DEVICE_CLASS_ENERGY_STORAGE = "energy_storage"
DEVICE_CLASS_FIRMWARE = "firmware"
DEVICE_CLASS_FREQUENCY = "frequency" DEVICE_CLASS_FREQUENCY = "frequency"
DEVICE_CLASS_GARAGE = "garage" DEVICE_CLASS_GARAGE = "garage"
DEVICE_CLASS_GARAGE_DOOR = "garage_door" DEVICE_CLASS_GARAGE_DOOR = "garage_door"

View file

@ -69,6 +69,9 @@
#ifdef USE_EVENT #ifdef USE_EVENT
#include "esphome/components/event/event.h" #include "esphome/components/event/event.h"
#endif #endif
#ifdef USE_UPDATE
#include "esphome/components/update/update_entity.h"
#endif
namespace esphome { namespace esphome {
@ -178,6 +181,10 @@ class Application {
void register_event(event::Event *event) { this->events_.push_back(event); } void register_event(event::Event *event) { this->events_.push_back(event); }
#endif #endif
#ifdef USE_UPDATE
void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); }
#endif
/// Register the component in this Application instance. /// Register the component in this Application instance.
template<class C> C *register_component(C *c) { template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered"); static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@ -421,6 +428,16 @@ class Application {
} }
#endif #endif
#ifdef USE_UPDATE
const std::vector<update::UpdateEntity *> &get_updates() { return this->updates_; }
update::UpdateEntity *get_update_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->updates_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler; Scheduler scheduler;
protected: protected:
@ -495,6 +512,9 @@ class Application {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
std::vector<alarm_control_panel::AlarmControlPanel *> alarm_control_panels_{}; std::vector<alarm_control_panel::AlarmControlPanel *> alarm_control_panels_{};
#endif #endif
#ifdef USE_UPDATE
std::vector<update::UpdateEntity *> updates_{};
#endif
std::string name_; std::string name_;
std::string friendly_name_; std::string friendly_name_;

View file

@ -351,6 +351,21 @@ void ComponentIterator::advance() {
} }
} }
break; break;
#endif
#ifdef USE_UPDATE
case IteratorState::UPDATE:
if (this->at_ >= App.get_updates().size()) {
advance_platform = true;
} else {
auto *update = App.get_updates()[this->at_];
if (update->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_update(update);
}
}
break;
#endif #endif
case IteratorState::MAX: case IteratorState::MAX:
if (this->on_end()) { if (this->on_end()) {

View file

@ -86,6 +86,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
virtual bool on_event(event::Event *event) = 0; virtual bool on_event(event::Event *event) = 0;
#endif
#ifdef USE_UPDATE
virtual bool on_update(update::UpdateEntity *update) = 0;
#endif #endif
virtual bool on_end(); virtual bool on_end();
@ -158,6 +161,9 @@ class ComponentIterator {
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
EVENT, EVENT,
#endif
#ifdef USE_UPDATE
UPDATE,
#endif #endif
MAX, MAX,
} state_{IteratorState::NONE}; } state_{IteratorState::NONE};

View file

@ -121,6 +121,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); }); obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); });
} }
#endif #endif
#ifdef USE_UPDATE
for (auto *obj : App.get_updates()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_update(obj); });
}
#endif
} }
} // namespace esphome } // namespace esphome

View file

@ -61,6 +61,9 @@
#ifdef USE_EVENT #ifdef USE_EVENT
#include "esphome/components/event/event.h" #include "esphome/components/event/event.h"
#endif #endif
#ifdef USE_UPDATE
#include "esphome/components/update/update_entity.h"
#endif
namespace esphome { namespace esphome {
@ -124,6 +127,9 @@ class Controller {
#ifdef USE_EVENT #ifdef USE_EVENT
virtual void on_event(event::Event *obj, const std::string &event_type){}; virtual void on_event(event::Event *obj, const std::string &event_type){};
#endif #endif
#ifdef USE_UPDATE
virtual void on_update(update::UpdateEntity *obj){};
#endif
}; };
} // namespace esphome } // namespace esphome

View file

@ -59,6 +59,7 @@
#define USE_TIME #define USE_TIME
#define USE_TOUCHSCREEN #define USE_TOUCHSCREEN
#define USE_UART_DEBUGGER #define USE_UART_DEBUGGER
#define USE_UPDATE
#define USE_VALVE #define USE_VALVE
#define USE_WIFI #define USE_WIFI
#define USE_WIFI_AP #define USE_WIFI_AP

View file

@ -17,6 +17,7 @@ import time
from collections.abc import Iterable from collections.abc import Iterable
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, TypeVar from typing import TYPE_CHECKING, Any, Callable, TypeVar
from urllib.parse import urlparse
import tornado import tornado
import tornado.concurrent import tornado.concurrent
@ -166,6 +167,18 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
# use Popen() with a reading thread instead # use Popen() with a reading thread instead
self._use_popen = os.name == "nt" self._use_popen = os.name == "nt"
def check_origin(self, origin):
if "ESPHOME_TRUSTED_DOMAINS" not in os.environ:
return super().check_origin(origin)
trusted_domains = [
s.strip() for s in os.environ["ESPHOME_TRUSTED_DOMAINS"].split(",")
]
url = urlparse(origin)
if url.hostname in trusted_domains:
return True
_LOGGER.info("check_origin %s, domain is not trusted", origin)
return False
def open(self, *args: str, **kwargs: str) -> None: def open(self, *args: str, **kwargs: str) -> None:
"""Handle new WebSocket connection.""" """Handle new WebSocket connection."""
# Ensure messages from the subprocess are sent immediately # Ensure messages from the subprocess are sent immediately

View file

@ -630,7 +630,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/lock/lock.h", "esphome/components/lock/lock.h",
"esphome/components/mqtt/mqtt_component.h", "esphome/components/mqtt/mqtt_component.h",
"esphome/components/number/number.h", "esphome/components/number/number.h",
"esphome/components/text/text.h", "esphome/components/one_wire/one_wire.h",
"esphome/components/output/binary_output.h", "esphome/components/output/binary_output.h",
"esphome/components/output/float_output.h", "esphome/components/output/float_output.h",
"esphome/components/nextion/nextion_base.h", "esphome/components/nextion/nextion_base.h",
@ -638,6 +638,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/sensor/sensor.h", "esphome/components/sensor/sensor.h",
"esphome/components/stepper/stepper.h", "esphome/components/stepper/stepper.h",
"esphome/components/switch/switch.h", "esphome/components/switch/switch.h",
"esphome/components/text/text.h",
"esphome/components/text_sensor/text_sensor.h", "esphome/components/text_sensor/text_sensor.h",
"esphome/components/valve/valve.h", "esphome/components/valve/valve.h",
"esphome/core/component.h", "esphome/core/component.h",

View file

@ -10,6 +10,7 @@ from homeassistant.components.event import EventDeviceClass
from homeassistant.components.number import NumberDeviceClass from homeassistant.components.number import NumberDeviceClass
from homeassistant.components.sensor import SensorDeviceClass from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.components.update import UpdateDeviceClass
from homeassistant.components.valve import ValveDeviceClass from homeassistant.components.valve import ValveDeviceClass
# pylint: enable=import-error # pylint: enable=import-error
@ -27,6 +28,7 @@ DOMAINS = {
"number": NumberDeviceClass, "number": NumberDeviceClass,
"sensor": SensorDeviceClass, "sensor": SensorDeviceClass,
"switch": SwitchDeviceClass, "switch": SwitchDeviceClass,
"update": UpdateDeviceClass,
"valve": ValveDeviceClass, "valve": ValveDeviceClass,
} }

View file

@ -1,11 +1,11 @@
dallas: one_wire:
- platform: gpio
pin: 4 pin: 4
sensor: sensor:
- platform: dallas - platform: dallas_temp
address: 0x1C0000031EDD2A28 address: 0x1C0000031EDD2A28
name: Dallas Temperature name: Dallas Temperature
resolution: 9 resolution: 9
- platform: dallas - platform: dallas_temp
index: 1
name: Dallas Temperature name: Dallas Temperature

View file

@ -73,3 +73,9 @@ button:
url: http://my.ha.net:8123/local/esphome/firmware.bin url: http://my.ha.net:8123/local/esphome/firmware.bin
- logger.log: "This message should be not displayed (reboot)" - logger.log: "This message should be not displayed (reboot)"
update:
- platform: http_request
name: OTA Update
id: ota_update
source: http://my.ha.net:8123/local/esphome/manifest.json

View file

@ -0,0 +1,13 @@
substitutions:
verify_ssl: "true"
http_request:
verify_ssl: ${verify_ssl}
ota:
- platform: http_request
update:
- platform: http_request
name: "OTA Update"
source: https://example.com/ota.json

View file

@ -1,2 +1,3 @@
packages: packages:
common: !include common.yaml common: !include common.yaml
update: !include common-update.yaml

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