mirror of
https://github.com/esphome/esphome.git
synced 2026-08-25 23:56:19 +00:00
Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer
This commit is contained in:
@@ -232,6 +232,7 @@ enum SerialProxyPortType {
|
||||
message SerialProxyInfo {
|
||||
string name = 1; // Human-readable port name
|
||||
SerialProxyPortType port_type = 2; // Port type (RS232, RS485)
|
||||
uint32 configured_line_states = 3; // Bitmask of SerialProxyLineStateFlags this instance can drive
|
||||
}
|
||||
|
||||
// DeviceInfoResponse max_data_length values:
|
||||
@@ -2626,6 +2627,22 @@ message ZWaveProxyRequest {
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
enum ZWaveProxyStatus {
|
||||
ZWAVE_PROXY_STATUS_OK = 0; // Request completed successfully
|
||||
ZWAVE_PROXY_STATUS_IN_USE = 1; // Denied: another client is already subscribed
|
||||
ZWAVE_PROXY_STATUS_NOT_SUPPORTED = 2; // Request type not supported
|
||||
}
|
||||
|
||||
// Acknowledges a ZWaveProxyRequest (subscribe/unsubscribe). Sent since API 1.16.
|
||||
message ZWaveProxyRequestResponse {
|
||||
option (id) = 151;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_ZWAVE_PROXY";
|
||||
|
||||
ZWaveProxyRequestType type = 1; // Which request type this responds to
|
||||
ZWaveProxyStatus status = 2; // Result status
|
||||
}
|
||||
|
||||
// ==================== INFRARED ====================
|
||||
// Note: Feature and capability flag enums are defined in
|
||||
// esphome/components/infrared/infrared.h
|
||||
@@ -2769,12 +2786,18 @@ message SerialProxyGetModemPinsResponse {
|
||||
|
||||
uint32 instance = 1; // Instance index (0-based)
|
||||
uint32 line_states = 2; // Bitmask of SerialProxyLineStateFlags
|
||||
SerialProxyStatus status = 3; // INVALID_ARGUMENT if the instance index is out of range (since API 1.16)
|
||||
}
|
||||
|
||||
enum SerialProxyRequestType {
|
||||
SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE = 0; // Subscribe to receive data from this serial proxy instance
|
||||
SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1; // Unsubscribe from this serial proxy instance
|
||||
SERIAL_PROXY_REQUEST_TYPE_FLUSH = 2; // Flush the serial port (block until all TX data is sent)
|
||||
// Values below are only valid in SerialProxyRequestResponse.type, identifying which
|
||||
// operation is being acknowledged. Sending them in SerialProxyRequest.type is an
|
||||
// error the device answers with INVALID_ARGUMENT.
|
||||
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3; // Acknowledges a SerialProxyConfigureRequest
|
||||
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4; // Acknowledges a SerialProxySetModemPinsRequest
|
||||
}
|
||||
|
||||
enum SerialProxyStatus {
|
||||
@@ -2783,6 +2806,8 @@ enum SerialProxyStatus {
|
||||
SERIAL_PROXY_STATUS_ERROR = 2; // Driver or hardware error
|
||||
SERIAL_PROXY_STATUS_TIMEOUT = 3; // Timed out before TX completed
|
||||
SERIAL_PROXY_STATUS_NOT_SUPPORTED = 4; // Request type not supported by this instance
|
||||
SERIAL_PROXY_STATUS_PORT_IN_USE = 5; // Denied: another client holds the port
|
||||
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6; // Invalid instance index or parameter value
|
||||
}
|
||||
|
||||
// Generic request message for simple serial proxy operations
|
||||
@@ -2795,7 +2820,9 @@ message SerialProxyRequest {
|
||||
SerialProxyRequestType type = 2; // Request type
|
||||
}
|
||||
|
||||
// Response to a SerialProxyRequest (e.g. flush completion or failure)
|
||||
// Acknowledges a serial proxy operation; the type field identifies which
|
||||
// operation is being acknowledged. Flush has been acknowledged since the
|
||||
// message was introduced; all other acknowledgements are sent since API 1.16.
|
||||
message SerialProxyRequestResponse {
|
||||
option (id) = 147;
|
||||
option (source) = SOURCE_SERVER;
|
||||
|
||||
@@ -1381,7 +1381,12 @@ void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
|
||||
}
|
||||
|
||||
void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||
zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
|
||||
ZWaveProxyRequestResponse resp{};
|
||||
resp.type = msg.type;
|
||||
resp.status = zwave_proxy::global_zwave_proxy->zwave_proxy_request(this, msg.type);
|
||||
if (!this->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Z-Wave proxy response");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1550,15 +1555,50 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
|
||||
#endif
|
||||
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
static enums::SerialProxyStatus serial_proxy_result_to_status(serial_proxy::SerialProxyResult result) {
|
||||
switch (result) {
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_OK:
|
||||
return enums::SERIAL_PROXY_STATUS_OK;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_ASSUMED_SUCCESS:
|
||||
return enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE:
|
||||
return enums::SERIAL_PROXY_STATUS_PORT_IN_USE;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT:
|
||||
return enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_TIMEOUT:
|
||||
return enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED:
|
||||
return enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED;
|
||||
case serial_proxy::SerialProxyResult::SERIAL_PROXY_RESULT_ERROR:
|
||||
return enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
}
|
||||
return enums::SERIAL_PROXY_STATUS_ERROR; // Unreachable; all enum values handled above
|
||||
}
|
||||
|
||||
static void send_serial_proxy_ack(APIConnection *conn, uint32_t instance, enums::SerialProxyRequestType type,
|
||||
enums::SerialProxyStatus status) {
|
||||
SerialProxyRequestResponse resp{};
|
||||
resp.instance = instance;
|
||||
resp.type = type;
|
||||
resp.status = status;
|
||||
if (!conn->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
|
||||
static_cast<uint32_t>(proxies.size()));
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE,
|
||||
enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->configure(this, msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity),
|
||||
msg.stop_bits, msg.data_size);
|
||||
serial_proxy::SerialProxyResult result = proxies[msg.instance]->configure(
|
||||
this, msg.baudrate, msg.flow_control, static_cast<uint8_t>(msg.parity), msg.stop_bits, msg.data_size);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE,
|
||||
serial_proxy_result_to_status(result));
|
||||
}
|
||||
|
||||
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
|
||||
@@ -1574,20 +1614,30 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS,
|
||||
enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
proxies[msg.instance]->set_modem_pins(this, msg.line_states);
|
||||
serial_proxy::SerialProxyResult result = proxies[msg.instance]->set_modem_pins(this, msg.line_states);
|
||||
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS,
|
||||
serial_proxy_result_to_status(result));
|
||||
}
|
||||
|
||||
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
return;
|
||||
}
|
||||
SerialProxyGetModemPinsResponse resp{};
|
||||
resp.instance = msg.instance;
|
||||
resp.line_states = proxies[msg.instance]->get_modem_pins();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
// Pre-1.16 clients do not read the status field and would take this error
|
||||
// for a successful "both pins deasserted" answer; let them time out as before
|
||||
if (!this->client_supports_api_version(1, 16)) {
|
||||
return;
|
||||
}
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT;
|
||||
} else {
|
||||
resp.line_states = proxies[msg.instance]->get_modem_pins();
|
||||
}
|
||||
if (!this->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
|
||||
}
|
||||
@@ -1597,40 +1647,31 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
|
||||
auto &proxies = App.get_serial_proxies();
|
||||
if (msg.instance >= proxies.size()) {
|
||||
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
|
||||
send_serial_proxy_ack(this, msg.instance, msg.type, enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
auto *proxy = proxies[msg.instance];
|
||||
enums::SerialProxyStatus status;
|
||||
switch (msg.type) {
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
proxies[msg.instance]->serial_proxy_request(this, msg.type);
|
||||
status = serial_proxy_result_to_status(proxy->serial_proxy_request(this, msg.type));
|
||||
break;
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH: {
|
||||
SerialProxyRequestResponse resp{};
|
||||
resp.instance = msg.instance;
|
||||
resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
|
||||
switch (proxies[msg.instance]->flush_port()) {
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_OK;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
|
||||
break;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
|
||||
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
if (!this->send_message(resp)) {
|
||||
API_LOG_MSG_DROPPED(TAG, "Serial proxy response");
|
||||
}
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH:
|
||||
status = serial_proxy_result_to_status(proxy->flush_port(this));
|
||||
break;
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE:
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS:
|
||||
// Response-only discriminators; never valid in a request
|
||||
ESP_LOGW(TAG, "Response-only serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
|
||||
status = enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
|
||||
status = enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
send_serial_proxy_ack(this, msg.instance, msg.type, status);
|
||||
}
|
||||
|
||||
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) {
|
||||
@@ -1757,7 +1798,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 15;
|
||||
resp.api_version_minor = 16;
|
||||
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
|
||||
resp.server_info = ESPHOME_VERSION_REF;
|
||||
resp.name = StringRef(App.get_name());
|
||||
@@ -1891,6 +1932,7 @@ bool APIConnection::send_device_info_response_() {
|
||||
auto &info = resp.serial_proxies[serial_proxy_index++];
|
||||
info.name = StringRef(proxy->get_name());
|
||||
info.port_type = proxy->get_port_type();
|
||||
info.configured_line_states = proxy->get_configured_modem_pins();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
@@ -1951,6 +1993,7 @@ bool APIConnection::send_device_capabilities_response_() {
|
||||
auto &info = resp.serial_proxies[serial_proxy_index++];
|
||||
info.name = StringRef(proxy->get_name());
|
||||
info.port_type = proxy->get_port_type();
|
||||
info.configured_line_states = proxy->get_configured_modem_pins();
|
||||
}
|
||||
#endif
|
||||
return this->send_message(resp);
|
||||
|
||||
@@ -102,12 +102,14 @@ uint8_t *SerialProxyInfo::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PAR
|
||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, this->name);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast<uint32_t>(this->port_type));
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, this->configured_line_states);
|
||||
return pos;
|
||||
}
|
||||
uint32_t SerialProxyInfo::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_length(1, this->name.size());
|
||||
size += this->port_type ? 2 : 0;
|
||||
size += ProtoSize::calc_uint32(1, this->configured_line_states);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
@@ -3942,6 +3944,18 @@ uint32_t ZWaveProxyRequest::calculate_size() const {
|
||||
size += ProtoSize::calc_length(1, this->data_len);
|
||||
return size;
|
||||
}
|
||||
uint8_t *ZWaveProxyRequestResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast<uint32_t>(this->type));
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast<uint32_t>(this->status));
|
||||
return pos;
|
||||
}
|
||||
uint32_t ZWaveProxyRequestResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += this->type ? 2 : 0;
|
||||
size += this->status ? 2 : 0;
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
uint8_t *ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
@@ -4184,12 +4198,14 @@ uint8_t *SerialProxyGetModemPinsResponse::encode(ProtoWriteBuffer &buffer PROTO_
|
||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, this->instance);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, this->line_states);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast<uint32_t>(this->status));
|
||||
return pos;
|
||||
}
|
||||
uint32_t SerialProxyGetModemPinsResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += ProtoSize::calc_uint32(1, this->instance);
|
||||
size += ProtoSize::calc_uint32(1, this->line_states);
|
||||
size += this->status ? 2 : 0;
|
||||
return size;
|
||||
}
|
||||
bool SerialProxyRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
|
||||
|
||||
@@ -334,6 +334,11 @@ enum ZWaveProxyRequestType : uint32_t {
|
||||
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
|
||||
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2,
|
||||
};
|
||||
enum ZWaveProxyStatus : uint32_t {
|
||||
ZWAVE_PROXY_STATUS_OK = 0,
|
||||
ZWAVE_PROXY_STATUS_IN_USE = 1,
|
||||
ZWAVE_PROXY_STATUS_NOT_SUPPORTED = 2,
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
enum SerialProxyParity : uint32_t {
|
||||
@@ -345,6 +350,8 @@ enum SerialProxyRequestType : uint32_t {
|
||||
SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE = 0,
|
||||
SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
|
||||
SERIAL_PROXY_REQUEST_TYPE_FLUSH = 2,
|
||||
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3,
|
||||
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4,
|
||||
};
|
||||
enum SerialProxyStatus : uint32_t {
|
||||
SERIAL_PROXY_STATUS_OK = 0,
|
||||
@@ -352,6 +359,8 @@ enum SerialProxyStatus : uint32_t {
|
||||
SERIAL_PROXY_STATUS_ERROR = 2,
|
||||
SERIAL_PROXY_STATUS_TIMEOUT = 3,
|
||||
SERIAL_PROXY_STATUS_NOT_SUPPORTED = 4,
|
||||
SERIAL_PROXY_STATUS_PORT_IN_USE = 5,
|
||||
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6,
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -523,6 +532,7 @@ class SerialProxyInfo final : public ProtoMessage {
|
||||
public:
|
||||
StringRef name{};
|
||||
enums::SerialProxyPortType port_type{};
|
||||
uint32_t configured_line_states{0};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -3130,6 +3140,23 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
|
||||
};
|
||||
class ZWaveProxyRequestResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 151;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 4;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("z_wave_proxy_request_response"); }
|
||||
#endif
|
||||
enums::ZWaveProxyRequestType type{};
|
||||
enums::ZWaveProxyStatus status{};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
@@ -3314,12 +3341,13 @@ class SerialProxyGetModemPinsRequest final : public ProtoDecodableMessage {
|
||||
class SerialProxyGetModemPinsResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 143;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 8;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 10;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("serial_proxy_get_modem_pins_response"); }
|
||||
#endif
|
||||
uint32_t instance{0};
|
||||
uint32_t line_states{0};
|
||||
enums::SerialProxyStatus status{};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
|
||||
@@ -816,6 +816,18 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::ZWaveProxyStatus>(enums::ZWaveProxyStatus value) {
|
||||
switch (value) {
|
||||
case enums::ZWAVE_PROXY_STATUS_OK:
|
||||
return ESPHOME_PSTR("ZWAVE_PROXY_STATUS_OK");
|
||||
case enums::ZWAVE_PROXY_STATUS_IN_USE:
|
||||
return ESPHOME_PSTR("ZWAVE_PROXY_STATUS_IN_USE");
|
||||
case enums::ZWAVE_PROXY_STATUS_NOT_SUPPORTED:
|
||||
return ESPHOME_PSTR("ZWAVE_PROXY_STATUS_NOT_SUPPORTED");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
template<> const char *proto_enum_to_string<enums::SerialProxyParity>(enums::SerialProxyParity value) {
|
||||
@@ -838,6 +850,10 @@ template<> const char *proto_enum_to_string<enums::SerialProxyRequestType>(enums
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_FLUSH");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_CONFIGURE");
|
||||
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -854,6 +870,10 @@ template<> const char *proto_enum_to_string<enums::SerialProxyStatus>(enums::Ser
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_TIMEOUT");
|
||||
case enums::SERIAL_PROXY_STATUS_NOT_SUPPORTED:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_NOT_SUPPORTED");
|
||||
case enums::SERIAL_PROXY_STATUS_PORT_IN_USE:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_PORT_IN_USE");
|
||||
case enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT:
|
||||
return ESPHOME_PSTR("SERIAL_PROXY_STATUS_INVALID_ARGUMENT");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -914,6 +934,7 @@ const char *SerialProxyInfo::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyInfo"));
|
||||
dump_field(out, ESPHOME_PSTR("name"), this->name);
|
||||
dump_field(out, ESPHOME_PSTR("port_type"), static_cast<enums::SerialProxyPortType>(this->port_type));
|
||||
dump_field(out, ESPHOME_PSTR("configured_line_states"), this->configured_line_states);
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
@@ -2644,6 +2665,12 @@ const char *ZWaveProxyRequest::dump_to(DumpBuffer &out) const {
|
||||
dump_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *ZWaveProxyRequestResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("ZWaveProxyRequestResponse"));
|
||||
dump_field(out, ESPHOME_PSTR("type"), static_cast<enums::ZWaveProxyRequestType>(this->type));
|
||||
dump_field(out, ESPHOME_PSTR("status"), static_cast<enums::ZWaveProxyStatus>(this->status));
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const {
|
||||
@@ -2753,6 +2780,7 @@ const char *SerialProxyGetModemPinsResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyGetModemPinsResponse"));
|
||||
dump_field(out, ESPHOME_PSTR("instance"), this->instance);
|
||||
dump_field(out, ESPHOME_PSTR("line_states"), this->line_states);
|
||||
dump_field(out, ESPHOME_PSTR("status"), static_cast<enums::SerialProxyStatus>(this->status));
|
||||
return out.c_str();
|
||||
}
|
||||
const char *SerialProxyRequest::dump_to(DumpBuffer &out) const {
|
||||
|
||||
@@ -14,6 +14,7 @@ void KeyCollector::loop() {
|
||||
}
|
||||
|
||||
void KeyCollector::dump_config() {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_CONFIG
|
||||
ESP_LOGCONFIG(TAG, "Key Collector:");
|
||||
if (this->min_length_ > 0)
|
||||
ESP_LOGCONFIG(TAG, " min length: %d", this->min_length_);
|
||||
@@ -35,6 +36,7 @@ void KeyCollector::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " allowed keys '%s'", this->allowed_keys_.c_str());
|
||||
if (this->timeout_ > 0)
|
||||
ESP_LOGCONFIG(TAG, " entry timeout: %0.1f", this->timeout_ / 1000.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void KeyCollector::add_provider(key_provider::KeyProvider *provider) {
|
||||
|
||||
@@ -345,7 +345,7 @@ int MipiRgb::get_height() {
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
|
||||
[[maybe_unused]] static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
|
||||
if (pin == nullptr)
|
||||
return "None";
|
||||
pin->dump_summary(buffer.data(), buffer.size());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
static const char *const TAG = "online_image";
|
||||
static const char *const CONTENT_TYPE_HEADER_NAME = "content-type";
|
||||
@@ -62,30 +63,11 @@ void OnlineImage::update() {
|
||||
headers.push_back({IF_MODIFIED_SINCE_HEADER_NAME, this->last_modified_});
|
||||
}
|
||||
|
||||
// Add Accept header based on image format
|
||||
const char *accept_mime_type;
|
||||
runtime_image::ImageFormat format = this->get_format();
|
||||
switch (format) {
|
||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
||||
case runtime_image::BMP:
|
||||
accept_mime_type = "image/bmp,*/*;q=0.8";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
case runtime_image::JPEG:
|
||||
accept_mime_type = "image/jpeg,*/*;q=0.8";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
||||
case runtime_image::PNG:
|
||||
accept_mime_type = "image/png,*/*;q=0.8";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
accept_mime_type = "image/*,*/*;q=0.8";
|
||||
break;
|
||||
}
|
||||
headers.push_back({"Accept", accept_mime_type});
|
||||
// Accept: "<mime>,*/*;q=0.8"; 32 covers the longest MIME type plus the suffix
|
||||
char accept_header[32];
|
||||
snprintf(accept_header, sizeof(accept_header), "%s,*/*;q=0.8", runtime_image::get_mime_type_for_format(format));
|
||||
headers.push_back({"Accept", accept_header});
|
||||
|
||||
// User headers last so they can override any of the above
|
||||
for (auto &header : this->request_headers_) {
|
||||
@@ -122,32 +104,18 @@ void OnlineImage::update() {
|
||||
|
||||
if (format == runtime_image::AUTO) {
|
||||
// Try to auto-detect format from Content-Type header
|
||||
auto content_type_header = this->downloader_->get_response_header(CONTENT_TYPE_HEADER_NAME);
|
||||
const char *content_type = content_type_header.c_str();
|
||||
ESP_LOGV(TAG, "Content-Type: %s", content_type);
|
||||
// Includes aliases seen from real servers (older IIS, CDNs, S3)
|
||||
if (str_contains_ignore_case(content_type, "image/bmp") ||
|
||||
str_contains_ignore_case(content_type, "image/x-ms-bmp") ||
|
||||
str_contains_ignore_case(content_type, "image/x-bmp")) {
|
||||
format = runtime_image::BMP;
|
||||
} else if (str_contains_ignore_case(content_type, "image/jpeg") ||
|
||||
str_contains_ignore_case(content_type, "image/jpg")) {
|
||||
format = runtime_image::JPEG;
|
||||
} else if (str_contains_ignore_case(content_type, "image/png") ||
|
||||
str_contains_ignore_case(content_type, "image/x-png")) {
|
||||
format = runtime_image::PNG;
|
||||
} else if (str_contains_ignore_case(content_type, "image/")) {
|
||||
ESP_LOGW(TAG, "Unsupported image type: '%s'", content_type);
|
||||
this->end_connection_();
|
||||
this->download_error_callback_.call();
|
||||
return;
|
||||
auto content_type = this->downloader_->get_response_header(CONTENT_TYPE_HEADER_NAME);
|
||||
ESP_LOGV(TAG, "Content-Type: %s", content_type.c_str());
|
||||
auto mime_format = esphome::runtime_image::get_format_for_mime_type(content_type.c_str());
|
||||
if (mime_format.has_value()) {
|
||||
format = *mime_format;
|
||||
} else {
|
||||
// TODO: implement auto-detection in runtime_image by sniffing the first few bytes of the image data
|
||||
if (content_type_header.empty()) {
|
||||
ESP_LOGW(TAG, "Server sent no Content-Type header; cannot determine image format. Set `format:` explicitly");
|
||||
if (content_type.empty()) {
|
||||
ESP_LOGE(TAG, "Server sent no Content-Type header; cannot determine image format. Set `format:` explicitly");
|
||||
} else if (str_contains_ignore_case(content_type.c_str(), "image/")) {
|
||||
ESP_LOGE(TAG, "Image format '%s' not supported.", content_type.c_str());
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Could not determine image format from Content-Type: '%s'. Set `format:` explicitly",
|
||||
content_type);
|
||||
ESP_LOGE(TAG, "Server did not return an image (Content-Type: '%s')", content_type.c_str());
|
||||
}
|
||||
this->end_connection_();
|
||||
this->download_error_callback_.call();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "image_format.h"
|
||||
#include "image_decoder.h"
|
||||
|
||||
namespace esphome::runtime_image {
|
||||
|
||||
struct MimeLookup {
|
||||
const char *mime_type;
|
||||
ImageFormat format;
|
||||
};
|
||||
|
||||
// The first entry per format is its canonical MIME type; the rest are aliases
|
||||
// seen from real servers (older IIS, CDNs, S3)
|
||||
static constexpr MimeLookup MIME_LOOKUP_TABLE[] = {
|
||||
#ifdef USE_RUNTIME_IMAGE_BMP
|
||||
{"image/bmp", ImageFormat::BMP}, {"image/x-ms-bmp", ImageFormat::BMP}, {"image/x-bmp", ImageFormat::BMP},
|
||||
#endif
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
{"image/jpeg", ImageFormat::JPEG}, {"image/jpg", ImageFormat::JPEG},
|
||||
#endif
|
||||
#ifdef USE_RUNTIME_IMAGE_PNG
|
||||
{"image/png", ImageFormat::PNG}, {"image/x-png", ImageFormat::PNG},
|
||||
#endif
|
||||
};
|
||||
|
||||
const char *get_mime_type_for_format(ImageFormat format) {
|
||||
for (const auto &entry : MIME_LOOKUP_TABLE) {
|
||||
if (entry.format == format) {
|
||||
return entry.mime_type;
|
||||
}
|
||||
}
|
||||
return "image/*"; // AUTO or compiled-out format
|
||||
}
|
||||
|
||||
std::optional<ImageFormat> get_format_for_mime_type(const char *mime_type) {
|
||||
for (const auto &entry : MIME_LOOKUP_TABLE) {
|
||||
if (str_contains_ignore_case(mime_type, entry.mime_type)) {
|
||||
return entry.format;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace esphome::runtime_image
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace esphome::runtime_image {
|
||||
|
||||
/**
|
||||
@@ -17,4 +19,9 @@ enum ImageFormat {
|
||||
BMP,
|
||||
};
|
||||
|
||||
/// Canonical MIME type for a format; "image/*" for AUTO/unknown
|
||||
const char *get_mime_type_for_format(ImageFormat format);
|
||||
/// Case-insensitive substring match of known media types; nullopt if none found
|
||||
std::optional<ImageFormat> get_format_for_mime_type(const char *mime_type);
|
||||
|
||||
} // namespace esphome::runtime_image
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "runtime_image.h"
|
||||
#include "image_decoder.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
@@ -89,12 +89,12 @@ void SerialProxy::dump_config() {
|
||||
this->dtr_pin_ != nullptr ? "configured" : "not configured");
|
||||
}
|
||||
|
||||
void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size) {
|
||||
SerialProxyResult SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control,
|
||||
uint8_t parity, uint8_t stop_bits, uint8_t data_size) {
|
||||
#ifdef USE_API
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring configure request from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG,
|
||||
@@ -105,25 +105,29 @@ void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrat
|
||||
auto *uart_comp = this->parent_;
|
||||
if (uart_comp == nullptr) {
|
||||
ESP_LOGE(TAG, "UART component not available");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_ERROR;
|
||||
}
|
||||
|
||||
// Validate all parameters before applying any (values come from a remote client)
|
||||
if (baudrate == 0) {
|
||||
ESP_LOGW(TAG, "Invalid baud rate: 0");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (stop_bits < 1 || stop_bits > 2) {
|
||||
ESP_LOGW(TAG, "Invalid stop bits: %u (must be 1 or 2)", stop_bits);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (data_size < 5 || data_size > 8) {
|
||||
ESP_LOGW(TAG, "Invalid data bits: %u (must be 5-8)", data_size);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (parity > 2) {
|
||||
ESP_LOGW(TAG, "Invalid parity: %u (must be 0-2)", parity);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_INVALID_ARGUMENT;
|
||||
}
|
||||
if (flow_control) {
|
||||
ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// Apply validated parameters
|
||||
@@ -143,10 +147,7 @@ void SerialProxy::configure(api::APIConnection *api_connection, uint32_t baudrat
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
uart_comp->load_settings(true);
|
||||
#endif
|
||||
|
||||
if (flow_control) {
|
||||
ESP_LOGW(TAG, "Hardware flow control requested but is not yet supported");
|
||||
}
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
|
||||
void SerialProxy::write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len) {
|
||||
@@ -163,13 +164,20 @@ void SerialProxy::write_from_client(api::APIConnection *api_connection, const ui
|
||||
this->write_array(data, len);
|
||||
}
|
||||
|
||||
void SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {
|
||||
SerialProxyResult SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {
|
||||
#ifdef USE_API
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring modem pin request from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
#endif
|
||||
// Asserting a pin that is not configured must fail so the client learns the signal never
|
||||
// reached the wire; deasserting an absent pin is harmless and stays allowed. Clients can
|
||||
// avoid this by masking against SerialProxyInfo.configured_line_states.
|
||||
if ((line_states & ~this->get_configured_modem_pins()) != 0) {
|
||||
ESP_LOGW(TAG, "Requested modem pin not configured on serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
const bool rts = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_RTS) != 0;
|
||||
const bool dtr = (line_states & SERIAL_PROXY_LINE_STATE_FLAG_DTR) != 0;
|
||||
ESP_LOGV(TAG, "Setting modem pins [%" PRIu32 "]: RTS=%s, DTR=%s", this->instance_index_, ONOFF(rts), ONOFF(dtr));
|
||||
@@ -182,6 +190,7 @@ void SerialProxy::set_modem_pins(api::APIConnection *api_connection, uint32_t li
|
||||
this->dtr_state_ = dtr;
|
||||
this->dtr_pin_->digital_write(dtr);
|
||||
}
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
|
||||
uint32_t SerialProxy::get_modem_pins() const {
|
||||
@@ -189,9 +198,26 @@ uint32_t SerialProxy::get_modem_pins() const {
|
||||
(this->dtr_state_ ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_DTR) : 0u);
|
||||
}
|
||||
|
||||
uart::UARTFlushResult SerialProxy::flush_port() {
|
||||
SerialProxyResult SerialProxy::flush_port(api::APIConnection *api_connection) {
|
||||
#ifdef USE_API
|
||||
// Flushing stalls the port, so it gets the same ownership check as writes
|
||||
if (this->port_claimed_by_other_(api_connection)) {
|
||||
ESP_LOGW(TAG, "Ignoring flush from client without port access [%" PRIu32 "]", this->instance_index_);
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Flushing serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return this->flush();
|
||||
switch (this->flush()) {
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_ASSUMED_SUCCESS;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_TIMEOUT;
|
||||
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_ERROR;
|
||||
}
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_ERROR; // Unreachable; all enum values handled above
|
||||
}
|
||||
|
||||
#ifdef USE_API
|
||||
@@ -200,12 +226,13 @@ bool SerialProxy::port_claimed_by_other_(api::APIConnection *api_connection) con
|
||||
this->api_connection_->is_connection_setup();
|
||||
}
|
||||
|
||||
void SerialProxy::serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type) {
|
||||
SerialProxyResult SerialProxy::serial_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::SerialProxyRequestType type) {
|
||||
switch (type) {
|
||||
case api::enums::SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
if (this->api_connection_ == api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is already subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// A living subscriber keeps exclusive access. Its connection may be dead without
|
||||
@@ -213,26 +240,27 @@ void SerialProxy::serial_proxy_request(api::APIConnection *api_connection, api::
|
||||
// in that case let the new client take over instead of locking it out.
|
||||
if (this->api_connection_->is_connection_setup()) {
|
||||
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_PORT_IN_USE;
|
||||
}
|
||||
ESP_LOGW(TAG, "Previous subscriber disconnected; taking over subscription");
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
this->enable_loop();
|
||||
ESP_LOGV(TAG, "API connection subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
case api::enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
// Unsubscribe is idempotent: not being subscribed is not an error
|
||||
if (this->api_connection_ != api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is not subscribed to serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
return;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
this->api_connection_ = nullptr;
|
||||
this->disable_loop();
|
||||
ESP_LOGV(TAG, "API connection unsubscribed from serial proxy [%" PRIu32 "]", this->instance_index_);
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(type));
|
||||
break;
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,17 @@ enum SerialProxyLineStateFlag : uint32_t {
|
||||
SERIAL_PROXY_LINE_STATE_FLAG_DTR = 1 << 1, ///< DTR (Data Terminal Ready)
|
||||
};
|
||||
|
||||
/// Result of a client-initiated operation; mapped to api::enums::SerialProxyStatus by the API layer
|
||||
enum class SerialProxyResult : uint8_t {
|
||||
SERIAL_PROXY_RESULT_OK, ///< Operation completed or request accepted
|
||||
SERIAL_PROXY_RESULT_ASSUMED_SUCCESS, ///< Platform cannot confirm TX drain; success assumed
|
||||
SERIAL_PROXY_RESULT_PORT_IN_USE, ///< Denied: another live client holds the port
|
||||
SERIAL_PROXY_RESULT_INVALID_ARGUMENT, ///< A parameter value is out of range
|
||||
SERIAL_PROXY_RESULT_ERROR, ///< Driver or hardware error
|
||||
SERIAL_PROXY_RESULT_TIMEOUT, ///< Timed out before TX completed
|
||||
SERIAL_PROXY_RESULT_NOT_SUPPORTED, ///< Requested feature is not available on this instance
|
||||
};
|
||||
|
||||
/// Maximum bytes to read from UART in a single loop iteration
|
||||
inline constexpr size_t SERIAL_PROXY_MAX_READ_SIZE = 256;
|
||||
|
||||
@@ -73,14 +84,14 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
/// @param parity Parity setting (0=none, 1=even, 2=odd)
|
||||
/// @param stop_bits Number of stop bits (1 or 2)
|
||||
/// @param data_size Number of data bits (5-8)
|
||||
void configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size);
|
||||
SerialProxyResult configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size);
|
||||
|
||||
/// Get the currently subscribed API connection (nullptr if none)
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
|
||||
/// Handle a subscribe/unsubscribe request from an API client
|
||||
void serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type);
|
||||
SerialProxyResult serial_proxy_request(api::APIConnection *api_connection, api::enums::SerialProxyRequestType type);
|
||||
|
||||
/// Write data received from an API client to the serial device
|
||||
/// @param api_connection The API connection sending the data
|
||||
@@ -89,13 +100,20 @@ class SerialProxy final : public uart::UARTDevice, public Component {
|
||||
void write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len);
|
||||
|
||||
/// Set modem pin states from a bitmask of SerialProxyLineStateFlag values
|
||||
void set_modem_pins(api::APIConnection *api_connection, uint32_t line_states);
|
||||
SerialProxyResult set_modem_pins(api::APIConnection *api_connection, uint32_t line_states);
|
||||
|
||||
/// Get current modem pin states as a bitmask of SerialProxyLineStateFlag values
|
||||
uint32_t get_modem_pins() const;
|
||||
|
||||
/// Get the modem pins this instance can drive as a bitmask of SerialProxyLineStateFlag values
|
||||
uint32_t get_configured_modem_pins() const {
|
||||
return (this->rts_pin_ != nullptr ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_RTS) : 0u) |
|
||||
(this->dtr_pin_ != nullptr ? static_cast<uint32_t>(SERIAL_PROXY_LINE_STATE_FLAG_DTR) : 0u);
|
||||
}
|
||||
|
||||
/// Flush the serial port (block until all TX data is sent)
|
||||
uart::UARTFlushResult flush_port();
|
||||
/// @param api_connection The API connection requesting the flush
|
||||
SerialProxyResult flush_port(api::APIConnection *api_connection);
|
||||
|
||||
/// Set the RTS GPIO pin (from YAML configuration)
|
||||
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
|
||||
|
||||
@@ -194,12 +194,13 @@ void ZWaveProxy::api_connection_authenticated(api::APIConnection *conn) {
|
||||
}
|
||||
}
|
||||
|
||||
void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) {
|
||||
api::enums::ZWaveProxyStatus ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::ZWaveProxyRequestType type) {
|
||||
switch (type) {
|
||||
case api::enums::ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE:
|
||||
if (this->api_connection_ == api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is already subscribed");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
if (this->api_connection_ != nullptr) {
|
||||
// A living subscriber keeps exclusive access. Its connection may be dead without
|
||||
@@ -207,25 +208,26 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
|
||||
// in that case let the new client take over instead of locking it out.
|
||||
if (this->api_connection_->is_connection_setup()) {
|
||||
ESP_LOGE(TAG, "Only one API subscription is allowed at a time");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_IN_USE;
|
||||
}
|
||||
ESP_LOGW(TAG, "Previous subscriber disconnected; taking over subscription");
|
||||
}
|
||||
this->api_connection_ = api_connection;
|
||||
ESP_LOGV(TAG, "API connection is now subscribed");
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
|
||||
case api::enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
|
||||
// Unsubscribe is idempotent: not being subscribed is not an error
|
||||
if (this->api_connection_ != api_connection) {
|
||||
ESP_LOGV(TAG, "API connection is not subscribed");
|
||||
return;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
this->api_connection_ = nullptr;
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown request type: %" PRIu32, static_cast<uint32_t>(type));
|
||||
break;
|
||||
return api::enums::ZWAVE_PROXY_STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ class ZWaveProxy final : public uart::UARTDevice, public Component {
|
||||
bool can_proceed() override;
|
||||
|
||||
void api_connection_authenticated(api::APIConnection *conn);
|
||||
void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type);
|
||||
api::enums::ZWaveProxyStatus zwave_proxy_request(api::APIConnection *api_connection,
|
||||
api::enums::ZWaveProxyRequestType type);
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
|
||||
uint32_t get_feature_flags() const { return ZWaveProxyFeature::FEATURE_ZWAVE_PROXY_ENABLED; }
|
||||
|
||||
+17
-21
@@ -398,27 +398,23 @@ def run_compile(config, verbose: bool) -> int:
|
||||
return rc
|
||||
_LOGGER.info("Regenerating CMakeLists.txt with discovered components...")
|
||||
write_project(minimal=False)
|
||||
# Restamp the reference file has_outdated_files() compares against.
|
||||
# A reconfigure that only changes properties or plain variables
|
||||
# (sdkconfig options, the exclusion set) does not rewrite
|
||||
# CMakeCache.txt, so without this the watched inputs stay newer
|
||||
# forever and every subsequent build repeats the discovery pass.
|
||||
# Done after the full write so an interrupt cannot leave a minimal
|
||||
# CMakeLists behind that is already marked fresh.
|
||||
cmakecache = CORE.relative_build_path("build/CMakeCache.txt")
|
||||
if cmakecache.is_file():
|
||||
os.utime(cmakecache)
|
||||
if CORE.testing_mode:
|
||||
# Reconfigure again so cmake is up to date with the full
|
||||
# component list before the build's idf.py invocation runs --
|
||||
# idf.py build would otherwise re-run cmake and regenerate
|
||||
# memory.ld, wiping the DRAM/IRAM patches applied below.
|
||||
# Outside testing mode ninja's own configure-time dep on
|
||||
# CMakeLists.txt handles the re-run as part of the build step.
|
||||
rc = run_reconfigure()
|
||||
if rc != 0:
|
||||
_LOGGER.error("Reconfigure with discovered components failed")
|
||||
return rc
|
||||
# Explicit reconfigure: ninja only re-runs cmake when CMakeLists.txt
|
||||
# is strictly newer than build.ninja, which fails on coarse-mtime
|
||||
# filesystems (#18682). Also keeps idf.py from regenerating memory.ld
|
||||
# in testing mode.
|
||||
rc = run_reconfigure()
|
||||
if rc != 0:
|
||||
_LOGGER.error("Reconfigure with discovered components failed")
|
||||
return rc
|
||||
# cmake does not rewrite CMakeCache.txt when only properties change,
|
||||
# so restamp it or every build repeats discovery. Only after success,
|
||||
# or a failed reconfigure would be marked fresh. build.ninja is
|
||||
# restamped too so the cache is not newer and ninja does not
|
||||
# re-run cmake.
|
||||
for name in ("build/CMakeCache.txt", "build/build.ninja"):
|
||||
path = CORE.relative_build_path(name)
|
||||
if path.is_file():
|
||||
os.utime(path)
|
||||
|
||||
# In testing mode, generate the linker script first, patch DRAM/IRAM sizes,
|
||||
# then build. memory.ld is regenerated by ninja during the build phase,
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ pyserial==3.5
|
||||
platformio==6.1.19
|
||||
esptool==5.3.1
|
||||
click==8.3.3
|
||||
aioesphomeapi==46.0.0
|
||||
aioesphomeapi==46.1.0
|
||||
aiohappyeyeballs==2.7.1 # Happy Eyeballs for requests downloads; already pulled in by aioesphomeapi
|
||||
zeroconf==0.150.0
|
||||
puremagic==2.2.0
|
||||
|
||||
@@ -13,17 +13,18 @@ namespace api {
|
||||
class APIConnection;
|
||||
} // namespace api
|
||||
|
||||
namespace uart {
|
||||
enum class UARTFlushResult : uint8_t {
|
||||
UART_FLUSH_RESULT_SUCCESS,
|
||||
UART_FLUSH_RESULT_ASSUMED_SUCCESS,
|
||||
UART_FLUSH_RESULT_TIMEOUT,
|
||||
UART_FLUSH_RESULT_FAILED,
|
||||
};
|
||||
} // namespace uart
|
||||
|
||||
namespace serial_proxy {
|
||||
|
||||
enum class SerialProxyResult : uint8_t {
|
||||
SERIAL_PROXY_RESULT_OK,
|
||||
SERIAL_PROXY_RESULT_ASSUMED_SUCCESS,
|
||||
SERIAL_PROXY_RESULT_PORT_IN_USE,
|
||||
SERIAL_PROXY_RESULT_INVALID_ARGUMENT,
|
||||
SERIAL_PROXY_RESULT_ERROR,
|
||||
SERIAL_PROXY_RESULT_TIMEOUT,
|
||||
SERIAL_PROXY_RESULT_NOT_SUPPORTED,
|
||||
};
|
||||
|
||||
class SerialProxy {
|
||||
public:
|
||||
void set_instance_index(uint32_t index) { this->instance_index_ = index; }
|
||||
@@ -31,13 +32,20 @@ class SerialProxy {
|
||||
const char *get_name() const { return ""; }
|
||||
api::enums::SerialProxyPortType get_port_type() const { return {}; }
|
||||
api::APIConnection *get_api_connection() { return nullptr; }
|
||||
void serial_proxy_request(api::APIConnection *conn, api::enums::SerialProxyRequestType type) {}
|
||||
void configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint32_t stop_bits, uint32_t data_size) {}
|
||||
SerialProxyResult serial_proxy_request(api::APIConnection *conn, api::enums::SerialProxyRequestType type) {
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
SerialProxyResult configure(api::APIConnection *api_connection, uint32_t baudrate, bool flow_control, uint8_t parity,
|
||||
uint8_t stop_bits, uint8_t data_size) {
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
void write_from_client(api::APIConnection *api_connection, const uint8_t *data, size_t len) {}
|
||||
void set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {}
|
||||
SerialProxyResult set_modem_pins(api::APIConnection *api_connection, uint32_t line_states) {
|
||||
return SerialProxyResult::SERIAL_PROXY_RESULT_OK;
|
||||
}
|
||||
uint32_t get_modem_pins() const { return 0; }
|
||||
uart::UARTFlushResult flush_port() { return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; }
|
||||
uint32_t get_configured_modem_pins() const { return 0; }
|
||||
SerialProxyResult flush_port(api::APIConnection *api_connection) { return SerialProxyResult::SERIAL_PROXY_RESULT_OK; }
|
||||
|
||||
protected:
|
||||
uint32_t instance_index_{0};
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace zwave_proxy {
|
||||
class ZWaveProxy {
|
||||
public:
|
||||
api::APIConnection *get_api_connection() { return nullptr; }
|
||||
void zwave_proxy_request(api::APIConnection *conn, api::enums::ZWaveProxyRequestType type) {}
|
||||
api::enums::ZWaveProxyStatus zwave_proxy_request(api::APIConnection *conn, api::enums::ZWaveProxyRequestType type) {
|
||||
return api::enums::ZWAVE_PROXY_STATUS_OK;
|
||||
}
|
||||
void send_frame(api::APIConnection *api_connection, const uint8_t *data, size_t length) {}
|
||||
void api_connection_authenticated(api::APIConnection *conn) {}
|
||||
uint32_t get_feature_flags() const { return 0; }
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "esphome/components/runtime_image/runtime_image.h"
|
||||
|
||||
namespace esphome::runtime_image::testing {
|
||||
|
||||
TEST(RuntimeImageMime, FormatForKnownMimeTypes) {
|
||||
EXPECT_EQ(get_format_for_mime_type("image/bmp"), BMP);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/x-ms-bmp"), BMP);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/x-bmp"), BMP);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/png"), PNG);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/x-png"), PNG);
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
EXPECT_EQ(get_format_for_mime_type("image/jpeg"), JPEG);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/jpg"), JPEG);
|
||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
||||
}
|
||||
|
||||
TEST(RuntimeImageMime, FormatMatchingIsCaseInsensitive) {
|
||||
EXPECT_EQ(get_format_for_mime_type("Image/PNG"), PNG);
|
||||
EXPECT_EQ(get_format_for_mime_type("IMAGE/BMP"), BMP);
|
||||
}
|
||||
|
||||
TEST(RuntimeImageMime, FormatMatchesContentTypeWithParameters) {
|
||||
// Content-Type headers may carry parameters after the media type
|
||||
EXPECT_EQ(get_format_for_mime_type("image/png; charset=binary"), PNG);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/bmp;name=\"a.bmp\""), BMP);
|
||||
}
|
||||
|
||||
TEST(RuntimeImageMime, UnknownMimeTypeHasNoFormat) {
|
||||
EXPECT_EQ(get_format_for_mime_type("text/html"), std::nullopt);
|
||||
EXPECT_EQ(get_format_for_mime_type("application/octet-stream"), std::nullopt);
|
||||
EXPECT_EQ(get_format_for_mime_type("image/*"), std::nullopt);
|
||||
EXPECT_EQ(get_format_for_mime_type(""), std::nullopt);
|
||||
EXPECT_EQ(get_format_for_mime_type(nullptr), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(RuntimeImageMime, MimeTypeForFormatRoundTrip) {
|
||||
EXPECT_STREQ(get_mime_type_for_format(BMP), "image/bmp");
|
||||
EXPECT_STREQ(get_mime_type_for_format(PNG), "image/png");
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
EXPECT_STREQ(get_mime_type_for_format(JPEG), "image/jpeg");
|
||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
||||
// AUTO has no single MIME type and falls back to the wildcard
|
||||
EXPECT_STREQ(get_mime_type_for_format(AUTO), "image/*");
|
||||
|
||||
// Every decodable format must resolve back to itself through its MIME type
|
||||
for (ImageFormat format : {
|
||||
BMP,
|
||||
PNG,
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
JPEG,
|
||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
||||
}) {
|
||||
EXPECT_EQ(get_format_for_mime_type(get_mime_type_for_format(format)), format) << format;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::runtime_image::testing
|
||||
@@ -263,6 +263,17 @@ def test_device_capabilities_response_has_id_150() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_z_wave_proxy_request_response_has_id_151() -> None:
|
||||
body = _extract_proto_message(PROTO_TEXT, "ZWaveProxyRequestResponse")
|
||||
match = re.search(r"option \(id\) = (\d+);", body)
|
||||
assert match is not None, "ZWaveProxyRequestResponse is missing `option (id)`"
|
||||
assert int(match.group(1)) == 151, (
|
||||
f"ZWaveProxyRequestResponse has id {match.group(1)}, expected 151. "
|
||||
"Message ids are part of the wire protocol and must not change once "
|
||||
"assigned."
|
||||
)
|
||||
|
||||
|
||||
def test_superseded_fields_are_not_marked_deprecated_in_proto() -> None:
|
||||
"""The six superseded fields must not carry `[deprecated = true]` in
|
||||
api.proto, or the generator drops them and old clients stop receiving
|
||||
|
||||
@@ -299,10 +299,13 @@ def test_run_compile_restamps_cmakecache_after_discovery(setup_core: Path) -> No
|
||||
_setup_build(setup_core)
|
||||
config = {CONF_ESPHOME: {}}
|
||||
cmakecache = CORE.relative_build_path("build/CMakeCache.txt")
|
||||
build_ninja = CORE.relative_build_path("build/build.ninja")
|
||||
cmakecache.parent.mkdir(parents=True, exist_ok=True)
|
||||
cmakecache.write_text("")
|
||||
build_ninja.write_text("")
|
||||
old = cmakecache.stat().st_mtime - 100
|
||||
os.utime(cmakecache, (old, old))
|
||||
os.utime(build_ninja, (old, old))
|
||||
|
||||
with (
|
||||
patch.object(toolchain, "need_reconfigure", return_value=True),
|
||||
@@ -314,6 +317,8 @@ def test_run_compile_restamps_cmakecache_after_discovery(setup_core: Path) -> No
|
||||
assert toolchain.run_compile(config, verbose=False) == 0
|
||||
|
||||
assert cmakecache.stat().st_mtime > old
|
||||
# build.ninja must not be older than the cache or ninja re-runs cmake
|
||||
assert build_ninja.stat().st_mtime >= cmakecache.stat().st_mtime
|
||||
|
||||
|
||||
def test_run_compile_discovery_without_cmakecache(setup_core: Path) -> None:
|
||||
@@ -334,6 +339,50 @@ def test_run_compile_discovery_without_cmakecache(setup_core: Path) -> None:
|
||||
assert not CORE.relative_build_path("build/CMakeCache.txt").exists()
|
||||
|
||||
|
||||
def test_run_compile_reconfigures_after_full_write_outside_testing_mode(
|
||||
setup_core: Path,
|
||||
) -> None:
|
||||
"""The full CMakeLists write is followed by a reconfigure (#18682); a
|
||||
failure there stops the build and leaves the cache unstamped."""
|
||||
_setup_build(setup_core)
|
||||
config = {CONF_ESPHOME: {}}
|
||||
cmakecache = CORE.relative_build_path("build/CMakeCache.txt")
|
||||
cmakecache.parent.mkdir(parents=True, exist_ok=True)
|
||||
cmakecache.write_text("")
|
||||
old = cmakecache.stat().st_mtime - 100
|
||||
os.utime(cmakecache, (old, old))
|
||||
calls: list[tuple] = []
|
||||
reconfigures = 0
|
||||
|
||||
def record_write(minimal: bool = False) -> None:
|
||||
calls.append(("write_project", minimal))
|
||||
|
||||
def record_reconfigure() -> int:
|
||||
nonlocal reconfigures
|
||||
reconfigures += 1
|
||||
calls.append(("run_reconfigure",))
|
||||
return 1 if reconfigures == 2 else 0
|
||||
|
||||
with (
|
||||
patch.object(toolchain, "need_reconfigure", return_value=True),
|
||||
patch("esphome.build_gen.espidf.write_project", side_effect=record_write),
|
||||
patch.object(toolchain, "run_reconfigure", side_effect=record_reconfigure),
|
||||
patch.object(toolchain, "run_idf_py", return_value=0) as mock_build,
|
||||
patch.object(toolchain, "print_summary"),
|
||||
):
|
||||
assert not CORE.testing_mode
|
||||
assert toolchain.run_compile(config, verbose=False) == 1
|
||||
|
||||
assert calls == [
|
||||
("write_project", True),
|
||||
("run_reconfigure",),
|
||||
("write_project", False),
|
||||
("run_reconfigure",),
|
||||
]
|
||||
mock_build.assert_not_called()
|
||||
assert cmakecache.stat().st_mtime == old
|
||||
|
||||
|
||||
def test_run_compile_passes_compile_process_limit(setup_core: Path) -> None:
|
||||
"""compile_process_limit is forwarded to run_idf_py as the job limit."""
|
||||
_setup_build(setup_core)
|
||||
|
||||
Reference in New Issue
Block a user