mirror of
https://github.com/esphome/esphome.git
synced 2026-02-14 13:37:34 -07:00
Compare commits
2 Commits
dev
...
api-string
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a51fcf9be2 | ||
|
|
0b2f79480b |
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
@@ -86,6 +86,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
@@ -411,7 +411,6 @@ esphome/components/rp2040_pwm/* @jesserockz
|
|||||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||||
esphome/components/rtl87xx/* @kuba2k2
|
esphome/components/rtl87xx/* @kuba2k2
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/runtime_image/* @clydebarrow @guillempages @kahrendt
|
|
||||||
esphome/components/runtime_stats/* @bdraco
|
esphome/components/runtime_stats/* @bdraco
|
||||||
esphome/components/rx8130/* @beormund
|
esphome/components/rx8130/* @beormund
|
||||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||||
|
|||||||
@@ -57,8 +57,14 @@ def maybe_conf(conf, *validators):
|
|||||||
return validate
|
return validate
|
||||||
|
|
||||||
|
|
||||||
def register_action(name: str, action_type: MockObjClass, schema: cv.Schema):
|
def register_action(
|
||||||
return ACTION_REGISTRY.register(name, action_type, schema)
|
name: str,
|
||||||
|
action_type: MockObjClass,
|
||||||
|
schema: cv.Schema,
|
||||||
|
*,
|
||||||
|
deferred: bool = False,
|
||||||
|
):
|
||||||
|
return ACTION_REGISTRY.register(name, action_type, schema, deferred=deferred)
|
||||||
|
|
||||||
|
|
||||||
def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
|
def register_condition(name: str, condition_type: MockObjClass, schema: cv.Schema):
|
||||||
@@ -335,7 +341,10 @@ async def component_is_idle_condition_to_code(
|
|||||||
|
|
||||||
|
|
||||||
@register_action(
|
@register_action(
|
||||||
"delay", DelayAction, cv.templatable(cv.positive_time_period_milliseconds)
|
"delay",
|
||||||
|
DelayAction,
|
||||||
|
cv.templatable(cv.positive_time_period_milliseconds),
|
||||||
|
deferred=True,
|
||||||
)
|
)
|
||||||
async def delay_action_to_code(
|
async def delay_action_to_code(
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
@@ -445,7 +454,7 @@ _validate_wait_until = cv.maybe_simple_value(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
|
@register_action("wait_until", WaitUntilAction, _validate_wait_until, deferred=True)
|
||||||
async def wait_until_action_to_code(
|
async def wait_until_action_to_code(
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
action_id: ID,
|
action_id: ID,
|
||||||
@@ -578,6 +587,26 @@ async def build_condition_list(
|
|||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
|
def has_deferred_actions(actions: ConfigType) -> bool:
|
||||||
|
"""Check if a validated action list contains any deferred actions.
|
||||||
|
|
||||||
|
Deferred actions (delay, wait_until, script.wait) store trigger args
|
||||||
|
for later execution, making non-owning types like StringRef unsafe.
|
||||||
|
"""
|
||||||
|
if isinstance(actions, list):
|
||||||
|
return any(has_deferred_actions(item) for item in actions)
|
||||||
|
if isinstance(actions, dict):
|
||||||
|
for key in actions:
|
||||||
|
if key in ACTION_REGISTRY and ACTION_REGISTRY[key].deferred:
|
||||||
|
return True
|
||||||
|
return any(
|
||||||
|
has_deferred_actions(v)
|
||||||
|
for v in actions.values()
|
||||||
|
if isinstance(v, (list, dict))
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def build_automation(
|
async def build_automation(
|
||||||
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
trigger: MockObj, args: TemplateArgsType, config: ConfigType
|
||||||
) -> MockObj:
|
) -> MockObj:
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = {
|
|||||||
"bool": cg.bool_,
|
"bool": cg.bool_,
|
||||||
"int": cg.int32,
|
"int": cg.int32,
|
||||||
"float": cg.float_,
|
"float": cg.float_,
|
||||||
"string": cg.std_string,
|
"string": cg.StringRef,
|
||||||
"bool[]": cg.FixedVector.template(cg.bool_).operator("const").operator("ref"),
|
"bool[]": cg.FixedVector.template(cg.bool_).operator("const").operator("ref"),
|
||||||
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
||||||
"float[]": cg.FixedVector.template(cg.float_).operator("const").operator("ref"),
|
"float[]": cg.FixedVector.template(cg.float_).operator("const").operator("ref"),
|
||||||
@@ -380,9 +380,16 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
if is_optional:
|
if is_optional:
|
||||||
func_args.append((cg.bool_, "return_response"))
|
func_args.append((cg.bool_, "return_response"))
|
||||||
|
|
||||||
|
# Check if action chain has deferred actions that would make
|
||||||
|
# non-owning StringRef dangle (rx_buf_ reused after delay)
|
||||||
|
has_deferred = automation.has_deferred_actions(conf.get(CONF_THEN, []))
|
||||||
|
|
||||||
service_arg_names: list[str] = []
|
service_arg_names: list[str] = []
|
||||||
for name, var_ in conf[CONF_VARIABLES].items():
|
for name, var_ in conf[CONF_VARIABLES].items():
|
||||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||||
|
# Fall back to std::string for string args if deferred actions exist
|
||||||
|
if has_deferred and native is cg.StringRef:
|
||||||
|
native = cg.std_string
|
||||||
service_template_args.append(native)
|
service_template_args.append(native)
|
||||||
func_args.append((native, name))
|
func_args.append((native, name))
|
||||||
service_arg_names.append(name)
|
service_arg_names.append(name)
|
||||||
|
|||||||
@@ -824,7 +824,7 @@ message HomeAssistantStateResponse {
|
|||||||
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||||
|
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
string state = 2;
|
string state = 2 [(null_terminate) = true];
|
||||||
string attribute = 3;
|
string attribute = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,7 +882,7 @@ message ExecuteServiceArgument {
|
|||||||
bool bool_ = 1;
|
bool bool_ = 1;
|
||||||
int32 legacy_int = 2;
|
int32 legacy_int = 2;
|
||||||
float float_ = 3;
|
float float_ = 3;
|
||||||
string string_ = 4;
|
string string_ = 4 [(null_terminate) = true];
|
||||||
// ESPHome 1.14 (api v1.3) make int a signed value
|
// ESPHome 1.14 (api v1.3) make int a signed value
|
||||||
sint32 int_ = 5;
|
sint32 int_ = 5;
|
||||||
repeated bool bool_array = 6 [packed=false, (fixed_vector) = true];
|
repeated bool bool_array = 6 [packed=false, (fixed_vector) = true];
|
||||||
|
|||||||
@@ -1683,31 +1683,18 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto &it : this->parent_->get_state_subs()) {
|
for (auto &it : this->parent_->get_state_subs()) {
|
||||||
// Compare entity_id: check length matches and content matches
|
if (msg.entity_id != it.entity_id) {
|
||||||
size_t entity_id_len = strlen(it.entity_id);
|
|
||||||
if (entity_id_len != msg.entity_id.size() ||
|
|
||||||
memcmp(it.entity_id, msg.entity_id.c_str(), msg.entity_id.size()) != 0) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare attribute: either both have matching attribute, or both have none
|
// Compare attribute: either both have matching attribute, or both have none
|
||||||
size_t sub_attr_len = it.attribute != nullptr ? strlen(it.attribute) : 0;
|
// it.attribute can be nullptr (meaning no attribute filter)
|
||||||
if (sub_attr_len != msg.attribute.size() ||
|
if (it.attribute != nullptr ? msg.attribute != it.attribute : !msg.attribute.empty()) {
|
||||||
(sub_attr_len > 0 && memcmp(it.attribute, msg.attribute.c_str(), sub_attr_len) != 0)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create null-terminated state for callback (parse_number needs null-termination)
|
// msg.state is already null-terminated in-place after protobuf decode
|
||||||
// HA state max length is 255 characters, but attributes can be much longer
|
it.callback(msg.state);
|
||||||
// Use stack buffer for common case (states), heap fallback for large attributes
|
|
||||||
size_t state_len = msg.state.size();
|
|
||||||
SmallBufferWithHeapFallback<MAX_STATE_LEN + 1> state_buf_alloc(state_len + 1);
|
|
||||||
char *state_buf = reinterpret_cast<char *>(state_buf_alloc.get());
|
|
||||||
if (state_len > 0) {
|
|
||||||
memcpy(state_buf, msg.state.c_str(), state_len);
|
|
||||||
}
|
|
||||||
state_buf[state_len] = '\0';
|
|
||||||
it.callback(StringRef(state_buf, state_len));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -201,9 +201,10 @@ APIError APINoiseFrameHelper::try_read_frame_() {
|
|||||||
return (state_ == State::DATA) ? APIError::BAD_DATA_PACKET : APIError::BAD_HANDSHAKE_PACKET_LEN;
|
return (state_ == State::DATA) ? APIError::BAD_DATA_PACKET : APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserve space for body
|
// Reserve space for body (+1 for null terminator so protobuf StringRef fields
|
||||||
if (this->rx_buf_.size() != msg_size) {
|
// can be safely null-terminated in-place after decode)
|
||||||
this->rx_buf_.resize(msg_size);
|
if (this->rx_buf_.size() != msg_size + 1) {
|
||||||
|
this->rx_buf_.resize(msg_size + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < msg_size) {
|
if (rx_buf_len_ < msg_size) {
|
||||||
|
|||||||
@@ -163,9 +163,10 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
|
|||||||
}
|
}
|
||||||
// header reading done
|
// header reading done
|
||||||
|
|
||||||
// Reserve space for body
|
// Reserve space for body (+1 for null terminator so protobuf StringRef fields
|
||||||
if (this->rx_buf_.size() != this->rx_header_parsed_len_) {
|
// can be safely null-terminated in-place after decode)
|
||||||
this->rx_buf_.resize(this->rx_header_parsed_len_);
|
if (this->rx_buf_.size() != this->rx_header_parsed_len_ + 1) {
|
||||||
|
this->rx_buf_.resize(this->rx_header_parsed_len_ + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||||
|
|||||||
@@ -90,4 +90,13 @@ extend google.protobuf.FieldOptions {
|
|||||||
// - uint16_t <field>_length_{0};
|
// - uint16_t <field>_length_{0};
|
||||||
// - uint16_t <field>_count_{0};
|
// - uint16_t <field>_count_{0};
|
||||||
optional bool packed_buffer = 50015 [default=false];
|
optional bool packed_buffer = 50015 [default=false];
|
||||||
|
|
||||||
|
// null_terminate: Write a null byte after string data in the decode buffer.
|
||||||
|
// When set on a string field in a SOURCE_CLIENT (decodable) message, the
|
||||||
|
// generated decode() override writes '\0' at data[length] after decoding.
|
||||||
|
// This makes the StringRef safe for c_str() usage without copying.
|
||||||
|
// Safe because: (1) frame helpers reserve +1 byte in rx_buf_, and
|
||||||
|
// (2) the overwritten byte was already consumed during decode.
|
||||||
|
// Only mark fields that actually need null-terminated access.
|
||||||
|
optional bool null_terminate = 50016 [default=false];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -953,6 +953,12 @@ bool HomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
void HomeAssistantStateResponse::decode(const uint8_t *buffer, size_t length) {
|
||||||
|
ProtoDecodableMessage::decode(buffer, length);
|
||||||
|
if (!this->state.empty()) {
|
||||||
|
const_cast<char *>(this->state.c_str())[this->state.size()] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
@@ -1057,6 +1063,9 @@ void ExecuteServiceArgument::decode(const uint8_t *buffer, size_t length) {
|
|||||||
uint32_t count_string_array = ProtoDecodableMessage::count_repeated_field(buffer, length, 9);
|
uint32_t count_string_array = ProtoDecodableMessage::count_repeated_field(buffer, length, 9);
|
||||||
this->string_array.init(count_string_array);
|
this->string_array.init(count_string_array);
|
||||||
ProtoDecodableMessage::decode(buffer, length);
|
ProtoDecodableMessage::decode(buffer, length);
|
||||||
|
if (!this->string_.empty()) {
|
||||||
|
const_cast<char *>(this->string_.c_str())[this->string_.size()] = '\0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bool ExecuteServiceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool ExecuteServiceRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
switch (field_id) {
|
switch (field_id) {
|
||||||
|
|||||||
@@ -1095,6 +1095,7 @@ class HomeAssistantStateResponse final : public ProtoDecodableMessage {
|
|||||||
StringRef entity_id{};
|
StringRef entity_id{};
|
||||||
StringRef state{};
|
StringRef state{};
|
||||||
StringRef attribute{};
|
StringRef attribute{};
|
||||||
|
void decode(const uint8_t *buffer, size_t length) override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
const char *dump_to(DumpBuffer &out) const override;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/string_ref.h"
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &
|
|||||||
}
|
}
|
||||||
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
template<> float get_execute_arg_value<float>(const ExecuteServiceArgument &arg) { return arg.float_; }
|
||||||
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
template<> std::string get_execute_arg_value<std::string>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||||
|
// Zero-copy StringRef version for YAML-generated services (string_ is null-terminated after decode)
|
||||||
|
template<> StringRef get_execute_arg_value<StringRef>(const ExecuteServiceArgument &arg) { return arg.string_; }
|
||||||
|
|
||||||
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
|
// Legacy std::vector versions for external components using custom_api_device.h - optimized with reserve
|
||||||
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
template<> std::vector<bool> get_execute_arg_value<std::vector<bool>>(const ExecuteServiceArgument &arg) {
|
||||||
@@ -61,6 +64,8 @@ template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SER
|
|||||||
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||||
|
// Zero-copy StringRef version for YAML-generated services
|
||||||
|
template<> enums::ServiceArgType to_service_arg_type<StringRef>() { return enums::SERVICE_ARG_TYPE_STRING; }
|
||||||
|
|
||||||
// Legacy std::vector versions for external components using custom_api_device.h
|
// Legacy std::vector versions for external components using custom_api_device.h
|
||||||
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
|
||||||
|
|||||||
@@ -9,20 +9,9 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
// Include BearSSL error constants for TLS failure diagnostics
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
#include <bearssl/bearssl_ssl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome::http_request {
|
namespace esphome::http_request {
|
||||||
|
|
||||||
static const char *const TAG = "http_request.arduino";
|
static const char *const TAG = "http_request.arduino";
|
||||||
#ifdef USE_ESP8266
|
|
||||||
static constexpr int RX_BUFFER_SIZE = 512;
|
|
||||||
static constexpr int TX_BUFFER_SIZE = 512;
|
|
||||||
// ESP8266 Arduino core (WiFiClientSecureBearSSL.cpp) returns -1000 on OOM
|
|
||||||
static constexpr int ESP8266_SSL_ERR_OOM = -1000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||||
const std::string &body,
|
const std::string &body,
|
||||||
@@ -58,7 +47,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
|||||||
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||||
stream_ptr = std::make_unique<WiFiClientSecure>();
|
stream_ptr = std::make_unique<WiFiClientSecure>();
|
||||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
||||||
secure_client->setBufferSizes(RX_BUFFER_SIZE, TX_BUFFER_SIZE);
|
secure_client->setBufferSizes(512, 512);
|
||||||
secure_client->setInsecure();
|
secure_client->setInsecure();
|
||||||
} else {
|
} else {
|
||||||
stream_ptr = std::make_unique<WiFiClient>();
|
stream_ptr = std::make_unique<WiFiClient>();
|
||||||
@@ -118,42 +107,13 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
|||||||
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
if (container->status_code < 0) {
|
if (container->status_code < 0) {
|
||||||
#if defined(USE_ESP8266) && defined(USE_HTTP_REQUEST_ESP8266_HTTPS)
|
|
||||||
if (secure) {
|
|
||||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
|
||||||
int last_error = secure_client->getLastSSLError();
|
|
||||||
|
|
||||||
if (last_error != 0) {
|
|
||||||
const LogString *error_msg;
|
|
||||||
switch (last_error) {
|
|
||||||
case ESP8266_SSL_ERR_OOM:
|
|
||||||
error_msg = LOG_STR("Unable to allocate buffer memory");
|
|
||||||
break;
|
|
||||||
case BR_ERR_TOO_LARGE:
|
|
||||||
error_msg = LOG_STR("Incoming TLS record does not fit in receive buffer (BR_ERR_TOO_LARGE)");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_msg = LOG_STR("Unknown SSL error");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGW(TAG, "SSL failure: %s (Code: %d)", LOG_STR_ARG(error_msg), last_error);
|
|
||||||
if (last_error == ESP8266_SSL_ERR_OOM) {
|
|
||||||
ESP_LOGW(TAG, "Heap free: %u bytes, configured buffer sizes: %u bytes", ESP.getFreeHeap(),
|
|
||||||
static_cast<unsigned int>(RX_BUFFER_SIZE + TX_BUFFER_SIZE));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Connection failure with no error code");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
|
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
|
||||||
HTTPClient::errorToString(container->status_code).c_str());
|
HTTPClient::errorToString(container->status_code).c_str());
|
||||||
|
|
||||||
this->status_momentary_error("failed", 1000);
|
this->status_momentary_error("failed", 1000);
|
||||||
container->end();
|
container->end();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_success(container->status_code)) {
|
if (!is_success(container->status_code)) {
|
||||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
||||||
this->status_momentary_error("failed", 1000);
|
this->status_momentary_error("failed", 1000);
|
||||||
|
|||||||
@@ -2,34 +2,97 @@ import logging
|
|||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import runtime_image
|
from esphome.components.const import CONF_BYTE_ORDER, CONF_REQUEST_HEADERS
|
||||||
from esphome.components.const import CONF_REQUEST_HEADERS
|
|
||||||
from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent
|
from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent
|
||||||
|
from esphome.components.image import (
|
||||||
|
CONF_INVERT_ALPHA,
|
||||||
|
CONF_TRANSPARENCY,
|
||||||
|
IMAGE_SCHEMA,
|
||||||
|
Image_,
|
||||||
|
get_image_type_enum,
|
||||||
|
get_transparency_enum,
|
||||||
|
validate_settings,
|
||||||
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BUFFER_SIZE,
|
CONF_BUFFER_SIZE,
|
||||||
|
CONF_DITHER,
|
||||||
|
CONF_FILE,
|
||||||
|
CONF_FORMAT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_ON_ERROR,
|
CONF_ON_ERROR,
|
||||||
|
CONF_RESIZE,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_TYPE,
|
||||||
CONF_URL,
|
CONF_URL,
|
||||||
)
|
)
|
||||||
from esphome.core import Lambda
|
from esphome.core import Lambda
|
||||||
|
|
||||||
AUTO_LOAD = ["image", "runtime_image"]
|
AUTO_LOAD = ["image"]
|
||||||
DEPENDENCIES = ["display", "http_request"]
|
DEPENDENCIES = ["display", "http_request"]
|
||||||
CODEOWNERS = ["@guillempages", "@clydebarrow"]
|
CODEOWNERS = ["@guillempages", "@clydebarrow"]
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
CONF_ON_DOWNLOAD_FINISHED = "on_download_finished"
|
CONF_ON_DOWNLOAD_FINISHED = "on_download_finished"
|
||||||
|
CONF_PLACEHOLDER = "placeholder"
|
||||||
CONF_UPDATE = "update"
|
CONF_UPDATE = "update"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
online_image_ns = cg.esphome_ns.namespace("online_image")
|
online_image_ns = cg.esphome_ns.namespace("online_image")
|
||||||
|
|
||||||
OnlineImage = online_image_ns.class_(
|
ImageFormat = online_image_ns.enum("ImageFormat")
|
||||||
"OnlineImage", cg.PollingComponent, runtime_image.RuntimeImage
|
|
||||||
)
|
|
||||||
|
class Format:
|
||||||
|
def __init__(self, image_type):
|
||||||
|
self.image_type = image_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enum(self):
|
||||||
|
return getattr(ImageFormat, self.image_type)
|
||||||
|
|
||||||
|
def actions(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMPFormat(Format):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("BMP")
|
||||||
|
|
||||||
|
def actions(self):
|
||||||
|
cg.add_define("USE_ONLINE_IMAGE_BMP_SUPPORT")
|
||||||
|
|
||||||
|
|
||||||
|
class JPEGFormat(Format):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("JPEG")
|
||||||
|
|
||||||
|
def actions(self):
|
||||||
|
cg.add_define("USE_ONLINE_IMAGE_JPEG_SUPPORT")
|
||||||
|
cg.add_library("JPEGDEC", None, "https://github.com/bitbank2/JPEGDEC#ca1e0f2")
|
||||||
|
|
||||||
|
|
||||||
|
class PNGFormat(Format):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("PNG")
|
||||||
|
|
||||||
|
def actions(self):
|
||||||
|
cg.add_define("USE_ONLINE_IMAGE_PNG_SUPPORT")
|
||||||
|
cg.add_library("pngle", "1.1.0")
|
||||||
|
|
||||||
|
|
||||||
|
IMAGE_FORMATS = {
|
||||||
|
x.image_type: x
|
||||||
|
for x in (
|
||||||
|
BMPFormat(),
|
||||||
|
JPEGFormat(),
|
||||||
|
PNGFormat(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IMAGE_FORMATS.update({"JPG": IMAGE_FORMATS["JPEG"]})
|
||||||
|
|
||||||
|
OnlineImage = online_image_ns.class_("OnlineImage", cg.PollingComponent, Image_)
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
SetUrlAction = online_image_ns.class_(
|
SetUrlAction = online_image_ns.class_(
|
||||||
@@ -48,17 +111,29 @@ DownloadErrorTrigger = online_image_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_options(*options):
|
||||||
|
return {
|
||||||
|
cv.Optional(option): cv.invalid(
|
||||||
|
f"{option} is an invalid option for online_image"
|
||||||
|
)
|
||||||
|
for option in options
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ONLINE_IMAGE_SCHEMA = (
|
ONLINE_IMAGE_SCHEMA = (
|
||||||
runtime_image.runtime_image_schema(OnlineImage)
|
IMAGE_SCHEMA.extend(remove_options(CONF_FILE, CONF_INVERT_ALPHA, CONF_DITHER))
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
# Online Image specific options
|
cv.Required(CONF_ID): cv.declare_id(OnlineImage),
|
||||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||||
|
# Online Image specific options
|
||||||
cv.Required(CONF_URL): cv.url,
|
cv.Required(CONF_URL): cv.url,
|
||||||
cv.Optional(CONF_BUFFER_SIZE, default=65536): cv.int_range(256, 65536),
|
|
||||||
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
|
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
|
||||||
cv.Schema({cv.string: cv.templatable(cv.string)})
|
cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||||
),
|
),
|
||||||
|
cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, upper=True),
|
||||||
|
cv.Optional(CONF_PLACEHOLDER): cv.use_id(Image_),
|
||||||
|
cv.Optional(CONF_BUFFER_SIZE, default=65536): cv.int_range(256, 65536),
|
||||||
cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation(
|
cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
@@ -87,7 +162,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
rp2040_arduino=cv.Version(0, 0, 0),
|
rp2040_arduino=cv.Version(0, 0, 0),
|
||||||
host=cv.Version(0, 0, 0),
|
host=cv.Version(0, 0, 0),
|
||||||
),
|
),
|
||||||
runtime_image.validate_runtime_image_settings,
|
validate_settings,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,21 +199,23 @@ async def online_image_action_to_code(config, action_id, template_arg, args):
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
# Use the enhanced helper function to get all runtime image parameters
|
image_format = IMAGE_FORMATS[config[CONF_FORMAT]]
|
||||||
settings = await runtime_image.process_runtime_image_config(config)
|
image_format.actions()
|
||||||
|
|
||||||
url = config[CONF_URL]
|
url = config[CONF_URL]
|
||||||
|
width, height = config.get(CONF_RESIZE, (0, 0))
|
||||||
|
transparent = get_transparency_enum(config[CONF_TRANSPARENCY])
|
||||||
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
url,
|
url,
|
||||||
settings.width,
|
width,
|
||||||
settings.height,
|
height,
|
||||||
settings.format_enum,
|
image_format.enum,
|
||||||
settings.image_type_enum,
|
get_image_type_enum(config[CONF_TYPE]),
|
||||||
settings.transparent,
|
transparent,
|
||||||
settings.placeholder or cg.nullptr,
|
|
||||||
config[CONF_BUFFER_SIZE],
|
config[CONF_BUFFER_SIZE],
|
||||||
settings.byte_order_big_endian,
|
config.get(CONF_BYTE_ORDER) != "LITTLE_ENDIAN",
|
||||||
)
|
)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
||||||
@@ -150,6 +227,10 @@ async def to_code(config):
|
|||||||
else:
|
else:
|
||||||
cg.add(var.add_request_header(key, value))
|
cg.add(var.add_request_header(key, value))
|
||||||
|
|
||||||
|
if placeholder_id := config.get(CONF_PLACEHOLDER):
|
||||||
|
placeholder = await cg.get_variable(placeholder_id)
|
||||||
|
cg.add(var.set_placeholder(placeholder))
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []):
|
for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [(bool, "cached")], conf)
|
await automation.build_automation(trigger, [(bool, "cached")], conf)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#include "bmp_decoder.h"
|
#include "bmp_image.h"
|
||||||
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
|
|
||||||
#include "esphome/components/display/display.h"
|
#include "esphome/components/display/display.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
static const char *const TAG = "image_decoder.bmp";
|
static const char *const TAG = "online_image.bmp";
|
||||||
|
|
||||||
int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
@@ -29,11 +30,7 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
return DECODE_ERROR_INVALID_TYPE;
|
return DECODE_ERROR_INVALID_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BMP file contains its own size in the header
|
this->download_size_ = encode_uint32(buffer[5], buffer[4], buffer[3], buffer[2]);
|
||||||
size_t file_size = encode_uint32(buffer[5], buffer[4], buffer[3], buffer[2]);
|
|
||||||
if (this->expected_size_ == 0) {
|
|
||||||
this->expected_size_ = file_size; // Use file header size if not provided
|
|
||||||
}
|
|
||||||
this->data_offset_ = encode_uint32(buffer[13], buffer[12], buffer[11], buffer[10]);
|
this->data_offset_ = encode_uint32(buffer[13], buffer[12], buffer[11], buffer[10]);
|
||||||
|
|
||||||
this->current_index_ = 14;
|
this->current_index_ = 14;
|
||||||
@@ -93,8 +90,8 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
while (index < size) {
|
while (index < size) {
|
||||||
uint8_t current_byte = buffer[index];
|
uint8_t current_byte = buffer[index];
|
||||||
for (uint8_t i = 0; i < 8; i++) {
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
size_t x = (this->paint_index_ % static_cast<size_t>(this->width_)) + i;
|
size_t x = (this->paint_index_ % this->width_) + i;
|
||||||
size_t y = static_cast<size_t>(this->height_ - 1) - (this->paint_index_ / static_cast<size_t>(this->width_));
|
size_t y = (this->height_ - 1) - (this->paint_index_ / this->width_);
|
||||||
Color c = (current_byte & (1 << (7 - i))) ? display::COLOR_ON : display::COLOR_OFF;
|
Color c = (current_byte & (1 << (7 - i))) ? display::COLOR_ON : display::COLOR_OFF;
|
||||||
this->draw(x, y, 1, 1, c);
|
this->draw(x, y, 1, 1, c);
|
||||||
}
|
}
|
||||||
@@ -113,8 +110,8 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
uint8_t b = buffer[index];
|
uint8_t b = buffer[index];
|
||||||
uint8_t g = buffer[index + 1];
|
uint8_t g = buffer[index + 1];
|
||||||
uint8_t r = buffer[index + 2];
|
uint8_t r = buffer[index + 2];
|
||||||
size_t x = this->paint_index_ % static_cast<size_t>(this->width_);
|
size_t x = this->paint_index_ % this->width_;
|
||||||
size_t y = static_cast<size_t>(this->height_ - 1) - (this->paint_index_ / static_cast<size_t>(this->width_));
|
size_t y = (this->height_ - 1) - (this->paint_index_ / this->width_);
|
||||||
Color c = Color(r, g, b);
|
Color c = Color(r, g, b);
|
||||||
this->draw(x, y, 1, 1, c);
|
this->draw(x, y, 1, 1, c);
|
||||||
this->paint_index_++;
|
this->paint_index_++;
|
||||||
@@ -136,6 +133,7 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
return size;
|
return size;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_BMP
|
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
@@ -1,32 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
|
|
||||||
#include "image_decoder.h"
|
#include "image_decoder.h"
|
||||||
#include "runtime_image.h"
|
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Image decoder specialization for BMP images.
|
* @brief Image decoder specialization for PNG images.
|
||||||
*/
|
*/
|
||||||
class BmpDecoder : public ImageDecoder {
|
class BmpDecoder : public ImageDecoder {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new BMP Decoder object.
|
* @brief Construct a new BMP Decoder object.
|
||||||
*
|
*
|
||||||
* @param image The RuntimeImage to decode the stream into.
|
* @param display The image to decode the stream into.
|
||||||
*/
|
*/
|
||||||
BmpDecoder(RuntimeImage *image) : ImageDecoder(image) {}
|
BmpDecoder(OnlineImage *image) : ImageDecoder(image) {}
|
||||||
|
|
||||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||||
|
|
||||||
bool is_finished() const override {
|
|
||||||
// BMP is finished when we've decoded all pixel data
|
|
||||||
return this->paint_index_ >= static_cast<size_t>(this->width_ * this->height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
size_t current_index_{0};
|
size_t current_index_{0};
|
||||||
size_t paint_index_{0};
|
size_t paint_index_{0};
|
||||||
@@ -41,6 +36,7 @@ class BmpDecoder : public ImageDecoder {
|
|||||||
uint8_t padding_bytes_{0};
|
uint8_t padding_bytes_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_BMP
|
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace esphome::online_image {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Buffer for managing downloaded data.
|
|
||||||
*
|
|
||||||
* This class provides a buffer for downloading data with tracking of
|
|
||||||
* unread bytes and dynamic resizing capabilities.
|
|
||||||
*/
|
|
||||||
class DownloadBuffer {
|
|
||||||
public:
|
|
||||||
DownloadBuffer(size_t size);
|
|
||||||
~DownloadBuffer() { this->allocator_.deallocate(this->buffer_, this->size_); }
|
|
||||||
|
|
||||||
uint8_t *data(size_t offset = 0);
|
|
||||||
uint8_t *append() { return this->data(this->unread_); }
|
|
||||||
|
|
||||||
size_t unread() const { return this->unread_; }
|
|
||||||
size_t size() const { return this->size_; }
|
|
||||||
size_t free_capacity() const { return this->size_ - this->unread_; }
|
|
||||||
|
|
||||||
size_t read(size_t len);
|
|
||||||
size_t write(size_t len) {
|
|
||||||
this->unread_ += len;
|
|
||||||
return this->unread_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() { this->unread_ = 0; }
|
|
||||||
size_t resize(size_t size);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
RAMAllocator<uint8_t> allocator_{};
|
|
||||||
uint8_t *buffer_;
|
|
||||||
size_t size_;
|
|
||||||
/** Total number of downloaded bytes not yet read. */
|
|
||||||
size_t unread_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::online_image
|
|
||||||
@@ -1,10 +1,29 @@
|
|||||||
#include "download_buffer.h"
|
#include "image_decoder.h"
|
||||||
|
#include "online_image.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace esphome::online_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
static const char *const TAG = "online_image.download_buffer";
|
static const char *const TAG = "online_image.decoder";
|
||||||
|
|
||||||
|
bool ImageDecoder::set_size(int width, int height) {
|
||||||
|
bool success = this->image_->resize_(width, height) > 0;
|
||||||
|
this->x_scale_ = static_cast<double>(this->image_->buffer_width_) / width;
|
||||||
|
this->y_scale_ = static_cast<double>(this->image_->buffer_height_) / height;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageDecoder::draw(int x, int y, int w, int h, const Color &color) {
|
||||||
|
auto width = std::min(this->image_->buffer_width_, static_cast<int>(std::ceil((x + w) * this->x_scale_)));
|
||||||
|
auto height = std::min(this->image_->buffer_height_, static_cast<int>(std::ceil((y + h) * this->y_scale_)));
|
||||||
|
for (int i = x * this->x_scale_; i < width; i++) {
|
||||||
|
for (int j = y * this->y_scale_; j < height; j++) {
|
||||||
|
this->image_->draw_pixel_(i, j, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DownloadBuffer::DownloadBuffer(size_t size) : size_(size) {
|
DownloadBuffer::DownloadBuffer(size_t size) : size_(size) {
|
||||||
this->buffer_ = this->allocator_.allocate(size);
|
this->buffer_ = this->allocator_.allocate(size);
|
||||||
@@ -24,12 +43,10 @@ uint8_t *DownloadBuffer::data(size_t offset) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t DownloadBuffer::read(size_t len) {
|
size_t DownloadBuffer::read(size_t len) {
|
||||||
if (len >= this->unread_) {
|
|
||||||
this->unread_ = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
this->unread_ -= len;
|
this->unread_ -= len;
|
||||||
memmove(this->data(), this->data(len), this->unread_);
|
if (this->unread_ > 0) {
|
||||||
|
memmove(this->data(), this->data(len), this->unread_);
|
||||||
|
}
|
||||||
return this->unread_;
|
return this->unread_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,4 +69,5 @@ size_t DownloadBuffer::resize(size_t size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::online_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "esphome/core/color.h"
|
#include "esphome/core/color.h"
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
enum DecodeError : int {
|
enum DecodeError : int {
|
||||||
DECODE_ERROR_INVALID_TYPE = -1,
|
DECODE_ERROR_INVALID_TYPE = -1,
|
||||||
@@ -9,7 +10,7 @@ enum DecodeError : int {
|
|||||||
DECODE_ERROR_OUT_OF_MEMORY = -3,
|
DECODE_ERROR_OUT_OF_MEMORY = -3,
|
||||||
};
|
};
|
||||||
|
|
||||||
class RuntimeImage;
|
class OnlineImage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Class to abstract decoding different image formats.
|
* @brief Class to abstract decoding different image formats.
|
||||||
@@ -19,19 +20,19 @@ class ImageDecoder {
|
|||||||
/**
|
/**
|
||||||
* @brief Construct a new Image Decoder object
|
* @brief Construct a new Image Decoder object
|
||||||
*
|
*
|
||||||
* @param image The RuntimeImage to decode the stream into.
|
* @param image The image to decode the stream into.
|
||||||
*/
|
*/
|
||||||
ImageDecoder(RuntimeImage *image) : image_(image) {}
|
ImageDecoder(OnlineImage *image) : image_(image) {}
|
||||||
virtual ~ImageDecoder() = default;
|
virtual ~ImageDecoder() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize the decoder.
|
* @brief Initialize the decoder.
|
||||||
*
|
*
|
||||||
* @param expected_size Hint about the expected data size (0 if unknown).
|
* @param download_size The total number of bytes that need to be downloaded for the image.
|
||||||
* @return int Returns 0 on success, a {@see DecodeError} value in case of an error.
|
* @return int Returns 0 on success, a {@see DecodeError} value in case of an error.
|
||||||
*/
|
*/
|
||||||
virtual int prepare(size_t expected_size) {
|
virtual int prepare(size_t download_size) {
|
||||||
this->expected_size_ = expected_size;
|
this->download_size_ = download_size;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,26 +73,49 @@ class ImageDecoder {
|
|||||||
*/
|
*/
|
||||||
void draw(int x, int y, int w, int h, const Color &color);
|
void draw(int x, int y, int w, int h, const Color &color);
|
||||||
|
|
||||||
/**
|
bool is_finished() const { return this->decoded_bytes_ == this->download_size_; }
|
||||||
* @brief Check if the decoder has finished processing.
|
|
||||||
*
|
|
||||||
* This should be overridden by decoders that can detect completion
|
|
||||||
* based on format-specific markers rather than byte counts.
|
|
||||||
*/
|
|
||||||
virtual bool is_finished() const {
|
|
||||||
if (this->expected_size_ > 0) {
|
|
||||||
return this->decoded_bytes_ >= this->expected_size_;
|
|
||||||
}
|
|
||||||
// If size is unknown, derived classes should override this
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RuntimeImage *image_;
|
OnlineImage *image_;
|
||||||
size_t expected_size_ = 0; // Expected data size (0 if unknown)
|
// Initializing to 1, to ensure it is distinguishable from initial "decoded_bytes_".
|
||||||
size_t decoded_bytes_ = 0; // Bytes processed so far
|
// Will be overwritten anyway once the download size is known.
|
||||||
|
size_t download_size_ = 1;
|
||||||
|
size_t decoded_bytes_ = 0;
|
||||||
double x_scale_ = 1.0;
|
double x_scale_ = 1.0;
|
||||||
double y_scale_ = 1.0;
|
double y_scale_ = 1.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
class DownloadBuffer {
|
||||||
|
public:
|
||||||
|
DownloadBuffer(size_t size);
|
||||||
|
|
||||||
|
virtual ~DownloadBuffer() { this->allocator_.deallocate(this->buffer_, this->size_); }
|
||||||
|
|
||||||
|
uint8_t *data(size_t offset = 0);
|
||||||
|
|
||||||
|
uint8_t *append() { return this->data(this->unread_); }
|
||||||
|
|
||||||
|
size_t unread() const { return this->unread_; }
|
||||||
|
size_t size() const { return this->size_; }
|
||||||
|
size_t free_capacity() const { return this->size_ - this->unread_; }
|
||||||
|
|
||||||
|
size_t read(size_t len);
|
||||||
|
size_t write(size_t len) {
|
||||||
|
this->unread_ += len;
|
||||||
|
return this->unread_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() { this->unread_ = 0; }
|
||||||
|
|
||||||
|
size_t resize(size_t size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RAMAllocator<uint8_t> allocator_{};
|
||||||
|
uint8_t *buffer_;
|
||||||
|
size_t size_;
|
||||||
|
/** Total number of downloaded bytes not yet read. */
|
||||||
|
size_t unread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
#include "jpeg_decoder.h"
|
#include "jpeg_image.h"
|
||||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
|
|
||||||
#include "esphome/components/display/display_buffer.h"
|
#include "esphome/components/display/display_buffer.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ESP_IDF
|
#include "online_image.h"
|
||||||
#include "esp_task_wdt.h"
|
static const char *const TAG = "online_image.jpeg";
|
||||||
#endif
|
|
||||||
|
|
||||||
static const char *const TAG = "image_decoder.jpeg";
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
namespace esphome::runtime_image {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback method that will be called by the JPEGDEC engine when a chunk
|
* @brief Callback method that will be called by the JPEGDEC engine when a chunk
|
||||||
@@ -25,14 +22,8 @@ static int draw_callback(JPEGDRAW *jpeg) {
|
|||||||
ImageDecoder *decoder = (ImageDecoder *) jpeg->pUser;
|
ImageDecoder *decoder = (ImageDecoder *) jpeg->pUser;
|
||||||
|
|
||||||
// Some very big images take too long to decode, so feed the watchdog on each callback
|
// Some very big images take too long to decode, so feed the watchdog on each callback
|
||||||
// to avoid crashing if the executing task has a watchdog enabled.
|
// to avoid crashing.
|
||||||
#ifdef USE_ESP_IDF
|
App.feed_wdt();
|
||||||
if (esp_task_wdt_status(nullptr) == ESP_OK) {
|
|
||||||
#endif
|
|
||||||
App.feed_wdt();
|
|
||||||
#ifdef USE_ESP_IDF
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
size_t position = 0;
|
size_t position = 0;
|
||||||
size_t height = static_cast<size_t>(jpeg->iHeight);
|
size_t height = static_cast<size_t>(jpeg->iHeight);
|
||||||
size_t width = static_cast<size_t>(jpeg->iWidth);
|
size_t width = static_cast<size_t>(jpeg->iWidth);
|
||||||
@@ -52,23 +43,22 @@ static int draw_callback(JPEGDRAW *jpeg) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int JpegDecoder::prepare(size_t expected_size) {
|
int JpegDecoder::prepare(size_t download_size) {
|
||||||
ImageDecoder::prepare(expected_size);
|
ImageDecoder::prepare(download_size);
|
||||||
// JPEG decoder needs complete data before decoding
|
auto size = this->image_->resize_download_buffer(download_size);
|
||||||
|
if (size < download_size) {
|
||||||
|
ESP_LOGE(TAG, "Download buffer resize failed!");
|
||||||
|
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
|
int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
|
||||||
// JPEG decoder requires complete data
|
if (size < this->download_size_) {
|
||||||
// If we know the expected size, wait for it
|
ESP_LOGV(TAG, "Download not complete. Size: %d/%d", size, this->download_size_);
|
||||||
if (this->expected_size_ > 0 && size < this->expected_size_) {
|
|
||||||
ESP_LOGV(TAG, "Download not complete. Size: %zu/%zu", size, this->expected_size_);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If size unknown, try to decode and see if it's valid
|
|
||||||
// The JPEGDEC library will fail gracefully if data is incomplete
|
|
||||||
|
|
||||||
if (!this->jpeg_.openRAM(buffer, size, draw_callback)) {
|
if (!this->jpeg_.openRAM(buffer, size, draw_callback)) {
|
||||||
ESP_LOGE(TAG, "Could not open image for decoding: %d", this->jpeg_.getLastError());
|
ESP_LOGE(TAG, "Could not open image for decoding: %d", this->jpeg_.getLastError());
|
||||||
return DECODE_ERROR_INVALID_TYPE;
|
return DECODE_ERROR_INVALID_TYPE;
|
||||||
@@ -98,6 +88,7 @@ int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "image_decoder.h"
|
#include "image_decoder.h"
|
||||||
#include "runtime_image.h"
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
#include <JPEGDEC.h>
|
#include <JPEGDEC.h>
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Image decoder specialization for JPEG images.
|
* @brief Image decoder specialization for JPEG images.
|
||||||
@@ -16,18 +16,19 @@ class JpegDecoder : public ImageDecoder {
|
|||||||
/**
|
/**
|
||||||
* @brief Construct a new JPEG Decoder object.
|
* @brief Construct a new JPEG Decoder object.
|
||||||
*
|
*
|
||||||
* @param image The RuntimeImage to decode the stream into.
|
* @param display The image to decode the stream into.
|
||||||
*/
|
*/
|
||||||
JpegDecoder(RuntimeImage *image) : ImageDecoder(image) {}
|
JpegDecoder(OnlineImage *image) : ImageDecoder(image) {}
|
||||||
~JpegDecoder() override {}
|
~JpegDecoder() override {}
|
||||||
|
|
||||||
int prepare(size_t expected_size) override;
|
int prepare(size_t download_size) override;
|
||||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
JPEGDEC jpeg_{};
|
JPEGDEC jpeg_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "online_image.h"
|
#include "online_image.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
static const char *const TAG = "online_image";
|
static const char *const TAG = "online_image";
|
||||||
static const char *const ETAG_HEADER_NAME = "etag";
|
static const char *const ETAG_HEADER_NAME = "etag";
|
||||||
@@ -8,82 +8,142 @@ static const char *const IF_NONE_MATCH_HEADER_NAME = "if-none-match";
|
|||||||
static const char *const LAST_MODIFIED_HEADER_NAME = "last-modified";
|
static const char *const LAST_MODIFIED_HEADER_NAME = "last-modified";
|
||||||
static const char *const IF_MODIFIED_SINCE_HEADER_NAME = "if-modified-since";
|
static const char *const IF_MODIFIED_SINCE_HEADER_NAME = "if-modified-since";
|
||||||
|
|
||||||
namespace esphome::online_image {
|
#include "image_decoder.h"
|
||||||
|
|
||||||
OnlineImage::OnlineImage(const std::string &url, int width, int height, runtime_image::ImageFormat format,
|
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
image::ImageType type, image::Transparency transparency, image::Image *placeholder,
|
#include "bmp_image.h"
|
||||||
uint32_t buffer_size, bool is_big_endian)
|
#endif
|
||||||
: RuntimeImage(format, type, transparency, placeholder, is_big_endian, width, height),
|
#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
download_buffer_(buffer_size),
|
#include "jpeg_image.h"
|
||||||
download_buffer_initial_size_(buffer_size) {
|
#endif
|
||||||
|
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
|
#include "png_image.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
|
using image::ImageType;
|
||||||
|
|
||||||
|
inline bool is_color_on(const Color &color) {
|
||||||
|
// This produces the most accurate monochrome conversion, but is slightly slower.
|
||||||
|
// return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) > 127;
|
||||||
|
|
||||||
|
// Approximation using fast integer computations; produces acceptable results
|
||||||
|
// Equivalent to 0.25 * R + 0.5 * G + 0.25 * B
|
||||||
|
return ((color.r >> 2) + (color.g >> 1) + (color.b >> 2)) & 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFormat format, ImageType type,
|
||||||
|
image::Transparency transparency, uint32_t download_buffer_size, bool is_big_endian)
|
||||||
|
: Image(nullptr, 0, 0, type, transparency),
|
||||||
|
buffer_(nullptr),
|
||||||
|
download_buffer_(download_buffer_size),
|
||||||
|
download_buffer_initial_size_(download_buffer_size),
|
||||||
|
format_(format),
|
||||||
|
fixed_width_(width),
|
||||||
|
fixed_height_(height),
|
||||||
|
is_big_endian_(is_big_endian) {
|
||||||
this->set_url(url);
|
this->set_url(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OnlineImage::validate_url_(const std::string &url) {
|
void OnlineImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
|
||||||
if (url.empty()) {
|
if (this->data_start_) {
|
||||||
ESP_LOGE(TAG, "URL is empty");
|
Image::draw(x, y, display, color_on, color_off);
|
||||||
return false;
|
} else if (this->placeholder_) {
|
||||||
|
this->placeholder_->draw(x, y, display, color_on, color_off);
|
||||||
}
|
}
|
||||||
if (url.length() > 2048) {
|
}
|
||||||
ESP_LOGE(TAG, "URL is too long");
|
|
||||||
return false;
|
void OnlineImage::release() {
|
||||||
|
if (this->buffer_) {
|
||||||
|
ESP_LOGV(TAG, "Deallocating old buffer");
|
||||||
|
this->allocator_.deallocate(this->buffer_, this->get_buffer_size_());
|
||||||
|
this->data_start_ = nullptr;
|
||||||
|
this->buffer_ = nullptr;
|
||||||
|
this->width_ = 0;
|
||||||
|
this->height_ = 0;
|
||||||
|
this->buffer_width_ = 0;
|
||||||
|
this->buffer_height_ = 0;
|
||||||
|
this->last_modified_ = "";
|
||||||
|
this->etag_ = "";
|
||||||
|
this->end_connection_();
|
||||||
}
|
}
|
||||||
if (url.compare(0, 7, "http://") != 0 && url.compare(0, 8, "https://") != 0) {
|
}
|
||||||
ESP_LOGE(TAG, "URL must start with http:// or https://");
|
|
||||||
return false;
|
size_t OnlineImage::resize_(int width_in, int height_in) {
|
||||||
|
int width = this->fixed_width_;
|
||||||
|
int height = this->fixed_height_;
|
||||||
|
if (this->is_auto_resize_()) {
|
||||||
|
width = width_in;
|
||||||
|
height = height_in;
|
||||||
|
if (this->width_ != width && this->height_ != height) {
|
||||||
|
this->release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
size_t new_size = this->get_buffer_size_(width, height);
|
||||||
|
if (this->buffer_) {
|
||||||
|
// Buffer already allocated => no need to resize
|
||||||
|
return new_size;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, "Allocating new buffer of %zu bytes", new_size);
|
||||||
|
this->buffer_ = this->allocator_.allocate(new_size);
|
||||||
|
if (this->buffer_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "allocation of %zu bytes failed. Biggest block in heap: %zu Bytes", new_size,
|
||||||
|
this->allocator_.get_max_free_block_size());
|
||||||
|
this->end_connection_();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
this->buffer_width_ = width;
|
||||||
|
this->buffer_height_ = height;
|
||||||
|
this->width_ = width;
|
||||||
|
ESP_LOGV(TAG, "New size: (%d, %d)", width, height);
|
||||||
|
return new_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnlineImage::update() {
|
void OnlineImage::update() {
|
||||||
if (this->is_decoding()) {
|
if (this->decoder_) {
|
||||||
ESP_LOGW(TAG, "Image already being updated.");
|
ESP_LOGW(TAG, "Image already being updated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ESP_LOGI(TAG, "Updating image %s", this->url_.c_str());
|
||||||
|
|
||||||
if (!this->validate_url_(this->url_)) {
|
std::list<http_request::Header> headers = {};
|
||||||
ESP_LOGE(TAG, "Invalid URL: %s", this->url_.c_str());
|
|
||||||
this->download_error_callback_.call();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Updating image from %s", this->url_.c_str());
|
http_request::Header accept_header;
|
||||||
|
accept_header.name = "Accept";
|
||||||
std::list<http_request::Header> headers;
|
std::string accept_mime_type;
|
||||||
|
switch (this->format_) {
|
||||||
// Add caching headers if we have them
|
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
if (!this->etag_.empty()) {
|
case ImageFormat::BMP:
|
||||||
headers.push_back({IF_NONE_MATCH_HEADER_NAME, this->etag_});
|
accept_mime_type = "image/bmp";
|
||||||
}
|
|
||||||
if (!this->last_modified_.empty()) {
|
|
||||||
headers.push_back({IF_MODIFIED_SINCE_HEADER_NAME, this->last_modified_});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Accept header based on image format
|
|
||||||
const char *accept_mime_type;
|
|
||||||
switch (this->get_format()) {
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
|
||||||
case runtime_image::BMP:
|
|
||||||
accept_mime_type = "image/bmp,*/*;q=0.8";
|
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
case runtime_image::JPEG:
|
case ImageFormat::JPEG:
|
||||||
accept_mime_type = "image/jpeg,*/*;q=0.8";
|
accept_mime_type = "image/jpeg";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
case runtime_image::PNG:
|
case ImageFormat::PNG:
|
||||||
accept_mime_type = "image/png,*/*;q=0.8";
|
accept_mime_type = "image/png";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
default:
|
default:
|
||||||
accept_mime_type = "image/*,*/*;q=0.8";
|
accept_mime_type = "image/*";
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
headers.push_back({"Accept", accept_mime_type});
|
accept_header.value = accept_mime_type + ",*/*;q=0.8";
|
||||||
|
|
||||||
|
if (!this->etag_.empty()) {
|
||||||
|
headers.push_back(http_request::Header{IF_NONE_MATCH_HEADER_NAME, this->etag_});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->last_modified_.empty()) {
|
||||||
|
headers.push_back(http_request::Header{IF_MODIFIED_SINCE_HEADER_NAME, this->last_modified_});
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.push_back(accept_header);
|
||||||
|
|
||||||
// User headers last so they can override any of the above
|
|
||||||
for (auto &header : this->request_headers_) {
|
for (auto &header : this->request_headers_) {
|
||||||
headers.push_back(http_request::Header{header.first, header.second.value()});
|
headers.push_back(http_request::Header{header.first, header.second.value()});
|
||||||
}
|
}
|
||||||
@@ -115,117 +175,186 @@ void OnlineImage::update() {
|
|||||||
ESP_LOGD(TAG, "Starting download");
|
ESP_LOGD(TAG, "Starting download");
|
||||||
size_t total_size = this->downloader_->content_length;
|
size_t total_size = this->downloader_->content_length;
|
||||||
|
|
||||||
// Initialize decoder with the known format
|
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
if (!this->begin_decode(total_size)) {
|
if (this->format_ == ImageFormat::BMP) {
|
||||||
ESP_LOGE(TAG, "Failed to initialize decoder for format %d", this->get_format());
|
ESP_LOGD(TAG, "Allocating BMP decoder");
|
||||||
|
this->decoder_ = make_unique<BmpDecoder>(this);
|
||||||
|
this->enable_loop();
|
||||||
|
}
|
||||||
|
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
|
#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
|
if (this->format_ == ImageFormat::JPEG) {
|
||||||
|
ESP_LOGD(TAG, "Allocating JPEG decoder");
|
||||||
|
this->decoder_ = esphome::make_unique<JpegDecoder>(this);
|
||||||
|
this->enable_loop();
|
||||||
|
}
|
||||||
|
#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
|
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
|
if (this->format_ == ImageFormat::PNG) {
|
||||||
|
ESP_LOGD(TAG, "Allocating PNG decoder");
|
||||||
|
this->decoder_ = make_unique<PngDecoder>(this);
|
||||||
|
this->enable_loop();
|
||||||
|
}
|
||||||
|
#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
|
|
||||||
|
if (!this->decoder_) {
|
||||||
|
ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported: %d", this->format_);
|
||||||
this->end_connection_();
|
this->end_connection_();
|
||||||
this->download_error_callback_.call();
|
this->download_error_callback_.call();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto prepare_result = this->decoder_->prepare(total_size);
|
||||||
// JPEG requires the complete image in the download buffer before decoding
|
if (prepare_result < 0) {
|
||||||
if (this->get_format() == runtime_image::JPEG && total_size > this->download_buffer_.size()) {
|
this->end_connection_();
|
||||||
this->download_buffer_.resize(total_size);
|
this->download_error_callback_.call();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Downloading image (Size: %zu)", total_size);
|
ESP_LOGI(TAG, "Downloading image (Size: %zu)", total_size);
|
||||||
this->start_time_ = ::time(nullptr);
|
this->start_time_ = ::time(nullptr);
|
||||||
this->enable_loop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnlineImage::loop() {
|
void OnlineImage::loop() {
|
||||||
if (!this->is_decoding()) {
|
if (!this->decoder_) {
|
||||||
// Not decoding at the moment => nothing to do.
|
// Not decoding at the moment => nothing to do.
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this->downloader_ || this->decoder_->is_finished()) {
|
||||||
if (!this->downloader_) {
|
this->data_start_ = buffer_;
|
||||||
ESP_LOGE(TAG, "Downloader not instantiated; cannot download");
|
this->width_ = buffer_width_;
|
||||||
this->end_connection_();
|
this->height_ = buffer_height_;
|
||||||
this->download_error_callback_.call();
|
ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(),
|
||||||
return;
|
this->width_, this->height_);
|
||||||
}
|
ESP_LOGD(TAG, "Total time: %" PRIu32 "s", (uint32_t) (::time(nullptr) - this->start_time_));
|
||||||
|
|
||||||
// Check if download is complete — use decoder's format-specific completion check
|
|
||||||
// to handle both known content-length and chunked transfer encoding
|
|
||||||
if (this->is_decode_finished() || (this->downloader_->content_length > 0 &&
|
|
||||||
this->downloader_->get_bytes_read() >= this->downloader_->content_length &&
|
|
||||||
this->download_buffer_.unread() == 0)) {
|
|
||||||
// Finalize decoding
|
|
||||||
this->end_decode();
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Image fully downloaded, %zu bytes in %" PRIu32 "s", this->downloader_->get_bytes_read(),
|
|
||||||
(uint32_t) (::time(nullptr) - this->start_time_));
|
|
||||||
|
|
||||||
// Save caching headers
|
|
||||||
this->etag_ = this->downloader_->get_response_header(ETAG_HEADER_NAME);
|
this->etag_ = this->downloader_->get_response_header(ETAG_HEADER_NAME);
|
||||||
this->last_modified_ = this->downloader_->get_response_header(LAST_MODIFIED_HEADER_NAME);
|
this->last_modified_ = this->downloader_->get_response_header(LAST_MODIFIED_HEADER_NAME);
|
||||||
|
|
||||||
this->download_finished_callback_.call(false);
|
this->download_finished_callback_.call(false);
|
||||||
this->end_connection_();
|
this->end_connection_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this->downloader_ == nullptr) {
|
||||||
// Download and decode more data
|
ESP_LOGE(TAG, "Downloader not instantiated; cannot download");
|
||||||
|
return;
|
||||||
|
}
|
||||||
size_t available = this->download_buffer_.free_capacity();
|
size_t available = this->download_buffer_.free_capacity();
|
||||||
if (available > 0) {
|
if (available) {
|
||||||
// Download in chunks to avoid blocking
|
// Some decoders need to fully download the image before downloading.
|
||||||
|
// In case of huge images, don't wait blocking until the whole image has been downloaded,
|
||||||
|
// use smaller chunks
|
||||||
available = std::min(available, this->download_buffer_initial_size_);
|
available = std::min(available, this->download_buffer_initial_size_);
|
||||||
auto len = this->downloader_->read(this->download_buffer_.append(), available);
|
auto len = this->downloader_->read(this->download_buffer_.append(), available);
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
this->download_buffer_.write(len);
|
this->download_buffer_.write(len);
|
||||||
|
auto fed = this->decoder_->decode(this->download_buffer_.data(), this->download_buffer_.unread());
|
||||||
// Feed data to decoder
|
if (fed < 0) {
|
||||||
auto consumed = this->feed_data(this->download_buffer_.data(), this->download_buffer_.unread());
|
ESP_LOGE(TAG, "Error when decoding image.");
|
||||||
|
|
||||||
if (consumed < 0) {
|
|
||||||
ESP_LOGE(TAG, "Error decoding image: %d", consumed);
|
|
||||||
this->end_connection_();
|
this->end_connection_();
|
||||||
this->download_error_callback_.call();
|
this->download_error_callback_.call();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->download_buffer_.read(fed);
|
||||||
if (consumed > 0) {
|
|
||||||
this->download_buffer_.read(consumed);
|
|
||||||
}
|
|
||||||
} else if (len < 0) {
|
|
||||||
ESP_LOGE(TAG, "Error downloading image: %d", len);
|
|
||||||
this->end_connection_();
|
|
||||||
this->download_error_callback_.call();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Buffer is full, need to decode some data first
|
}
|
||||||
auto consumed = this->feed_data(this->download_buffer_.data(), this->download_buffer_.unread());
|
|
||||||
if (consumed > 0) {
|
void OnlineImage::map_chroma_key(Color &color) {
|
||||||
this->download_buffer_.read(consumed);
|
if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) {
|
||||||
} else if (consumed < 0) {
|
if (color.g == 1 && color.r == 0 && color.b == 0) {
|
||||||
ESP_LOGE(TAG, "Decode error with full buffer: %d", consumed);
|
color.g = 0;
|
||||||
this->end_connection_();
|
}
|
||||||
this->download_error_callback_.call();
|
if (color.w < 0x80) {
|
||||||
return;
|
color.r = 0;
|
||||||
} else {
|
color.g = this->type_ == ImageType::IMAGE_TYPE_RGB565 ? 4 : 1;
|
||||||
// Decoder can't process more data, might need complete image
|
color.b = 0;
|
||||||
// This is normal for JPEG which needs complete data
|
}
|
||||||
ESP_LOGV(TAG, "Decoder waiting for more data");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnlineImage::draw_pixel_(int x, int y, Color color) {
|
||||||
|
if (!this->buffer_) {
|
||||||
|
ESP_LOGE(TAG, "Buffer not allocated!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (x < 0 || y < 0 || x >= this->buffer_width_ || y >= this->buffer_height_) {
|
||||||
|
ESP_LOGE(TAG, "Tried to paint a pixel (%d,%d) outside the image!", x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t pos = this->get_position_(x, y);
|
||||||
|
switch (this->type_) {
|
||||||
|
case ImageType::IMAGE_TYPE_BINARY: {
|
||||||
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||||
|
pos = x + y * width_8;
|
||||||
|
auto bitno = 0x80 >> (pos % 8u);
|
||||||
|
pos /= 8u;
|
||||||
|
auto on = is_color_on(color);
|
||||||
|
if (this->has_transparency() && color.w < 0x80)
|
||||||
|
on = false;
|
||||||
|
if (on) {
|
||||||
|
this->buffer_[pos] |= bitno;
|
||||||
|
} else {
|
||||||
|
this->buffer_[pos] &= ~bitno;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImageType::IMAGE_TYPE_GRAYSCALE: {
|
||||||
|
auto gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
|
||||||
|
if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) {
|
||||||
|
if (gray == 1) {
|
||||||
|
gray = 0;
|
||||||
|
}
|
||||||
|
if (color.w < 0x80) {
|
||||||
|
gray = 1;
|
||||||
|
}
|
||||||
|
} else if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
||||||
|
if (color.w != 0xFF)
|
||||||
|
gray = color.w;
|
||||||
|
}
|
||||||
|
this->buffer_[pos] = gray;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImageType::IMAGE_TYPE_RGB565: {
|
||||||
|
this->map_chroma_key(color);
|
||||||
|
uint16_t col565 = display::ColorUtil::color_to_565(color);
|
||||||
|
if (this->is_big_endian_) {
|
||||||
|
this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
|
||||||
|
this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF);
|
||||||
|
} else {
|
||||||
|
this->buffer_[pos + 0] = static_cast<uint8_t>(col565 & 0xFF);
|
||||||
|
this->buffer_[pos + 1] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
||||||
|
this->buffer_[pos + 2] = color.w;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImageType::IMAGE_TYPE_RGB: {
|
||||||
|
this->map_chroma_key(color);
|
||||||
|
this->buffer_[pos + 0] = color.r;
|
||||||
|
this->buffer_[pos + 1] = color.g;
|
||||||
|
this->buffer_[pos + 2] = color.b;
|
||||||
|
if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
||||||
|
this->buffer_[pos + 3] = color.w;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnlineImage::end_connection_() {
|
void OnlineImage::end_connection_() {
|
||||||
// Abort any in-progress decode to free decoder resources.
|
|
||||||
// Use RuntimeImage::release() directly to avoid recursion with OnlineImage::release().
|
|
||||||
if (this->is_decoding()) {
|
|
||||||
RuntimeImage::release();
|
|
||||||
}
|
|
||||||
if (this->downloader_) {
|
if (this->downloader_) {
|
||||||
this->downloader_->end();
|
this->downloader_->end();
|
||||||
this->downloader_ = nullptr;
|
this->downloader_ = nullptr;
|
||||||
}
|
}
|
||||||
|
this->decoder_.reset();
|
||||||
this->download_buffer_.reset();
|
this->download_buffer_.reset();
|
||||||
this->disable_loop();
|
}
|
||||||
|
|
||||||
|
bool OnlineImage::validate_url_(const std::string &url) {
|
||||||
|
if ((url.length() < 8) || !url.starts_with("http") || (url.find("://") == std::string::npos)) {
|
||||||
|
ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnlineImage::add_on_finished_callback(std::function<void(bool)> &&callback) {
|
void OnlineImage::add_on_finished_callback(std::function<void(bool)> &&callback) {
|
||||||
@@ -236,16 +365,5 @@ void OnlineImage::add_on_error_callback(std::function<void()> &&callback) {
|
|||||||
this->download_error_callback_.add(std::move(callback));
|
this->download_error_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnlineImage::release() {
|
} // namespace online_image
|
||||||
// Clear cache headers
|
} // namespace esphome
|
||||||
this->etag_ = "";
|
|
||||||
this->last_modified_ = "";
|
|
||||||
|
|
||||||
// End any active connection
|
|
||||||
this->end_connection_();
|
|
||||||
|
|
||||||
// Call parent's release to free the image buffer
|
|
||||||
RuntimeImage::release();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::online_image
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "download_buffer.h"
|
|
||||||
#include "esphome/components/http_request/http_request.h"
|
#include "esphome/components/http_request/http_request.h"
|
||||||
#include "esphome/components/runtime_image/runtime_image.h"
|
#include "esphome/components/image/image.h"
|
||||||
#include "esphome/core/automation.h"
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome::online_image {
|
#include "image_decoder.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
using t_http_codes = enum {
|
using t_http_codes = enum {
|
||||||
HTTP_CODE_OK = 200,
|
HTTP_CODE_OK = 200,
|
||||||
@@ -16,13 +17,27 @@ using t_http_codes = enum {
|
|||||||
HTTP_CODE_NOT_FOUND = 404,
|
HTTP_CODE_NOT_FOUND = 404,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Format that the image is encoded with.
|
||||||
|
*/
|
||||||
|
enum ImageFormat {
|
||||||
|
/** Automatically detect from MIME type. Not supported yet. */
|
||||||
|
AUTO,
|
||||||
|
/** JPEG format. */
|
||||||
|
JPEG,
|
||||||
|
/** PNG format. */
|
||||||
|
PNG,
|
||||||
|
/** BMP format. */
|
||||||
|
BMP,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Download an image from a given URL, and decode it using the specified decoder.
|
* @brief Download an image from a given URL, and decode it using the specified decoder.
|
||||||
* The image will then be stored in a buffer, so that it can be re-displayed without the
|
* The image will then be stored in a buffer, so that it can be re-displayed without the
|
||||||
* need to re-download or re-decode.
|
* need to re-download or re-decode.
|
||||||
*/
|
*/
|
||||||
class OnlineImage : public PollingComponent,
|
class OnlineImage : public PollingComponent,
|
||||||
public runtime_image::RuntimeImage,
|
public image::Image,
|
||||||
public Parented<esphome::http_request::HttpRequestComponent> {
|
public Parented<esphome::http_request::HttpRequestComponent> {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -31,19 +46,17 @@ class OnlineImage : public PollingComponent,
|
|||||||
* @param url URL to download the image from.
|
* @param url URL to download the image from.
|
||||||
* @param width Desired width of the target image area.
|
* @param width Desired width of the target image area.
|
||||||
* @param height Desired height of the target image area.
|
* @param height Desired height of the target image area.
|
||||||
* @param format Format that the image is encoded in (@see runtime_image::ImageFormat).
|
* @param format Format that the image is encoded in (@see ImageFormat).
|
||||||
* @param type The pixel format for the image.
|
|
||||||
* @param transparency The transparency type for the image.
|
|
||||||
* @param placeholder Optional placeholder image to show while loading.
|
|
||||||
* @param buffer_size Size of the buffer used to download the image.
|
* @param buffer_size Size of the buffer used to download the image.
|
||||||
* @param is_big_endian Whether the image is stored in big-endian format.
|
|
||||||
*/
|
*/
|
||||||
OnlineImage(const std::string &url, int width, int height, runtime_image::ImageFormat format, image::ImageType type,
|
OnlineImage(const std::string &url, int width, int height, ImageFormat format, image::ImageType type,
|
||||||
image::Transparency transparency, image::Image *placeholder, uint32_t buffer_size,
|
image::Transparency transparency, uint32_t buffer_size, bool is_big_endian);
|
||||||
bool is_big_endian = false);
|
|
||||||
|
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
void map_chroma_key(Color &color);
|
||||||
|
|
||||||
/** Set the URL to download the image from. */
|
/** Set the URL to download the image from. */
|
||||||
void set_url(const std::string &url) {
|
void set_url(const std::string &url) {
|
||||||
@@ -56,26 +69,82 @@ class OnlineImage : public PollingComponent,
|
|||||||
|
|
||||||
/** Add the request header */
|
/** Add the request header */
|
||||||
template<typename V> void add_request_header(const std::string &header, V value) {
|
template<typename V> void add_request_header(const std::string &header, V value) {
|
||||||
this->request_headers_.push_back(std::pair<std::string, TemplatableValue<std::string>>(header, value));
|
this->request_headers_.push_back(std::pair<std::string, TemplatableValue<std::string> >(header, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the image that needs to be shown as long as the downloaded image
|
||||||
|
* is not available.
|
||||||
|
*
|
||||||
|
* @param placeholder Pointer to the (@link Image) to show as placeholder.
|
||||||
|
*/
|
||||||
|
void set_placeholder(image::Image *placeholder) { this->placeholder_ = placeholder; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the buffer storing the image. The image will need to be downloaded again
|
* Release the buffer storing the image. The image will need to be downloaded again
|
||||||
* to be able to be displayed.
|
* to be able to be displayed.
|
||||||
*/
|
*/
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize the download buffer
|
||||||
|
*
|
||||||
|
* @param size The new size for the download buffer.
|
||||||
|
*/
|
||||||
|
size_t resize_download_buffer(size_t size) { return this->download_buffer_.resize(size); }
|
||||||
|
|
||||||
void add_on_finished_callback(std::function<void(bool)> &&callback);
|
void add_on_finished_callback(std::function<void(bool)> &&callback);
|
||||||
void add_on_error_callback(std::function<void()> &&callback);
|
void add_on_error_callback(std::function<void()> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool validate_url_(const std::string &url);
|
bool validate_url_(const std::string &url);
|
||||||
|
|
||||||
|
RAMAllocator<uint8_t> allocator_{};
|
||||||
|
|
||||||
|
uint32_t get_buffer_size_() const { return get_buffer_size_(this->buffer_width_, this->buffer_height_); }
|
||||||
|
int get_buffer_size_(int width, int height) const { return (this->get_bpp() * width + 7u) / 8u * height; }
|
||||||
|
|
||||||
|
int get_position_(int x, int y) const { return (x + y * this->buffer_width_) * this->get_bpp() / 8; }
|
||||||
|
|
||||||
|
ESPHOME_ALWAYS_INLINE bool is_auto_resize_() const { return this->fixed_width_ == 0 || this->fixed_height_ == 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resize the image buffer to the requested dimensions.
|
||||||
|
*
|
||||||
|
* The buffer will be allocated if not existing.
|
||||||
|
* If the dimensions have been fixed in the yaml config, the buffer will be created
|
||||||
|
* with those dimensions and not resized, even on request.
|
||||||
|
* Otherwise, the old buffer will be deallocated and a new buffer with the requested
|
||||||
|
* allocated
|
||||||
|
*
|
||||||
|
* @param width
|
||||||
|
* @param height
|
||||||
|
* @return 0 if no memory could be allocated, the size of the new buffer otherwise.
|
||||||
|
*/
|
||||||
|
size_t resize_(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draw a pixel into the buffer.
|
||||||
|
*
|
||||||
|
* This is used by the decoder to fill the buffer that will later be displayed
|
||||||
|
* by the `draw` method. This will internally convert the supplied 32 bit RGBA
|
||||||
|
* color into the requested image storage format.
|
||||||
|
*
|
||||||
|
* @param x Horizontal pixel position.
|
||||||
|
* @param y Vertical pixel position.
|
||||||
|
* @param color 32 bit color to put into the pixel.
|
||||||
|
*/
|
||||||
|
void draw_pixel_(int x, int y, Color color);
|
||||||
|
|
||||||
void end_connection_();
|
void end_connection_();
|
||||||
|
|
||||||
CallbackManager<void(bool)> download_finished_callback_{};
|
CallbackManager<void(bool)> download_finished_callback_{};
|
||||||
CallbackManager<void()> download_error_callback_{};
|
CallbackManager<void()> download_error_callback_{};
|
||||||
|
|
||||||
std::shared_ptr<http_request::HttpContainer> downloader_{nullptr};
|
std::shared_ptr<http_request::HttpContainer> downloader_{nullptr};
|
||||||
|
std::unique_ptr<ImageDecoder> decoder_{nullptr};
|
||||||
|
|
||||||
|
uint8_t *buffer_;
|
||||||
DownloadBuffer download_buffer_;
|
DownloadBuffer download_buffer_;
|
||||||
/**
|
/**
|
||||||
* This is the *initial* size of the download buffer, not the current size.
|
* This is the *initial* size of the download buffer, not the current size.
|
||||||
@@ -84,10 +153,40 @@ class OnlineImage : public PollingComponent,
|
|||||||
*/
|
*/
|
||||||
size_t download_buffer_initial_size_;
|
size_t download_buffer_initial_size_;
|
||||||
|
|
||||||
|
const ImageFormat format_;
|
||||||
|
image::Image *placeholder_{nullptr};
|
||||||
|
|
||||||
std::string url_{""};
|
std::string url_{""};
|
||||||
|
|
||||||
std::vector<std::pair<std::string, TemplatableValue<std::string>>> request_headers_;
|
std::vector<std::pair<std::string, TemplatableValue<std::string> > > request_headers_;
|
||||||
|
|
||||||
|
/** width requested on configuration, or 0 if non specified. */
|
||||||
|
const int fixed_width_;
|
||||||
|
/** height requested on configuration, or 0 if non specified. */
|
||||||
|
const int fixed_height_;
|
||||||
|
/**
|
||||||
|
* Whether the image is stored in big-endian format.
|
||||||
|
* This is used to determine how to store 16 bit colors in the buffer.
|
||||||
|
*/
|
||||||
|
bool is_big_endian_;
|
||||||
|
/**
|
||||||
|
* Actual width of the current image. If fixed_width_ is specified,
|
||||||
|
* this will be equal to it; otherwise it will be set once the decoding
|
||||||
|
* starts and the original size is known.
|
||||||
|
* This needs to be separate from "BaseImage::get_width()" because the latter
|
||||||
|
* must return 0 until the image has been decoded (to avoid showing partially
|
||||||
|
* decoded images).
|
||||||
|
*/
|
||||||
|
int buffer_width_;
|
||||||
|
/**
|
||||||
|
* Actual height of the current image. If fixed_height_ is specified,
|
||||||
|
* this will be equal to it; otherwise it will be set once the decoding
|
||||||
|
* starts and the original size is known.
|
||||||
|
* This needs to be separate from "BaseImage::get_height()" because the latter
|
||||||
|
* must return 0 until the image has been decoded (to avoid showing partially
|
||||||
|
* decoded images).
|
||||||
|
*/
|
||||||
|
int buffer_height_;
|
||||||
/**
|
/**
|
||||||
* The value of the ETag HTTP header provided in the last response.
|
* The value of the ETag HTTP header provided in the last response.
|
||||||
*/
|
*/
|
||||||
@@ -98,6 +197,9 @@ class OnlineImage : public PollingComponent,
|
|||||||
std::string last_modified_ = "";
|
std::string last_modified_ = "";
|
||||||
|
|
||||||
time_t start_time_;
|
time_t start_time_;
|
||||||
|
|
||||||
|
friend bool ImageDecoder::set_size(int width, int height);
|
||||||
|
friend void ImageDecoder::draw(int x, int y, int w, int h, const Color &color);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
|
||||||
@@ -139,4 +241,5 @@ class DownloadErrorTrigger : public Trigger<> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::online_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#include "png_decoder.h"
|
#include "png_image.h"
|
||||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
|
|
||||||
#include "esphome/components/display/display_buffer.h"
|
#include "esphome/components/display/display_buffer.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
static const char *const TAG = "image_decoder.png";
|
static const char *const TAG = "online_image.png";
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback method that will be called by the PNGLE engine when the basic
|
* @brief Callback method that will be called by the PNGLE engine when the basic
|
||||||
@@ -48,7 +49,7 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PngDecoder::PngDecoder(RuntimeImage *image) : ImageDecoder(image) {
|
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {
|
||||||
{
|
{
|
||||||
pngle_t *pngle = this->allocator_.allocate(1, PNGLE_T_SIZE);
|
pngle_t *pngle = this->allocator_.allocate(1, PNGLE_T_SIZE);
|
||||||
if (!pngle) {
|
if (!pngle) {
|
||||||
@@ -68,8 +69,8 @@ PngDecoder::~PngDecoder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PngDecoder::prepare(size_t expected_size) {
|
int PngDecoder::prepare(size_t download_size) {
|
||||||
ImageDecoder::prepare(expected_size);
|
ImageDecoder::prepare(download_size);
|
||||||
if (!this->pngle_) {
|
if (!this->pngle_) {
|
||||||
ESP_LOGE(TAG, "PNG decoder engine not initialized!");
|
ESP_LOGE(TAG, "PNG decoder engine not initialized!");
|
||||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||||
@@ -85,9 +86,8 @@ int HOT PngDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
ESP_LOGE(TAG, "PNG decoder engine not initialized!");
|
ESP_LOGE(TAG, "PNG decoder engine not initialized!");
|
||||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
// PNG can be decoded progressively, but wait for a reasonable chunk
|
if (size < 256 && size < this->download_size_ - this->decoded_bytes_) {
|
||||||
if (size < 256 && this->expected_size_ > 0 && size < this->expected_size_ - this->decoded_bytes_) {
|
ESP_LOGD(TAG, "Waiting for data");
|
||||||
ESP_LOGD(TAG, "Waiting for more data");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto fed = pngle_feed(this->pngle_, buffer, size);
|
auto fed = pngle_feed(this->pngle_, buffer, size);
|
||||||
@@ -99,6 +99,7 @@ int HOT PngDecoder::decode(uint8_t *buffer, size_t size) {
|
|||||||
return fed;
|
return fed;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_PNG
|
#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "image_decoder.h"
|
#include "image_decoder.h"
|
||||||
#include "runtime_image.h"
|
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
|
||||||
#include <pngle.h>
|
#include <pngle.h>
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
namespace esphome {
|
||||||
|
namespace online_image {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Image decoder specialization for PNG images.
|
* @brief Image decoder specialization for PNG images.
|
||||||
@@ -17,12 +17,12 @@ class PngDecoder : public ImageDecoder {
|
|||||||
/**
|
/**
|
||||||
* @brief Construct a new PNG Decoder object.
|
* @brief Construct a new PNG Decoder object.
|
||||||
*
|
*
|
||||||
* @param image The RuntimeImage to decode the stream into.
|
* @param display The image to decode the stream into.
|
||||||
*/
|
*/
|
||||||
PngDecoder(RuntimeImage *image);
|
PngDecoder(OnlineImage *image);
|
||||||
~PngDecoder() override;
|
~PngDecoder() override;
|
||||||
|
|
||||||
int prepare(size_t expected_size) override;
|
int prepare(size_t download_size) override;
|
||||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||||
|
|
||||||
void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; }
|
void increment_pixels_decoded(uint32_t count) { this->pixels_decoded_ += count; }
|
||||||
@@ -30,10 +30,11 @@ class PngDecoder : public ImageDecoder {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
RAMAllocator<pngle_t> allocator_;
|
RAMAllocator<pngle_t> allocator_;
|
||||||
pngle_t *pngle_{nullptr};
|
pngle_t *pngle_;
|
||||||
uint32_t pixels_decoded_{0};
|
uint32_t pixels_decoded_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace online_image
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
#endif // USE_RUNTIME_IMAGE_PNG
|
#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
@@ -76,7 +76,7 @@ class PN532 : public PollingComponent {
|
|||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(nfc::NfcTagUid &uid);
|
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(nfc::NfcTagUid &uid);
|
||||||
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
bool write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len);
|
bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||||
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid);
|
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid);
|
||||||
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid);
|
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid);
|
||||||
@@ -88,7 +88,7 @@ class PN532 : public PollingComponent {
|
|||||||
uint16_t read_mifare_ultralight_capacity_();
|
uint16_t read_mifare_ultralight_capacity_();
|
||||||
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
bool write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
|
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
|
||||||
bool clean_mifare_ultralight_();
|
bool clean_mifare_ultralight_();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "pn532.h"
|
#include "pn532.h"
|
||||||
@@ -107,10 +106,10 @@ bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
|
bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
|
std::vector<uint8_t> blank_buffer(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> TRAILER_BUFFER = {
|
std::vector<uint8_t> trailer_buffer(
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
bool error = false;
|
bool error = false;
|
||||||
|
|
||||||
@@ -119,20 +118,20 @@ bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (block != 0) {
|
if (block != 0) {
|
||||||
if (!this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
|
if (!this->write_mifare_classic_block_(block, blank_buffer)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block);
|
ESP_LOGE(TAG, "Unable to write block %d", block);
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
|
if (!this->write_mifare_classic_block_(block + 1, blank_buffer)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size())) {
|
if (!this->write_mifare_classic_block_(block + 2, blank_buffer)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size())) {
|
if (!this->write_mifare_classic_block_(block + 3, trailer_buffer)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block + 3);
|
ESP_LOGE(TAG, "Unable to write block %d", block + 3);
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
@@ -142,28 +141,28 @@ bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
|
bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
|
std::vector<uint8_t> empty_ndef_message(
|
||||||
0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
|
std::vector<uint8_t> blank_block(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
|
std::vector<uint8_t> block_1_data(
|
||||||
0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
|
std::vector<uint8_t> block_2_data(
|
||||||
0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
|
std::vector<uint8_t> block_3_trailer(
|
||||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
|
std::vector<uint8_t> ndef_trailer(
|
||||||
0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
|
if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) {
|
||||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!");
|
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()))
|
if (!this->write_mifare_classic_block_(1, block_1_data))
|
||||||
return false;
|
return false;
|
||||||
if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()))
|
if (!this->write_mifare_classic_block_(2, block_2_data))
|
||||||
return false;
|
return false;
|
||||||
if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()))
|
if (!this->write_mifare_classic_block_(3, block_3_trailer))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
|
ESP_LOGD(TAG, "Sector 0 formatted to NDEF");
|
||||||
@@ -173,36 +172,36 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (block == 4) {
|
if (block == 4) {
|
||||||
if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size())) {
|
if (!this->write_mifare_classic_block_(block, empty_ndef_message)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block);
|
ESP_LOGE(TAG, "Unable to write block %d", block);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
|
if (!this->write_mifare_classic_block_(block, blank_block)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block);
|
ESP_LOGE(TAG, "Unable to write block %d", block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
|
if (!this->write_mifare_classic_block_(block + 1, blank_block)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %d", block + 1);
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size())) {
|
if (!this->write_mifare_classic_block_(block + 2, blank_block)) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %d", block + 2);
|
||||||
}
|
}
|
||||||
if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size())) {
|
if (!this->write_mifare_classic_block_(block + 3, ndef_trailer)) {
|
||||||
ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
|
ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
|
bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||||
std::vector<uint8_t> cmd({
|
std::vector<uint8_t> data({
|
||||||
PN532_COMMAND_INDATAEXCHANGE,
|
PN532_COMMAND_INDATAEXCHANGE,
|
||||||
0x01, // One card
|
0x01, // One card
|
||||||
nfc::MIFARE_CMD_WRITE,
|
nfc::MIFARE_CMD_WRITE,
|
||||||
block_num,
|
block_num,
|
||||||
});
|
});
|
||||||
cmd.insert(cmd.end(), data, data + len);
|
data.insert(data.end(), write_data.begin(), write_data.end());
|
||||||
if (!this->write_command_(cmd)) {
|
if (!this->write_command_(data)) {
|
||||||
ESP_LOGE(TAG, "Error writing block %d", block_num);
|
ESP_LOGE(TAG, "Error writing block %d", block_num);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -244,7 +243,8 @@ bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *mes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE)) {
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||||
|
if (!this->write_mifare_classic_block_(current_block, data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "pn532.h"
|
#include "pn532.h"
|
||||||
@@ -144,7 +143,8 @@ bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *
|
|||||||
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
while (index < buffer_length) {
|
while (index < buffer_length) {
|
||||||
if (!this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE)) {
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||||
|
if (!this->write_mifare_ultralight_page_(current_page, data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||||
@@ -157,25 +157,25 @@ bool PN532::clean_mifare_ultralight_() {
|
|||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
|
std::vector<uint8_t> blank_data = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
||||||
if (!this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size())) {
|
if (!this->write_mifare_ultralight_page_(i, blank_data)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
|
bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||||
std::vector<uint8_t> cmd({
|
std::vector<uint8_t> data({
|
||||||
PN532_COMMAND_INDATAEXCHANGE,
|
PN532_COMMAND_INDATAEXCHANGE,
|
||||||
0x01, // One card
|
0x01, // One card
|
||||||
nfc::MIFARE_CMD_WRITE_ULTRALIGHT,
|
nfc::MIFARE_CMD_WRITE_ULTRALIGHT,
|
||||||
page_num,
|
page_num,
|
||||||
});
|
});
|
||||||
cmd.insert(cmd.end(), write_data, write_data + len);
|
data.insert(data.end(), write_data.begin(), write_data.end());
|
||||||
if (!this->write_command_(cmd)) {
|
if (!this->write_command_(data)) {
|
||||||
ESP_LOGE(TAG, "Error writing page %u", page_num);
|
ESP_LOGE(TAG, "Error writing page %u", page_num);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ class PN7150 : public nfc::Nfcc, public Component {
|
|||||||
|
|
||||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len);
|
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||||
uint8_t sect_to_auth_(uint8_t block_num);
|
uint8_t sect_to_auth_(uint8_t block_num);
|
||||||
uint8_t format_mifare_classic_mifare_();
|
uint8_t format_mifare_classic_mifare_();
|
||||||
@@ -250,7 +250,7 @@ class PN7150 : public nfc::Nfcc, public Component {
|
|||||||
uint16_t read_mifare_ultralight_capacity_();
|
uint16_t read_mifare_ultralight_capacity_();
|
||||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
uint8_t clean_mifare_ultralight_();
|
uint8_t clean_mifare_ultralight_();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "pn7150.h"
|
#include "pn7150.h"
|
||||||
@@ -140,10 +139,10 @@ uint8_t PN7150::sect_to_auth_(const uint8_t block_num) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::format_mifare_classic_mifare_() {
|
uint8_t PN7150::format_mifare_classic_mifare_() {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
|
std::vector<uint8_t> blank_buffer(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> TRAILER_BUFFER = {
|
std::vector<uint8_t> trailer_buffer(
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
auto status = nfc::STATUS_OK;
|
auto status = nfc::STATUS_OK;
|
||||||
|
|
||||||
@@ -152,20 +151,20 @@ uint8_t PN7150::format_mifare_classic_mifare_() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (block != 0) {
|
if (block != 0) {
|
||||||
if (this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
@@ -175,30 +174,30 @@ uint8_t PN7150::format_mifare_classic_mifare_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::format_mifare_classic_ndef_() {
|
uint8_t PN7150::format_mifare_classic_ndef_() {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
|
std::vector<uint8_t> empty_ndef_message(
|
||||||
0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
|
std::vector<uint8_t> blank_block(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
|
std::vector<uint8_t> block_1_data(
|
||||||
0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
|
std::vector<uint8_t> block_2_data(
|
||||||
0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
|
std::vector<uint8_t> block_3_trailer(
|
||||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
|
std::vector<uint8_t> ndef_trailer(
|
||||||
0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,26 +210,25 @@ uint8_t PN7150::format_mifare_classic_ndef_() {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (block == 4) {
|
if (block == 4) {
|
||||||
if (this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size()) !=
|
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||||
nfc::STATUS_OK) {
|
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
@@ -238,7 +236,7 @@ uint8_t PN7150::format_mifare_classic_ndef_() {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
|
uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||||
nfc::NciMessage rx;
|
nfc::NciMessage rx;
|
||||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||||
|
|
||||||
@@ -250,7 +248,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, const uint8_t *da
|
|||||||
}
|
}
|
||||||
// write command part two
|
// write command part two
|
||||||
tx.set_payload({XCHG_DATA_OID});
|
tx.set_payload({XCHG_DATA_OID});
|
||||||
tx.get_message().insert(tx.get_message().end(), data, data + len);
|
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||||
@@ -296,8 +294,8 @@ uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) !=
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||||
nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -145,8 +144,8 @@ uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha
|
|||||||
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
while (index < buffer_length) {
|
while (index < buffer_length) {
|
||||||
if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) !=
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||||
nfc::STATUS_OK) {
|
if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||||
@@ -159,19 +158,19 @@ uint8_t PN7150::clean_mifare_ultralight_() {
|
|||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
|
std::vector<uint8_t> blank_data = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
||||||
if (this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nfc::STATUS_OK;
|
return nfc::STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
|
uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||||
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
||||||
payload.insert(payload.end(), write_data, write_data + len);
|
payload.insert(payload.end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
nfc::NciMessage rx;
|
nfc::NciMessage rx;
|
||||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ class PN7160 : public nfc::Nfcc, public Component {
|
|||||||
|
|
||||||
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag);
|
||||||
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len);
|
uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||||
uint8_t sect_to_auth_(uint8_t block_num);
|
uint8_t sect_to_auth_(uint8_t block_num);
|
||||||
uint8_t format_mifare_classic_mifare_();
|
uint8_t format_mifare_classic_mifare_();
|
||||||
@@ -267,7 +267,7 @@ class PN7160 : public nfc::Nfcc, public Component {
|
|||||||
uint16_t read_mifare_ultralight_capacity_();
|
uint16_t read_mifare_ultralight_capacity_();
|
||||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len);
|
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
uint8_t clean_mifare_ultralight_();
|
uint8_t clean_mifare_ultralight_();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "pn7160.h"
|
#include "pn7160.h"
|
||||||
@@ -140,10 +139,10 @@ uint8_t PN7160::sect_to_auth_(const uint8_t block_num) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::format_mifare_classic_mifare_() {
|
uint8_t PN7160::format_mifare_classic_mifare_() {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BUFFER = {
|
std::vector<uint8_t> blank_buffer(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> TRAILER_BUFFER = {
|
std::vector<uint8_t> trailer_buffer(
|
||||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
auto status = nfc::STATUS_OK;
|
auto status = nfc::STATUS_OK;
|
||||||
|
|
||||||
@@ -152,20 +151,20 @@ uint8_t PN7160::format_mifare_classic_mifare_() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (block != 0) {
|
if (block != 0) {
|
||||||
if (this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 3);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
@@ -175,30 +174,30 @@ uint8_t PN7160::format_mifare_classic_mifare_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::format_mifare_classic_ndef_() {
|
uint8_t PN7160::format_mifare_classic_ndef_() {
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> EMPTY_NDEF_MESSAGE = {
|
std::vector<uint8_t> empty_ndef_message(
|
||||||
0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLANK_BLOCK = {
|
std::vector<uint8_t> blank_block(
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_1_DATA = {
|
std::vector<uint8_t> block_1_data(
|
||||||
0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_2_DATA = {
|
std::vector<uint8_t> block_2_data(
|
||||||
0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
|
{0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> BLOCK_3_TRAILER = {
|
std::vector<uint8_t> block_3_trailer(
|
||||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_CLASSIC_BLOCK_SIZE> NDEF_TRAILER = {
|
std::vector<uint8_t> ndef_trailer(
|
||||||
0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
|
||||||
|
|
||||||
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting");
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,26 +210,25 @@ uint8_t PN7160::format_mifare_classic_ndef_() {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (block == 4) {
|
if (block == 4) {
|
||||||
if (this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size()) !=
|
if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) {
|
||||||
nfc::STATUS_OK) {
|
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block);
|
ESP_LOGE(TAG, "Unable to write block %u", block);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 1);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
ESP_LOGE(TAG, "Unable to write block %u", block + 2);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
if (this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) {
|
||||||
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3);
|
||||||
status = nfc::STATUS_FAILED;
|
status = nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
@@ -238,7 +236,7 @@ uint8_t PN7160::format_mifare_classic_ndef_() {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) {
|
uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||||
nfc::NciMessage rx;
|
nfc::NciMessage rx;
|
||||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||||
@@ -250,7 +248,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, const uint8_t *da
|
|||||||
}
|
}
|
||||||
// write command part two
|
// write command part two
|
||||||
tx.set_payload({XCHG_DATA_OID});
|
tx.set_payload({XCHG_DATA_OID});
|
||||||
tx.get_message().insert(tx.get_message().end(), data, data + len);
|
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||||
@@ -296,8 +294,8 @@ uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr<nfc::NdefMessage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) !=
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE);
|
||||||
nfc::STATUS_OK) {
|
if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
index += nfc::MIFARE_CLASSIC_BLOCK_SIZE;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include <array>
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -145,8 +144,8 @@ uint8_t PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha
|
|||||||
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
while (index < buffer_length) {
|
while (index < buffer_length) {
|
||||||
if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) !=
|
std::vector<uint8_t> data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE);
|
||||||
nfc::STATUS_OK) {
|
if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE;
|
||||||
@@ -159,19 +158,19 @@ uint8_t PN7160::clean_mifare_ultralight_() {
|
|||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE;
|
||||||
|
|
||||||
static constexpr std::array<uint8_t, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE> BLANK_DATA = {0x00, 0x00, 0x00, 0x00};
|
std::vector<uint8_t> blank_data = {0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) {
|
||||||
if (this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size()) != nfc::STATUS_OK) {
|
if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) {
|
||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nfc::STATUS_OK;
|
return nfc::STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) {
|
uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data) {
|
||||||
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
std::vector<uint8_t> payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num};
|
||||||
payload.insert(payload.end(), write_data, write_data + len);
|
payload.insert(payload.end(), write_data.begin(), write_data.end());
|
||||||
|
|
||||||
nfc::NciMessage rx;
|
nfc::NciMessage rx;
|
||||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload);
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
|
||||||
from esphome.components.const import CONF_BYTE_ORDER
|
|
||||||
from esphome.components.image import (
|
|
||||||
IMAGE_TYPE,
|
|
||||||
Image_,
|
|
||||||
validate_settings,
|
|
||||||
validate_transparency,
|
|
||||||
validate_type,
|
|
||||||
)
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.const import CONF_FORMAT, CONF_ID, CONF_RESIZE, CONF_TYPE
|
|
||||||
|
|
||||||
AUTO_LOAD = ["image"]
|
|
||||||
CODEOWNERS = ["@guillempages", "@clydebarrow", "@kahrendt"]
|
|
||||||
|
|
||||||
CONF_PLACEHOLDER = "placeholder"
|
|
||||||
CONF_TRANSPARENCY = "transparency"
|
|
||||||
|
|
||||||
runtime_image_ns = cg.esphome_ns.namespace("runtime_image")
|
|
||||||
|
|
||||||
# Base decoder classes
|
|
||||||
ImageDecoder = runtime_image_ns.class_("ImageDecoder")
|
|
||||||
BmpDecoder = runtime_image_ns.class_("BmpDecoder", ImageDecoder)
|
|
||||||
JpegDecoder = runtime_image_ns.class_("JpegDecoder", ImageDecoder)
|
|
||||||
PngDecoder = runtime_image_ns.class_("PngDecoder", ImageDecoder)
|
|
||||||
|
|
||||||
# Runtime image class
|
|
||||||
RuntimeImage = runtime_image_ns.class_(
|
|
||||||
"RuntimeImage", cg.esphome_ns.namespace("image").class_("Image")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Image format enum
|
|
||||||
ImageFormat = runtime_image_ns.enum("ImageFormat")
|
|
||||||
IMAGE_FORMAT_AUTO = ImageFormat.AUTO
|
|
||||||
IMAGE_FORMAT_JPEG = ImageFormat.JPEG
|
|
||||||
IMAGE_FORMAT_PNG = ImageFormat.PNG
|
|
||||||
IMAGE_FORMAT_BMP = ImageFormat.BMP
|
|
||||||
|
|
||||||
# Export enum for decode errors
|
|
||||||
DecodeError = runtime_image_ns.enum("DecodeError")
|
|
||||||
DECODE_ERROR_INVALID_TYPE = DecodeError.DECODE_ERROR_INVALID_TYPE
|
|
||||||
DECODE_ERROR_UNSUPPORTED_FORMAT = DecodeError.DECODE_ERROR_UNSUPPORTED_FORMAT
|
|
||||||
DECODE_ERROR_OUT_OF_MEMORY = DecodeError.DECODE_ERROR_OUT_OF_MEMORY
|
|
||||||
|
|
||||||
|
|
||||||
class Format:
|
|
||||||
"""Base class for image format definitions."""
|
|
||||||
|
|
||||||
def __init__(self, name: str, decoder_class: cg.MockObjClass) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.decoder_class = decoder_class
|
|
||||||
|
|
||||||
def actions(self) -> None:
|
|
||||||
"""Add defines and libraries needed for this format."""
|
|
||||||
|
|
||||||
|
|
||||||
class BMPFormat(Format):
|
|
||||||
"""BMP format decoder configuration."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("BMP", BmpDecoder)
|
|
||||||
|
|
||||||
def actions(self) -> None:
|
|
||||||
cg.add_define("USE_RUNTIME_IMAGE_BMP")
|
|
||||||
|
|
||||||
|
|
||||||
class JPEGFormat(Format):
|
|
||||||
"""JPEG format decoder configuration."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("JPEG", JpegDecoder)
|
|
||||||
|
|
||||||
def actions(self) -> None:
|
|
||||||
cg.add_define("USE_RUNTIME_IMAGE_JPEG")
|
|
||||||
cg.add_library("JPEGDEC", None, "https://github.com/bitbank2/JPEGDEC#ca1e0f2")
|
|
||||||
|
|
||||||
|
|
||||||
class PNGFormat(Format):
|
|
||||||
"""PNG format decoder configuration."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("PNG", PngDecoder)
|
|
||||||
|
|
||||||
def actions(self) -> None:
|
|
||||||
cg.add_define("USE_RUNTIME_IMAGE_PNG")
|
|
||||||
cg.add_library("pngle", "1.1.0")
|
|
||||||
|
|
||||||
|
|
||||||
# Registry of available formats
|
|
||||||
IMAGE_FORMATS = {
|
|
||||||
"BMP": BMPFormat(),
|
|
||||||
"JPEG": JPEGFormat(),
|
|
||||||
"PNG": PNGFormat(),
|
|
||||||
"JPG": JPEGFormat(), # Alias for JPEG
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_format(format_name: str) -> Format | None:
|
|
||||||
"""Get a format instance by name."""
|
|
||||||
return IMAGE_FORMATS.get(format_name.upper())
|
|
||||||
|
|
||||||
|
|
||||||
def enable_format(format_name: str) -> Format | None:
|
|
||||||
"""Enable a specific image format by adding its defines and libraries."""
|
|
||||||
format_obj = get_format(format_name)
|
|
||||||
if format_obj:
|
|
||||||
format_obj.actions()
|
|
||||||
return format_obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Runtime image configuration schema base - to be extended by components
|
|
||||||
def runtime_image_schema(image_class: cg.MockObjClass = RuntimeImage) -> cv.Schema:
|
|
||||||
"""Create a runtime image schema with the specified image class."""
|
|
||||||
return cv.Schema(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_ID): cv.declare_id(image_class),
|
|
||||||
cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, upper=True),
|
|
||||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
|
||||||
cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE),
|
|
||||||
cv.Optional(CONF_BYTE_ORDER): cv.one_of(
|
|
||||||
"BIG_ENDIAN", "LITTLE_ENDIAN", upper=True
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_TRANSPARENCY, default="OPAQUE"): validate_transparency(),
|
|
||||||
cv.Optional(CONF_PLACEHOLDER): cv.use_id(Image_),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_runtime_image_settings(config: dict) -> dict:
|
|
||||||
"""Apply validate_settings from image component to runtime image config."""
|
|
||||||
return validate_settings(config)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RuntimeImageSettings:
|
|
||||||
"""Processed runtime image configuration parameters."""
|
|
||||||
|
|
||||||
width: int
|
|
||||||
height: int
|
|
||||||
format_enum: cg.MockObj
|
|
||||||
image_type_enum: cg.MockObj
|
|
||||||
transparent: cg.MockObj
|
|
||||||
byte_order_big_endian: bool
|
|
||||||
placeholder: cg.MockObj | None
|
|
||||||
|
|
||||||
|
|
||||||
async def process_runtime_image_config(config: dict) -> RuntimeImageSettings:
|
|
||||||
"""
|
|
||||||
Helper function to process common runtime image configuration parameters.
|
|
||||||
Handles format enabling and returns all necessary enums and parameters.
|
|
||||||
"""
|
|
||||||
from esphome.components.image import get_image_type_enum, get_transparency_enum
|
|
||||||
|
|
||||||
# Get resize dimensions with default (0, 0)
|
|
||||||
width, height = config.get(CONF_RESIZE, (0, 0))
|
|
||||||
|
|
||||||
# Handle format (required for runtime images)
|
|
||||||
format_name = config[CONF_FORMAT]
|
|
||||||
# Enable the format in the runtime_image component
|
|
||||||
enable_format(format_name)
|
|
||||||
# Map format names to enum values (handle JPG as alias for JPEG)
|
|
||||||
if format_name.upper() == "JPG":
|
|
||||||
format_name = "JPEG"
|
|
||||||
format_enum = getattr(ImageFormat, format_name.upper())
|
|
||||||
|
|
||||||
# Get image type enum
|
|
||||||
image_type_enum = get_image_type_enum(config[CONF_TYPE])
|
|
||||||
|
|
||||||
# Get transparency enum
|
|
||||||
transparent = get_transparency_enum(config.get(CONF_TRANSPARENCY, "OPAQUE"))
|
|
||||||
|
|
||||||
# Get byte order (True for big endian, False for little endian)
|
|
||||||
byte_order_big_endian = config.get(CONF_BYTE_ORDER) != "LITTLE_ENDIAN"
|
|
||||||
|
|
||||||
# Get placeholder if specified
|
|
||||||
placeholder = None
|
|
||||||
if placeholder_id := config.get(CONF_PLACEHOLDER):
|
|
||||||
placeholder = await cg.get_variable(placeholder_id)
|
|
||||||
|
|
||||||
return RuntimeImageSettings(
|
|
||||||
width=width,
|
|
||||||
height=height,
|
|
||||||
format_enum=format_enum,
|
|
||||||
image_type_enum=image_type_enum,
|
|
||||||
transparent=transparent,
|
|
||||||
byte_order_big_endian=byte_order_big_endian,
|
|
||||||
placeholder=placeholder,
|
|
||||||
)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#include "image_decoder.h"
|
|
||||||
#include "runtime_image.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
|
||||||
|
|
||||||
static const char *const TAG = "image_decoder";
|
|
||||||
|
|
||||||
bool ImageDecoder::set_size(int width, int height) {
|
|
||||||
bool success = this->image_->resize(width, height) > 0;
|
|
||||||
this->x_scale_ = static_cast<double>(this->image_->get_buffer_width()) / width;
|
|
||||||
this->y_scale_ = static_cast<double>(this->image_->get_buffer_height()) / height;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageDecoder::draw(int x, int y, int w, int h, const Color &color) {
|
|
||||||
auto width = std::min(this->image_->get_buffer_width(), static_cast<int>(std::ceil((x + w) * this->x_scale_)));
|
|
||||||
auto height = std::min(this->image_->get_buffer_height(), static_cast<int>(std::ceil((y + h) * this->y_scale_)));
|
|
||||||
for (int i = x * this->x_scale_; i < width; i++) {
|
|
||||||
for (int j = y * this->y_scale_; j < height; j++) {
|
|
||||||
this->image_->draw_pixel(i, j, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
#include "runtime_image.h"
|
|
||||||
#include "image_decoder.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
|
||||||
#include "bmp_decoder.h"
|
|
||||||
#endif
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
|
||||||
#include "jpeg_decoder.h"
|
|
||||||
#endif
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
|
||||||
#include "png_decoder.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
|
||||||
|
|
||||||
static const char *const TAG = "runtime_image";
|
|
||||||
|
|
||||||
inline bool is_color_on(const Color &color) {
|
|
||||||
// This produces the most accurate monochrome conversion, but is slightly slower.
|
|
||||||
// return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) > 127;
|
|
||||||
|
|
||||||
// Approximation using fast integer computations; produces acceptable results
|
|
||||||
// Equivalent to 0.25 * R + 0.5 * G + 0.25 * B
|
|
||||||
return ((color.r >> 2) + (color.g >> 1) + (color.b >> 2)) & 0x80;
|
|
||||||
}
|
|
||||||
|
|
||||||
RuntimeImage::RuntimeImage(ImageFormat format, image::ImageType type, image::Transparency transparency,
|
|
||||||
image::Image *placeholder, bool is_big_endian, int fixed_width, int fixed_height)
|
|
||||||
: Image(nullptr, 0, 0, type, transparency),
|
|
||||||
format_(format),
|
|
||||||
fixed_width_(fixed_width),
|
|
||||||
fixed_height_(fixed_height),
|
|
||||||
placeholder_(placeholder),
|
|
||||||
is_big_endian_(is_big_endian) {}
|
|
||||||
|
|
||||||
RuntimeImage::~RuntimeImage() { this->release(); }
|
|
||||||
|
|
||||||
int RuntimeImage::resize(int width, int height) {
|
|
||||||
// Use fixed dimensions if specified (0 means auto-resize)
|
|
||||||
int target_width = this->fixed_width_ ? this->fixed_width_ : width;
|
|
||||||
int target_height = this->fixed_height_ ? this->fixed_height_ : height;
|
|
||||||
|
|
||||||
size_t result = this->resize_buffer_(target_width, target_height);
|
|
||||||
if (result > 0 && this->progressive_display_) {
|
|
||||||
// Update display dimensions for progressive display
|
|
||||||
this->width_ = this->buffer_width_;
|
|
||||||
this->height_ = this->buffer_height_;
|
|
||||||
this->data_start_ = this->buffer_;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeImage::draw_pixel(int x, int y, const Color &color) {
|
|
||||||
if (!this->buffer_) {
|
|
||||||
ESP_LOGE(TAG, "Buffer not allocated!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (x < 0 || y < 0 || x >= this->buffer_width_ || y >= this->buffer_height_) {
|
|
||||||
ESP_LOGE(TAG, "Tried to paint a pixel (%d,%d) outside the image!", x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this->type_) {
|
|
||||||
case image::IMAGE_TYPE_BINARY: {
|
|
||||||
const uint32_t width_8 = ((this->buffer_width_ + 7u) / 8u) * 8u;
|
|
||||||
uint32_t pos = x + y * width_8;
|
|
||||||
auto bitno = 0x80 >> (pos % 8u);
|
|
||||||
pos /= 8u;
|
|
||||||
auto on = is_color_on(color);
|
|
||||||
if (this->has_transparency() && color.w < 0x80)
|
|
||||||
on = false;
|
|
||||||
if (on) {
|
|
||||||
this->buffer_[pos] |= bitno;
|
|
||||||
} else {
|
|
||||||
this->buffer_[pos] &= ~bitno;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case image::IMAGE_TYPE_GRAYSCALE: {
|
|
||||||
uint32_t pos = this->get_position_(x, y);
|
|
||||||
auto gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
|
|
||||||
if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) {
|
|
||||||
if (gray == 1) {
|
|
||||||
gray = 0;
|
|
||||||
}
|
|
||||||
if (color.w < 0x80) {
|
|
||||||
gray = 1;
|
|
||||||
}
|
|
||||||
} else if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
|
||||||
if (color.w != 0xFF)
|
|
||||||
gray = color.w;
|
|
||||||
}
|
|
||||||
this->buffer_[pos] = gray;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case image::IMAGE_TYPE_RGB565: {
|
|
||||||
uint32_t pos = this->get_position_(x, y);
|
|
||||||
Color mapped_color = color;
|
|
||||||
this->map_chroma_key(mapped_color);
|
|
||||||
uint16_t rgb565 = display::ColorUtil::color_to_565(mapped_color);
|
|
||||||
if (this->is_big_endian_) {
|
|
||||||
this->buffer_[pos + 0] = static_cast<uint8_t>((rgb565 >> 8) & 0xFF);
|
|
||||||
this->buffer_[pos + 1] = static_cast<uint8_t>(rgb565 & 0xFF);
|
|
||||||
} else {
|
|
||||||
this->buffer_[pos + 0] = static_cast<uint8_t>(rgb565 & 0xFF);
|
|
||||||
this->buffer_[pos + 1] = static_cast<uint8_t>((rgb565 >> 8) & 0xFF);
|
|
||||||
}
|
|
||||||
if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
|
||||||
this->buffer_[pos + 2] = color.w;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case image::IMAGE_TYPE_RGB: {
|
|
||||||
uint32_t pos = this->get_position_(x, y);
|
|
||||||
Color mapped_color = color;
|
|
||||||
this->map_chroma_key(mapped_color);
|
|
||||||
this->buffer_[pos + 0] = mapped_color.r;
|
|
||||||
this->buffer_[pos + 1] = mapped_color.g;
|
|
||||||
this->buffer_[pos + 2] = mapped_color.b;
|
|
||||||
if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
|
|
||||||
this->buffer_[pos + 3] = color.w;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeImage::map_chroma_key(Color &color) {
|
|
||||||
if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) {
|
|
||||||
if (color.g == 1 && color.r == 0 && color.b == 0) {
|
|
||||||
color.g = 0;
|
|
||||||
}
|
|
||||||
if (color.w < 0x80) {
|
|
||||||
color.r = 0;
|
|
||||||
color.g = this->type_ == image::IMAGE_TYPE_RGB565 ? 4 : 1;
|
|
||||||
color.b = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
|
|
||||||
if (this->data_start_) {
|
|
||||||
// If we have a complete image, use the base class draw method
|
|
||||||
Image::draw(x, y, display, color_on, color_off);
|
|
||||||
} else if (this->placeholder_) {
|
|
||||||
// Show placeholder while the runtime image is not available
|
|
||||||
this->placeholder_->draw(x, y, display, color_on, color_off);
|
|
||||||
}
|
|
||||||
// If no image is loaded and no placeholder, nothing to draw
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeImage::begin_decode(size_t expected_size) {
|
|
||||||
if (this->decoder_) {
|
|
||||||
ESP_LOGW(TAG, "Decoding already in progress");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->decoder_ = this->create_decoder_();
|
|
||||||
if (!this->decoder_) {
|
|
||||||
ESP_LOGE(TAG, "Failed to create decoder for format %d", this->format_);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->total_size_ = expected_size;
|
|
||||||
this->decoded_bytes_ = 0;
|
|
||||||
|
|
||||||
// Initialize decoder
|
|
||||||
int result = this->decoder_->prepare(expected_size);
|
|
||||||
if (result < 0) {
|
|
||||||
ESP_LOGE(TAG, "Failed to prepare decoder: %d", result);
|
|
||||||
this->decoder_ = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RuntimeImage::feed_data(uint8_t *data, size_t len) {
|
|
||||||
if (!this->decoder_) {
|
|
||||||
ESP_LOGE(TAG, "No decoder initialized");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int consumed = this->decoder_->decode(data, len);
|
|
||||||
if (consumed > 0) {
|
|
||||||
this->decoded_bytes_ += consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeImage::end_decode() {
|
|
||||||
if (!this->decoder_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the image for display
|
|
||||||
if (!this->progressive_display_) {
|
|
||||||
// Only now make the image visible
|
|
||||||
this->width_ = this->buffer_width_;
|
|
||||||
this->height_ = this->buffer_height_;
|
|
||||||
this->data_start_ = this->buffer_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up decoder
|
|
||||||
this->decoder_ = nullptr;
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Decoding complete: %dx%d, %zu bytes", this->width_, this->height_, this->decoded_bytes_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RuntimeImage::is_decode_finished() const {
|
|
||||||
if (!this->decoder_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this->decoder_->is_finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeImage::release() {
|
|
||||||
this->release_buffer_();
|
|
||||||
// Reset decoder separately — release() can be called from within the decoder
|
|
||||||
// (via set_size -> resize -> resize_buffer_), so we must not destroy the decoder here.
|
|
||||||
// The decoder lifecycle is managed by begin_decode()/end_decode().
|
|
||||||
this->decoder_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RuntimeImage::release_buffer_() {
|
|
||||||
if (this->buffer_) {
|
|
||||||
ESP_LOGV(TAG, "Releasing buffer of size %zu", this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
|
|
||||||
this->allocator_.deallocate(this->buffer_, this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
|
|
||||||
this->buffer_ = nullptr;
|
|
||||||
this->data_start_ = nullptr;
|
|
||||||
this->width_ = 0;
|
|
||||||
this->height_ = 0;
|
|
||||||
this->buffer_width_ = 0;
|
|
||||||
this->buffer_height_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t RuntimeImage::resize_buffer_(int width, int height) {
|
|
||||||
size_t new_size = this->get_buffer_size_(width, height);
|
|
||||||
|
|
||||||
if (this->buffer_ && this->buffer_width_ == width && this->buffer_height_ == height) {
|
|
||||||
// Buffer already allocated with correct size
|
|
||||||
return new_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release old buffer if dimensions changed
|
|
||||||
if (this->buffer_) {
|
|
||||||
this->release_buffer_();
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Allocating buffer: %dx%d, %zu bytes", width, height, new_size);
|
|
||||||
this->buffer_ = this->allocator_.allocate(new_size);
|
|
||||||
|
|
||||||
if (!this->buffer_) {
|
|
||||||
ESP_LOGE(TAG, "Failed to allocate %zu bytes. Largest free block: %zu", new_size,
|
|
||||||
this->allocator_.get_max_free_block_size());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear buffer
|
|
||||||
memset(this->buffer_, 0, new_size);
|
|
||||||
|
|
||||||
this->buffer_width_ = width;
|
|
||||||
this->buffer_height_ = height;
|
|
||||||
|
|
||||||
return new_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t RuntimeImage::get_buffer_size_(int width, int height) const {
|
|
||||||
return (this->get_bpp() * width + 7u) / 8u * height;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RuntimeImage::get_position_(int x, int y) const { return (x + y * this->buffer_width_) * this->get_bpp() / 8; }
|
|
||||||
|
|
||||||
std::unique_ptr<ImageDecoder> RuntimeImage::create_decoder_() {
|
|
||||||
switch (this->format_) {
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
|
||||||
case BMP:
|
|
||||||
return make_unique<BmpDecoder>(this);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
|
||||||
case JPEG:
|
|
||||||
return make_unique<JpegDecoder>(this);
|
|
||||||
#endif
|
|
||||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
|
||||||
case PNG:
|
|
||||||
return make_unique<PngDecoder>(this);
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
ESP_LOGE(TAG, "Unsupported image format: %d", this->format_);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "esphome/components/image/image.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
|
||||||
|
|
||||||
namespace esphome::runtime_image {
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
class ImageDecoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Image format types that can be decoded dynamically.
|
|
||||||
*/
|
|
||||||
enum ImageFormat {
|
|
||||||
/** Automatically detect from data. Not implemented yet. */
|
|
||||||
AUTO,
|
|
||||||
/** JPEG format. */
|
|
||||||
JPEG,
|
|
||||||
/** PNG format. */
|
|
||||||
PNG,
|
|
||||||
/** BMP format. */
|
|
||||||
BMP,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A dynamic image that can be loaded and decoded at runtime.
|
|
||||||
*
|
|
||||||
* This class provides dynamic buffer allocation and management for images
|
|
||||||
* that are decoded at runtime, as opposed to static images compiled into
|
|
||||||
* the firmware. It serves as a base class for components that need to
|
|
||||||
* load images dynamically from various sources.
|
|
||||||
*/
|
|
||||||
class RuntimeImage : public image::Image {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new RuntimeImage object.
|
|
||||||
*
|
|
||||||
* @param format The image format to decode.
|
|
||||||
* @param type The pixel format for the image.
|
|
||||||
* @param transparency The transparency type for the image.
|
|
||||||
* @param placeholder Optional placeholder image to show while loading.
|
|
||||||
* @param is_big_endian Whether the image is stored in big-endian format.
|
|
||||||
* @param fixed_width Fixed width for the image (0 for auto-resize).
|
|
||||||
* @param fixed_height Fixed height for the image (0 for auto-resize).
|
|
||||||
*/
|
|
||||||
RuntimeImage(ImageFormat format, image::ImageType type, image::Transparency transparency,
|
|
||||||
image::Image *placeholder = nullptr, bool is_big_endian = false, int fixed_width = 0,
|
|
||||||
int fixed_height = 0);
|
|
||||||
|
|
||||||
~RuntimeImage();
|
|
||||||
|
|
||||||
// Decoder interface methods
|
|
||||||
/**
|
|
||||||
* @brief Resize the image buffer to the requested dimensions.
|
|
||||||
*
|
|
||||||
* The buffer will be allocated if not existing.
|
|
||||||
* If fixed dimensions have been specified in the constructor, the buffer will be created
|
|
||||||
* with those dimensions and not resized, even on request.
|
|
||||||
* Otherwise, the old buffer will be deallocated and a new buffer with the requested
|
|
||||||
* dimensions allocated.
|
|
||||||
*
|
|
||||||
* @param width Requested width (ignored if fixed_width_ is set)
|
|
||||||
* @param height Requested height (ignored if fixed_height_ is set)
|
|
||||||
* @return Size of the allocated buffer in bytes, or 0 if allocation failed.
|
|
||||||
*/
|
|
||||||
int resize(int width, int height);
|
|
||||||
void draw_pixel(int x, int y, const Color &color);
|
|
||||||
void map_chroma_key(Color &color);
|
|
||||||
int get_buffer_width() const { return this->buffer_width_; }
|
|
||||||
int get_buffer_height() const { return this->buffer_height_; }
|
|
||||||
|
|
||||||
// Image drawing interface
|
|
||||||
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Begin decoding an image.
|
|
||||||
*
|
|
||||||
* @param expected_size Optional hint about the expected data size.
|
|
||||||
* @return true if decoder was successfully initialized.
|
|
||||||
*/
|
|
||||||
bool begin_decode(size_t expected_size = 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Feed data to the decoder.
|
|
||||||
*
|
|
||||||
* @param data Pointer to the data buffer.
|
|
||||||
* @param len Length of data to process.
|
|
||||||
* @return Number of bytes consumed by the decoder.
|
|
||||||
*/
|
|
||||||
int feed_data(uint8_t *data, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Complete the decoding process.
|
|
||||||
*
|
|
||||||
* @return true if decoding completed successfully.
|
|
||||||
*/
|
|
||||||
bool end_decode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if decoding is currently in progress.
|
|
||||||
*/
|
|
||||||
bool is_decoding() const { return this->decoder_ != nullptr; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if the decoder has finished processing all data.
|
|
||||||
*
|
|
||||||
* This delegates to the decoder's format-specific completion check,
|
|
||||||
* which handles both known-size and chunked transfer cases.
|
|
||||||
*/
|
|
||||||
bool is_decode_finished() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if an image is currently loaded.
|
|
||||||
*/
|
|
||||||
bool is_loaded() const { return this->buffer_ != nullptr; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the image format.
|
|
||||||
*/
|
|
||||||
ImageFormat get_format() const { return this->format_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Release the image buffer and free memory.
|
|
||||||
*/
|
|
||||||
void release();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set whether to allow progressive display during decode.
|
|
||||||
*
|
|
||||||
* When enabled, the image can be displayed even while still decoding.
|
|
||||||
* When disabled, the image is only displayed after decoding completes.
|
|
||||||
*/
|
|
||||||
void set_progressive_display(bool progressive) { this->progressive_display_ = progressive; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief Resize the image buffer to the requested dimensions.
|
|
||||||
*
|
|
||||||
* @param width New width in pixels.
|
|
||||||
* @param height New height in pixels.
|
|
||||||
* @return Size of the allocated buffer, or 0 on failure.
|
|
||||||
*/
|
|
||||||
size_t resize_buffer_(int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Release only the image buffer without resetting the decoder.
|
|
||||||
*
|
|
||||||
* This is safe to call from within the decoder (e.g., during resize).
|
|
||||||
*/
|
|
||||||
void release_buffer_();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the buffer size in bytes for given dimensions.
|
|
||||||
*/
|
|
||||||
size_t get_buffer_size_(int width, int height) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the position in the buffer for a pixel.
|
|
||||||
*/
|
|
||||||
int get_position_(int x, int y) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create decoder instance for the image's format.
|
|
||||||
*/
|
|
||||||
std::unique_ptr<ImageDecoder> create_decoder_();
|
|
||||||
|
|
||||||
// Memory management
|
|
||||||
RAMAllocator<uint8_t> allocator_{};
|
|
||||||
uint8_t *buffer_{nullptr};
|
|
||||||
|
|
||||||
// Decoder management
|
|
||||||
std::unique_ptr<ImageDecoder> decoder_{nullptr};
|
|
||||||
/** The image format this RuntimeImage is configured to decode. */
|
|
||||||
const ImageFormat format_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actual width of the current image.
|
|
||||||
* This needs to be separate from "Image::get_width()" because the latter
|
|
||||||
* must return 0 until the image has been decoded (to avoid showing partially
|
|
||||||
* decoded images). When progressive_display_ is enabled, Image dimensions
|
|
||||||
* are updated during decoding to allow rendering in progress.
|
|
||||||
*/
|
|
||||||
int buffer_width_{0};
|
|
||||||
/**
|
|
||||||
* Actual height of the current image.
|
|
||||||
* This needs to be separate from "Image::get_height()" because the latter
|
|
||||||
* must return 0 until the image has been decoded (to avoid showing partially
|
|
||||||
* decoded images). When progressive_display_ is enabled, Image dimensions
|
|
||||||
* are updated during decoding to allow rendering in progress.
|
|
||||||
*/
|
|
||||||
int buffer_height_{0};
|
|
||||||
|
|
||||||
// Decoding state
|
|
||||||
size_t total_size_{0};
|
|
||||||
size_t decoded_bytes_{0};
|
|
||||||
|
|
||||||
/** Fixed width requested on configuration, or 0 if not specified. */
|
|
||||||
const int fixed_width_{0};
|
|
||||||
/** Fixed height requested on configuration, or 0 if not specified. */
|
|
||||||
const int fixed_height_{0};
|
|
||||||
|
|
||||||
/** Placeholder image to show when the runtime image is not available. */
|
|
||||||
image::Image *placeholder_{nullptr};
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
bool progressive_display_{false};
|
|
||||||
/**
|
|
||||||
* Whether the image is stored in big-endian format.
|
|
||||||
* This is used to determine how to store 16 bit colors in the buffer.
|
|
||||||
*/
|
|
||||||
bool is_big_endian_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
|
||||||
@@ -219,6 +219,7 @@ async def script_stop_action_to_code(config, action_id, template_arg, args):
|
|||||||
"script.wait",
|
"script.wait",
|
||||||
ScriptWaitAction,
|
ScriptWaitAction,
|
||||||
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(Script)}),
|
||||||
|
deferred=True,
|
||||||
)
|
)
|
||||||
async def script_wait_action_to_code(config, action_id, template_arg, args):
|
async def script_wait_action_to_code(config, action_id, template_arg, args):
|
||||||
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
full_id, paren = await cg.get_variable_with_full_id(config[CONF_ID])
|
||||||
|
|||||||
@@ -216,16 +216,23 @@ bool WiFiComponent::wifi_apply_hostname_() {
|
|||||||
ESP_LOGV(TAG, "Set hostname failed");
|
ESP_LOGV(TAG, "Set hostname failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update hostname on all lwIP interfaces so DHCP packets include it.
|
// inform dhcp server of hostname change using dhcp_renew()
|
||||||
// lwIP includes the hostname in DHCP DISCOVER/REQUEST automatically
|
|
||||||
// via LWIP_NETIF_HOSTNAME — no dhcp_renew() needed. The hostname is
|
|
||||||
// fixed at compile time and never changes at runtime.
|
|
||||||
for (netif *intf = netif_list; intf; intf = intf->next) {
|
for (netif *intf = netif_list; intf; intf = intf->next) {
|
||||||
|
// unconditionally update all known interfaces
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
#if LWIP_VERSION_MAJOR == 1
|
||||||
intf->hostname = (char *) wifi_station_get_hostname();
|
intf->hostname = (char *) wifi_station_get_hostname();
|
||||||
#else
|
#else
|
||||||
intf->hostname = wifi_station_get_hostname();
|
intf->hostname = wifi_station_get_hostname();
|
||||||
#endif
|
#endif
|
||||||
|
if (netif_dhcp_data(intf) != nullptr) {
|
||||||
|
// renew already started DHCP leases
|
||||||
|
err_t lwipret = dhcp_renew(intf);
|
||||||
|
if (lwipret != ERR_OK) {
|
||||||
|
ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname,
|
||||||
|
(int) lwipret, intf->name[0], intf->name[1], intf->num);
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -148,9 +148,9 @@
|
|||||||
#define USE_MQTT
|
#define USE_MQTT
|
||||||
#define USE_MQTT_COVER_JSON
|
#define USE_MQTT_COVER_JSON
|
||||||
#define USE_NETWORK
|
#define USE_NETWORK
|
||||||
#define USE_RUNTIME_IMAGE_BMP
|
#define USE_ONLINE_IMAGE_BMP_SUPPORT
|
||||||
#define USE_RUNTIME_IMAGE_PNG
|
#define USE_ONLINE_IMAGE_PNG_SUPPORT
|
||||||
#define USE_RUNTIME_IMAGE_JPEG
|
#define USE_ONLINE_IMAGE_JPEG_SUPPORT
|
||||||
#define USE_OTA
|
#define USE_OTA
|
||||||
#define USE_OTA_PASSWORD
|
#define USE_OTA_PASSWORD
|
||||||
#define USE_OTA_STATE_LISTENER
|
#define USE_OTA_STATE_LISTENER
|
||||||
|
|||||||
@@ -152,13 +152,11 @@ void EntityBase_UnitOfMeasurement::set_unit_of_measurement(const char *unit_of_m
|
|||||||
this->unit_of_measurement_ = unit_of_measurement;
|
this->unit_of_measurement_ = unit_of_measurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ENTITY_ICON
|
|
||||||
void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj) {
|
void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj) {
|
||||||
if (!obj.get_icon_ref().empty()) {
|
if (!obj.get_icon_ref().empty()) {
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj.get_icon_ref().c_str());
|
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj.get_icon_ref().c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj) {
|
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj) {
|
||||||
if (!obj.get_device_class_ref().empty()) {
|
if (!obj.get_device_class_ref().empty()) {
|
||||||
|
|||||||
@@ -231,13 +231,8 @@ class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming)
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Log entity icon if set (for use in dump_config)
|
/// Log entity icon if set (for use in dump_config)
|
||||||
#ifdef USE_ENTITY_ICON
|
|
||||||
#define LOG_ENTITY_ICON(tag, prefix, obj) log_entity_icon(tag, prefix, obj)
|
#define LOG_ENTITY_ICON(tag, prefix, obj) log_entity_icon(tag, prefix, obj)
|
||||||
void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj);
|
void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj);
|
||||||
#else
|
|
||||||
#define LOG_ENTITY_ICON(tag, prefix, obj) ((void) 0)
|
|
||||||
inline void log_entity_icon(const char *, const char *, const EntityBase &) {}
|
|
||||||
#endif
|
|
||||||
/// Log entity device class if set (for use in dump_config)
|
/// Log entity device class if set (for use in dump_config)
|
||||||
#define LOG_ENTITY_DEVICE_CLASS(tag, prefix, obj) log_entity_device_class(tag, prefix, obj)
|
#define LOG_ENTITY_DEVICE_CLASS(tag, prefix, obj) log_entity_device_class(tag, prefix, obj)
|
||||||
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj);
|
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj);
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ class StringRef {
|
|||||||
|
|
||||||
operator std::string() const { return str(); }
|
operator std::string() const { return str(); }
|
||||||
|
|
||||||
|
/// Compare with a null-terminated C string (compatible with std::string::compare)
|
||||||
|
int compare(const char *s) const {
|
||||||
|
size_t s_len = std::strlen(s);
|
||||||
|
int result = std::memcmp(base_, s, std::min(len_, s_len));
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
if (len_ < s_len)
|
||||||
|
return -1;
|
||||||
|
if (len_ > s_len)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Find first occurrence of substring, returns std::string::npos if not found.
|
/// Find first occurrence of substring, returns std::string::npos if not found.
|
||||||
/// Note: Requires the underlying string to be null-terminated.
|
/// Note: Requires the underlying string to be null-terminated.
|
||||||
size_type find(const char *s, size_type pos = 0) const {
|
size_type find(const char *s, size_type pos = 0) const {
|
||||||
|
|||||||
@@ -24,11 +24,14 @@ class RegistryEntry:
|
|||||||
fun: Callable[..., Any],
|
fun: Callable[..., Any],
|
||||||
type_id: "MockObjClass",
|
type_id: "MockObjClass",
|
||||||
schema: "Schema",
|
schema: "Schema",
|
||||||
|
*,
|
||||||
|
deferred: bool = False,
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.fun = fun
|
self.fun = fun
|
||||||
self.type_id = type_id
|
self.type_id = type_id
|
||||||
self.raw_schema = schema
|
self.raw_schema = schema
|
||||||
|
self.deferred = deferred
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coroutine_fun(self):
|
def coroutine_fun(self):
|
||||||
@@ -49,9 +52,16 @@ class Registry(dict[str, RegistryEntry]):
|
|||||||
self.base_schema = base_schema or {}
|
self.base_schema = base_schema or {}
|
||||||
self.type_id_key = type_id_key
|
self.type_id_key = type_id_key
|
||||||
|
|
||||||
def register(self, name: str, type_id: "MockObjClass", schema: "Schema"):
|
def register(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
type_id: "MockObjClass",
|
||||||
|
schema: "Schema",
|
||||||
|
*,
|
||||||
|
deferred: bool = False,
|
||||||
|
):
|
||||||
def decorator(fun: Callable[..., Any]):
|
def decorator(fun: Callable[..., Any]):
|
||||||
self[name] = RegistryEntry(name, fun, type_id, schema)
|
self[name] = RegistryEntry(name, fun, type_id, schema, deferred=deferred)
|
||||||
return fun
|
return fun
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@@ -2020,6 +2020,8 @@ def build_message_type(
|
|||||||
|
|
||||||
# Collect fixed_vector fields for custom decode generation
|
# Collect fixed_vector fields for custom decode generation
|
||||||
fixed_vector_fields = []
|
fixed_vector_fields = []
|
||||||
|
# Collect fields with (null_terminate) = true option
|
||||||
|
null_terminate_fields = []
|
||||||
|
|
||||||
for field in desc.field:
|
for field in desc.field:
|
||||||
# Skip deprecated fields completely
|
# Skip deprecated fields completely
|
||||||
@@ -2062,6 +2064,10 @@ def build_message_type(
|
|||||||
|
|
||||||
ti = create_field_type_info(field, needs_decode, needs_encode)
|
ti = create_field_type_info(field, needs_decode, needs_encode)
|
||||||
|
|
||||||
|
# Collect fields with (null_terminate) = true for post-decode null-termination
|
||||||
|
if needs_decode and get_field_opt(field, pb.null_terminate, False):
|
||||||
|
null_terminate_fields.append(ti.field_name)
|
||||||
|
|
||||||
# Skip field declarations for fields that are in the base class
|
# Skip field declarations for fields that are in the base class
|
||||||
# but include their encode/decode logic
|
# but include their encode/decode logic
|
||||||
if field.name not in common_field_names:
|
if field.name not in common_field_names:
|
||||||
@@ -2168,8 +2174,8 @@ def build_message_type(
|
|||||||
prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;"
|
prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;"
|
||||||
protected_content.insert(0, prot)
|
protected_content.insert(0, prot)
|
||||||
|
|
||||||
# Generate custom decode() override for messages with FixedVector fields
|
# Generate custom decode() override for messages with FixedVector or null_terminate fields
|
||||||
if fixed_vector_fields:
|
if fixed_vector_fields or null_terminate_fields:
|
||||||
# Generate the decode() implementation in cpp
|
# Generate the decode() implementation in cpp
|
||||||
o = f"void {desc.name}::decode(const uint8_t *buffer, size_t length) {{\n"
|
o = f"void {desc.name}::decode(const uint8_t *buffer, size_t length) {{\n"
|
||||||
# Count and init each FixedVector field
|
# Count and init each FixedVector field
|
||||||
@@ -2178,6 +2184,13 @@ def build_message_type(
|
|||||||
o += f" this->{field_name}.init(count_{field_name});\n"
|
o += f" this->{field_name}.init(count_{field_name});\n"
|
||||||
# Call parent decode to populate the fields
|
# Call parent decode to populate the fields
|
||||||
o += " ProtoDecodableMessage::decode(buffer, length);\n"
|
o += " ProtoDecodableMessage::decode(buffer, length);\n"
|
||||||
|
# Null-terminate fields marked with (null_terminate) = true in-place.
|
||||||
|
# Safe: decode is complete, byte after string was already parsed (next field tag)
|
||||||
|
# or is the +1 reserved byte at end of rx_buf_.
|
||||||
|
for field_name in null_terminate_fields:
|
||||||
|
o += f" if (!this->{field_name}.empty()) {{\n"
|
||||||
|
o += f" const_cast<char *>(this->{field_name}.c_str())[this->{field_name}.size()] = '\\0';\n"
|
||||||
|
o += " }\n"
|
||||||
o += "}\n"
|
o += "}\n"
|
||||||
cpp += o
|
cpp += o
|
||||||
# Generate the decode() declaration in header (public method)
|
# Generate the decode() declaration in header (public method)
|
||||||
|
|||||||
Reference in New Issue
Block a user