mirror of
https://github.com/esphome/esphome.git
synced 2026-08-17 10:52:56 +08:00
Merge pull request #17532 from esphome/bump-2026.7.0b2
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Check import esphome.__main__ time (push) Has been cancelled
CI / Test downstream esphome/device-builder (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.12) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.12) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.12) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (${{ matrix.bucket.name }}) (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 C6 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 P4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 S3 (push) Has been cancelled
CI / Test components batch (${{ matrix.batch.components }}) (push) Has been cancelled
CI / Test esp32 components with PlatformIO (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
CI - GitHub Scripts / Test auto-label-pr scripts (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Check import esphome.__main__ time (push) Has been cancelled
CI / Test downstream esphome/device-builder (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.12) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.12) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.12) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (${{ matrix.bucket.name }}) (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 C6 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 P4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 S3 (push) Has been cancelled
CI / Test components batch (${{ matrix.batch.components }}) (push) Has been cancelled
CI / Test esp32 components with PlatformIO (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
CI - GitHub Scripts / Test auto-label-pr scripts (push) Has been cancelled
2026.7.0b2
This commit is contained in:
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2026.7.0b1
|
||||
PROJECT_NUMBER = 2026.7.0b2
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ RUN \
|
||||
-r /requirements.txt
|
||||
|
||||
# Install the ESPHome Device Builder dashboard.
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.3.1
|
||||
RUN uv pip install --no-cache-dir esphome-device-builder==1.4.3
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
|
||||
@@ -488,8 +488,11 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
|
||||
else:
|
||||
# No key provided, but encryption desired
|
||||
# This will allow a plaintext client to provide a noise key,
|
||||
# send it to the device, and then switch to noise.
|
||||
# Until a key is set, the device accepts both Noise connections
|
||||
# using the well-known all-zeros PSK (preferred: the key travels
|
||||
# encrypted, protecting against passive sniffing) and plaintext
|
||||
# connections (deprecated, remove after 2027.2.0) so a client can
|
||||
# provide a noise key and the device then switches to noise only.
|
||||
# The key will be saved in flash and used for future connections
|
||||
# and plaintext disabled. Only a factory reset can remove it.
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
@@ -310,6 +310,11 @@ message DeviceInfoResponse {
|
||||
|
||||
// Serial proxy instance metadata
|
||||
repeated SerialProxyInfo serial_proxies = 25 [(field_ifdef) = "USE_SERIAL_PROXY", (fixed_array_size_define) = "SERIAL_PROXY_COUNT"];
|
||||
|
||||
// Device is unprovisioned and accepts Noise handshakes with the well-known
|
||||
// all-zeros PSK, so the api encryption key can be provisioned without being
|
||||
// sent in plaintext (protects against passive sniffing, not active MITM)
|
||||
bool api_encryption_provisionable = 26 [(field_ifdef) = "USE_API_NOISE"];
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
||||
@@ -198,6 +198,29 @@ APIConnection::~APIConnection() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
void APIConnection::upgrade_helper_to_noise_() {
|
||||
// The client opened with a Noise hello while this device has no encryption
|
||||
// key set. Replace the plaintext helper with a Noise helper so the key can
|
||||
// be provisioned over an encrypted channel: the noise context PSK is all
|
||||
// zeros when unprovisioned, and NNpsk0 still runs a fresh ephemeral X25519
|
||||
// exchange, so a passive listener cannot read the session. A publicly known
|
||||
// PSK authenticates nobody; this protects against sniffing only.
|
||||
auto *plaintext = static_cast<APIPlaintextFrameHelper *>(this->helper_.get());
|
||||
uint8_t header[3];
|
||||
uint8_t header_len = plaintext->get_consumed_header(header);
|
||||
auto *noise = new APINoiseFrameHelper(plaintext->release_socket_for_switch(), this->parent_->get_noise_ctx());
|
||||
// Carry over the peername-based client name (Hello has not arrived yet)
|
||||
const char *name = plaintext->get_client_name();
|
||||
noise->set_client_name(name, strlen(name));
|
||||
this->helper_.reset(noise); // destroys the plaintext helper
|
||||
APIError err = noise->init_from_handoff(header, header_len);
|
||||
if (err != APIError::OK) {
|
||||
this->fatal_error_with_log_(LOG_STR("Noise handoff failed"), err);
|
||||
}
|
||||
}
|
||||
#endif // USE_API_NOISE && USE_API_PLAINTEXT
|
||||
|
||||
void APIConnection::destroy_active_iterator_() {
|
||||
switch (this->active_iterator_) {
|
||||
case ActiveIterator::LIST_ENTITIES:
|
||||
@@ -256,6 +279,15 @@ void APIConnection::loop() {
|
||||
// No more data available
|
||||
break;
|
||||
} else if (err != APIError::OK) {
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
// Checked inside the error branch to keep the hot err == OK path
|
||||
// free of it; this can only fire on the first bytes of a plaintext
|
||||
// helper on an unprovisioned device
|
||||
if (err == APIError::PROTOCOL_SWITCH_TO_NOISE) {
|
||||
this->upgrade_helper_to_noise_();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
|
||||
return;
|
||||
} else {
|
||||
@@ -1351,7 +1383,7 @@ void APIConnection::on_voice_assistant_set_configuration(const VoiceAssistantSet
|
||||
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void APIConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) {
|
||||
zwave_proxy::global_zwave_proxy->send_frame(msg.data, msg.data_len);
|
||||
zwave_proxy::global_zwave_proxy->send_frame(this, msg.data, msg.data_len);
|
||||
}
|
||||
|
||||
void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||
@@ -1860,6 +1892,12 @@ bool APIConnection::send_device_info_response_() {
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
resp.api_encryption_supported = true;
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
// No key from YAML: while no key is set, the key can be provisioned over a
|
||||
// zero-PSK Noise connection. Gated on the YAML define (not the plaintext
|
||||
// one) so this advertisement survives the plaintext removal in 2027.2.0.
|
||||
resp.api_encryption_provisionable = !this->parent_->get_noise_ctx().has_psk();
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
size_t device_index = 0;
|
||||
@@ -2037,10 +2075,21 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio
|
||||
}
|
||||
} else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) {
|
||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||
} else if (APINoiseContext::is_all_zeros(psk)) {
|
||||
// Accepting the reserved provisioning PSK would report success without
|
||||
// enabling encryption (or silently clear an existing key)
|
||||
ESP_LOGW(TAG, "Rejecting all-zero encryption key");
|
||||
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||
} else {
|
||||
resp.success = true;
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
if (this->helper_->frame_footer_size() == 0) {
|
||||
// Plaintext transport has no frame footer; Noise always has the MAC footer.
|
||||
// Remove after 2027.2.0 together with plaintext support on keyless devices.
|
||||
ESP_LOGW(TAG, "Key received over plaintext; deprecated, will be removed in 2027.2.0");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return this->send_message(resp);
|
||||
|
||||
@@ -626,6 +626,11 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void destroy_active_iterator_();
|
||||
void begin_iterator_(ActiveIterator type);
|
||||
void finalize_iterator_sync_();
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
// Swap the plaintext helper for a Noise helper after the client opened
|
||||
// with a Noise hello on an unprovisioned device (zero-PSK provisioning).
|
||||
void upgrade_helper_to_noise_();
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||
#endif
|
||||
|
||||
@@ -97,6 +97,8 @@ const LogString *api_error_to_logstr(APIError err) {
|
||||
return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
|
||||
}
|
||||
#endif
|
||||
// PROTOCOL_SWITCH_TO_NOISE is intercepted in APIConnection::loop() before
|
||||
// any logging can happen, so it intentionally has no entry here.
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,11 @@ enum class APIError : uint16_t {
|
||||
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
||||
BAD_HANDSHAKE_ERROR_BYTE = 1021,
|
||||
#endif
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
// Not an error: an unprovisioned device received a Noise client hello on a
|
||||
// plaintext connection; the caller must hand the socket off to a Noise helper.
|
||||
PROTOCOL_SWITCH_TO_NOISE = 1023,
|
||||
#endif
|
||||
};
|
||||
|
||||
const LogString *api_error_to_logstr(APIError err);
|
||||
@@ -200,6 +205,12 @@ class APIFrameHelper {
|
||||
// or track that they stopped early and retry without this check.
|
||||
// See Socket::ready() for details.
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
// Move the socket out of this helper so a replacement helper can take it
|
||||
// over (plaintext to Noise handoff on unprovisioned devices). The drained
|
||||
// helper must be destroyed right after.
|
||||
std::unique_ptr<socket::Socket> release_socket_for_switch() { return std::move(this->socket_); }
|
||||
#endif
|
||||
// Release excess memory from internal buffers after initial sync
|
||||
void release_buffers() {
|
||||
// rx_buf_: Safe to clear only if no partial read in progress.
|
||||
|
||||
@@ -109,6 +109,40 @@ APIError APINoiseFrameHelper::init() {
|
||||
state_ = State::CLIENT_HELLO;
|
||||
return APIError::OK;
|
||||
}
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
APIError APINoiseFrameHelper::init_from_handoff(const uint8_t *header, uint8_t header_len) {
|
||||
APIError err = this->init();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
// Seed the header bytes the plaintext helper consumed before detecting the
|
||||
// Noise indicator; try_read_frame_ resumes from rx_header_buf_len_.
|
||||
std::memcpy(this->rx_header_buf_, header, header_len);
|
||||
this->rx_header_buf_len_ = header_len;
|
||||
// Pump the handshake without gating on socket_->ready(): on LWIP the
|
||||
// plaintext helper's partial read can drain rcvevent while the rest of the
|
||||
// client hello sits in the lastdata cache, so ready() may report false even
|
||||
// though data is available.
|
||||
return this->pump_handshake_();
|
||||
}
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
||||
/// Drive the handshake state machine until DATA, WOULD_BLOCK, or a fatal
|
||||
/// error. WOULD_BLOCK is not an error: reads stop naturally on EWOULDBLOCK
|
||||
/// and resume on the next loop().
|
||||
APIError APINoiseFrameHelper::pump_handshake_() {
|
||||
while (this->state_ != State::DATA) {
|
||||
APIError err = this->state_action_();
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
// Helper for handling handshake frame errors
|
||||
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||
if (aerr == APIError::BAD_INDICATOR) {
|
||||
@@ -131,16 +165,13 @@ APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func
|
||||
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
// Cache ready() outside the loop. On ESP8266 LWIP raw TCP, ready() returns false once
|
||||
// the rx buffer is consumed. Re-checking each iteration would block handshake writes
|
||||
// that must follow reads, deadlocking the handshake. state_action() will return
|
||||
// WOULD_BLOCK when no more data is available to read.
|
||||
bool socket_ready = this->socket_->ready();
|
||||
while (state_ != State::DATA && socket_ready) {
|
||||
APIError err = state_action_();
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
// Check ready() once, not per state transition. On ESP8266 LWIP raw TCP,
|
||||
// ready() returns false once the rx buffer is consumed. Re-checking each
|
||||
// iteration would block handshake writes that must follow reads,
|
||||
// deadlocking the handshake. pump_handshake_() stops on WOULD_BLOCK when
|
||||
// no more data is available to read.
|
||||
if (state_ != State::DATA && this->socket_->ready()) {
|
||||
APIError err = this->pump_handshake_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,20 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
// Take over a connection whose first bytes were consumed by a plaintext
|
||||
// helper on an unprovisioned device (see APIError::PROTOCOL_SWITCH_TO_NOISE).
|
||||
// Seeds the already-read header bytes and pumps the handshake state machine
|
||||
// until it would block.
|
||||
APIError init_from_handoff(const uint8_t *header, uint8_t header_len);
|
||||
#endif
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
|
||||
protected:
|
||||
APIError pump_handshake_();
|
||||
APIError state_action_();
|
||||
APIError state_action_client_hello_();
|
||||
APIError state_action_server_hello_();
|
||||
|
||||
@@ -89,6 +89,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_() {
|
||||
// If this was the first read, validate the indicator byte
|
||||
if (rx_header_buf_pos_ == 0 && received > 0) {
|
||||
if (rx_header_buf_[0] != 0x00) {
|
||||
#ifdef USE_API_NOISE
|
||||
// Dual build (encryption supported but no key set): a 0x01 first byte
|
||||
// is a Noise client hello. Hand the connection off to a Noise helper
|
||||
// running the all-zeros provisioning PSK so the encryption key can be
|
||||
// set without crossing the wire in plaintext. Preserve the bytes we
|
||||
// already consumed; they are the start of the Noise 3-byte header.
|
||||
if (rx_header_buf_[0] == 0x01) {
|
||||
rx_header_buf_pos_ = static_cast<uint8_t>(received);
|
||||
return APIError::PROTOCOL_SWITCH_TO_NOISE;
|
||||
}
|
||||
#endif
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||
return APIError::BAD_INDICATOR;
|
||||
|
||||
@@ -23,6 +23,15 @@ class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) override;
|
||||
#ifdef USE_API_NOISE
|
||||
// After try_read_frame_ returned PROTOCOL_SWITCH_TO_NOISE: copy out the
|
||||
// header bytes already consumed from the socket (at most 3, the size of the
|
||||
// Noise fixed header) so the replacement Noise helper can be seeded with them.
|
||||
uint8_t get_consumed_header(uint8_t out[3]) const {
|
||||
memcpy(out, this->rx_header_buf_, this->rx_header_buf_pos_);
|
||||
return this->rx_header_buf_pos_;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_();
|
||||
|
||||
@@ -10,13 +10,20 @@ using psk_t = std::array<uint8_t, 32>;
|
||||
|
||||
class APINoiseContext {
|
||||
public:
|
||||
// The all-zeros PSK is reserved: it marks the device as unprovisioned and
|
||||
// doubles as the well-known provisioning PSK that unprovisioned devices
|
||||
// accept for Noise handshakes (passive-sniffing protection only, no
|
||||
// authentication). It is never a valid real key.
|
||||
static bool is_all_zeros(const psk_t &psk) {
|
||||
uint8_t acc = 0;
|
||||
for (uint8_t b : psk) {
|
||||
acc |= b;
|
||||
}
|
||||
return acc == 0;
|
||||
}
|
||||
void set_psk(psk_t psk) {
|
||||
this->psk_ = psk;
|
||||
bool has_psk = false;
|
||||
for (auto i : psk) {
|
||||
has_psk |= i;
|
||||
}
|
||||
this->has_psk_ = has_psk;
|
||||
this->has_psk_ = !is_all_zeros(psk);
|
||||
}
|
||||
const psk_t &get_psk() const { return this->psk_; }
|
||||
bool has_psk() const { return this->has_psk_; }
|
||||
|
||||
@@ -170,6 +170,9 @@ uint8_t *DeviceInfoResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_
|
||||
for (const auto &it : this->serial_proxies) {
|
||||
ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 25, it);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 26, this->api_encryption_provisionable);
|
||||
#endif
|
||||
return pos;
|
||||
}
|
||||
@@ -232,6 +235,9 @@ uint32_t DeviceInfoResponse::calculate_size() const {
|
||||
for (const auto &it : this->serial_proxies) {
|
||||
size += ProtoSize::calc_message_force(2, it.calculate_size());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
size += ProtoSize::calc_bool(2, this->api_encryption_provisionable);
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ class SerialProxyInfo final : public ProtoMessage {
|
||||
class DeviceInfoResponse final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 10;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 309;
|
||||
static constexpr uint16_t ESTIMATED_SIZE = 312;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("device_info_response"); }
|
||||
#endif
|
||||
@@ -588,6 +588,9 @@ class DeviceInfoResponse final : public ProtoMessage {
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
std::array<SerialProxyInfo, SERIAL_PROXY_COUNT> serial_proxies{};
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
bool api_encryption_provisionable{false};
|
||||
#endif
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
|
||||
@@ -982,6 +982,9 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
dump_field(out, ESPHOME_PSTR("api_encryption_provisionable"), this->api_encryption_provisionable);
|
||||
#endif
|
||||
return out.c_str();
|
||||
}
|
||||
|
||||
@@ -448,7 +448,9 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
cv.Exclusive(
|
||||
CONF_TRIGGER_ON_INITIAL_STATE, CONF_TRIGGER_ON_INITIAL_STATE
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||
): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}),
|
||||
|
||||
@@ -50,7 +50,9 @@ _BUTTON_SCHEMA = (
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||
): validate_device_class,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -131,7 +131,9 @@ _COVER_SCHEMA = (
|
||||
cv.Optional(CONF_MQTT_JSON_STATE_PAYLOAD): cv.All(
|
||||
cv.requires_component("mqtt"), cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||
): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
#include "deep_sleep_component.h"
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/wake.h"
|
||||
#include <zephyr/sys/poweroff.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
// The Zephyr watchdog has a short window (2s, or 10s with Zigbee) and
|
||||
// WDT_OPT_PAUSE_IN_SLEEP only pauses it during true hardware sleep — not while a
|
||||
// radio thread (e.g. the Zigbee stack) keeps the CPU busy in k_sem_take(). Feed
|
||||
// it at least this often while waiting so it does not reset the device.
|
||||
static const uint32_t WDT_FEED_INTERVAL_MS = 1000;
|
||||
|
||||
static bool wakeable_delay_feed_wdt(uint32_t ms) {
|
||||
while (ms > 0) {
|
||||
const uint32_t step = std::min(ms, WDT_FEED_INTERVAL_MS);
|
||||
esphome::internal::wakeable_delay(step);
|
||||
esphome::arch_feed_wdt();
|
||||
if (esphome::wake_request_take()) {
|
||||
return true;
|
||||
}
|
||||
if (ms != UINT32_MAX) {
|
||||
ms -= step;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {}
|
||||
@@ -15,8 +38,9 @@ void DeepSleepComponent::dump_config_platform_() {}
|
||||
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
|
||||
|
||||
void DeepSleepComponent::deep_sleep_() {
|
||||
bool woke = false;
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
esphome::internal::wakeable_delay(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
|
||||
woke = wakeable_delay_feed_wdt(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
|
||||
} else {
|
||||
#ifndef USE_ZIGBEE
|
||||
// the device can be woken up through one of the following signals:
|
||||
@@ -29,10 +53,9 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
// The system is reset when it wakes up from System OFF mode.
|
||||
sys_poweroff();
|
||||
#else
|
||||
esphome::internal::wakeable_delay(UINT32_MAX);
|
||||
woke = wakeable_delay_feed_wdt(UINT32_MAX);
|
||||
#endif
|
||||
}
|
||||
const bool woke = esphome::wake_request_take();
|
||||
if (woke) {
|
||||
ESP_LOGD(TAG, "Woken up by another thread");
|
||||
} else {
|
||||
|
||||
@@ -50,7 +50,9 @@ _EVENT_SCHEMA = (
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
|
||||
cv.GenerateID(): cv.declare_id(Event),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||
): validate_device_class,
|
||||
cv.Optional(CONF_ON_EVENT): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -342,7 +342,7 @@ bool Mcp4461Component::increase_wiper_(Mcp4461WiperIdx wiper) {
|
||||
ESP_LOGV(TAG, "Increasing wiper %u", wiper_idx);
|
||||
uint8_t addr = this->get_wiper_address_(wiper_idx);
|
||||
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT);
|
||||
auto err = this->write(&this->address_, reg);
|
||||
auto err = this->write(®, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
|
||||
this->status_set_warning();
|
||||
@@ -373,7 +373,7 @@ bool Mcp4461Component::decrease_wiper_(Mcp4461WiperIdx wiper) {
|
||||
ESP_LOGV(TAG, "Decreasing wiper %u", wiper_idx);
|
||||
uint8_t addr = this->get_wiper_address_(wiper_idx);
|
||||
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::DECREMENT);
|
||||
auto err = this->write(&this->address_, reg);
|
||||
auto err = this->write(®, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
|
||||
this->status_set_warning();
|
||||
|
||||
@@ -47,7 +47,7 @@ void MDNSComponent::setup_buffers_and_register_(PlatformRegisterFn platform_regi
|
||||
auto &services = services_storage;
|
||||
#endif
|
||||
|
||||
#ifdef USE_API
|
||||
#ifdef USE_MDNS_DEVICE_INFO_TXT
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
get_mac_address_into_buffer(this->mac_address_);
|
||||
char *mac_ptr = this->mac_address_;
|
||||
@@ -70,17 +70,20 @@ void MDNSComponent::setup_buffers_and_register_(PlatformRegisterFn platform_regi
|
||||
platform_register(this, services);
|
||||
}
|
||||
|
||||
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, char *mac_address_buf,
|
||||
char *config_hash_buf) {
|
||||
void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services,
|
||||
const char *mac_address_buf, const char *config_hash_buf) {
|
||||
// IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES
|
||||
// in mdns/__init__.py. If you add a new service here, update both locations.
|
||||
|
||||
#ifdef USE_MDNS_DEVICE_INFO_TXT
|
||||
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_CONFIG_HASH, "config_hash");
|
||||
#endif
|
||||
|
||||
#ifdef USE_API
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_ESPHOMELIB, "_esphomelib");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_FRIENDLY_NAME, "friendly_name");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_CONFIG_HASH, "config_hash");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_MAC, "mac");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_PLATFORM, "platform");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_BOARD, "board");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_NETWORK, "network");
|
||||
@@ -107,7 +110,13 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
txt_count++; // network
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
const bool api_has_psk = api::global_api_server->get_noise_ctx().has_psk();
|
||||
txt_count++; // api_encryption or api_encryption_supported
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
if (!api_has_psk) {
|
||||
txt_count++; // api_provisioning
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
txt_count += 2; // project_name and project_version
|
||||
@@ -163,9 +172,18 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION, "api_encryption");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_ENCRYPTION_SUPPORTED, "api_encryption_supported");
|
||||
MDNS_STATIC_CONST_CHAR(NOISE_ENCRYPTION, "Noise_NNpsk0_25519_ChaChaPoly_SHA256");
|
||||
bool has_psk = api::global_api_server->get_noise_ctx().has_psk();
|
||||
const char *encryption_key = has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
|
||||
const char *encryption_key = api_has_psk ? TXT_API_ENCRYPTION : TXT_API_ENCRYPTION_SUPPORTED;
|
||||
txt_records.push_back({MDNS_STR(encryption_key), MDNS_STR(NOISE_ENCRYPTION)});
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
if (!api_has_psk) {
|
||||
// Unprovisioned device without a YAML key: advertise that the encryption
|
||||
// key can be provisioned over a zero-PSK Noise connection. Gated on the
|
||||
// YAML define so this survives the plaintext removal in 2027.2.0.
|
||||
MDNS_STATIC_CONST_CHAR(TXT_API_PROVISIONING, "api_provisioning");
|
||||
MDNS_STATIC_CONST_CHAR(VALUE_ZERO_PSK, "zero-psk");
|
||||
txt_records.push_back({MDNS_STR(TXT_API_PROVISIONING), MDNS_STR(VALUE_ZERO_PSK)});
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
@@ -212,12 +230,18 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
web_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||
web_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
web_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
|
||||
#ifndef USE_API
|
||||
// Without the native API there is no _esphomelib service, so publish the
|
||||
// device info here for the device builder to discover.
|
||||
web_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)},
|
||||
{MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)},
|
||||
{MDNS_STR(TXT_CONFIG_HASH), MDNS_STR(config_hash_buf)}};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_WEBSERVER) && \
|
||||
!defined(USE_MDNS_EXTRA_SERVICES)
|
||||
MDNS_STATIC_CONST_CHAR(SERVICE_HTTP, "_http");
|
||||
MDNS_STATIC_CONST_CHAR(TXT_VERSION, "version");
|
||||
|
||||
// Publish "http" service if not using native API or any other services
|
||||
// This is just to have *some* mDNS service so that .local resolution works
|
||||
@@ -225,7 +249,9 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
|
||||
fallback_service.service_type = MDNS_STR(SERVICE_HTTP);
|
||||
fallback_service.proto = MDNS_STR(SERVICE_TCP);
|
||||
fallback_service.port = []() -> uint16_t { return USE_WEBSERVER_PORT; };
|
||||
fallback_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)}};
|
||||
fallback_service.txt_records = {{MDNS_STR(TXT_VERSION), MDNS_STR(VALUE_VERSION)},
|
||||
{MDNS_STR(TXT_MAC), MDNS_STR(mac_address_buf)},
|
||||
{MDNS_STR(TXT_CONFIG_HASH), MDNS_STR(config_hash_buf)}};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,15 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Device info TXT records (version, mac, config_hash) are published on the _esphomelib service
|
||||
// when the native API is enabled, otherwise on the _http service (web_server's or the fallback one).
|
||||
// When neither applies (only prometheus, sendspin or user-defined services are configured), no
|
||||
// device info records are published and the buffers below are not needed.
|
||||
#if defined(USE_API) || defined(USE_WEBSERVER) || \
|
||||
(!defined(USE_PROMETHEUS) && !defined(USE_SENDSPIN) && !defined(USE_MDNS_EXTRA_SERVICES))
|
||||
#define USE_MDNS_DEVICE_INFO_TXT
|
||||
#endif
|
||||
|
||||
namespace esphome::mdns {
|
||||
|
||||
// Helper struct that identifies strings that may be stored in flash storage (similar to LogString)
|
||||
@@ -136,7 +145,7 @@ class MDNSComponent final : public Component
|
||||
StaticVector<std::string, MDNS_DYNAMIC_TXT_COUNT> dynamic_txt_values_;
|
||||
#endif
|
||||
|
||||
#if defined(USE_API) && defined(USE_MDNS_STORE_SERVICES)
|
||||
#if defined(USE_MDNS_DEVICE_INFO_TXT) && defined(USE_MDNS_STORE_SERVICES)
|
||||
/// Fixed buffer for MAC address (only needed when services are stored)
|
||||
char mac_address_[MAC_ADDRESS_BUFFER_SIZE];
|
||||
/// Fixed buffer for config hash hex string (only needed when services are stored)
|
||||
@@ -149,8 +158,8 @@ class MDNSComponent final : public Component
|
||||
// RP2040 defers MDNS.begin() until the first IP-up event; this tracks that.
|
||||
bool initialized_{false};
|
||||
#endif
|
||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, char *mac_address_buf,
|
||||
char *config_hash_buf);
|
||||
void compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services, const char *mac_address_buf,
|
||||
const char *config_hash_buf);
|
||||
};
|
||||
|
||||
} // namespace esphome::mdns
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace esphome::mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
#ifdef USE_MDNS_STORE_SERVICES
|
||||
#ifdef USE_API
|
||||
#ifdef USE_MDNS_DEVICE_INFO_TXT
|
||||
get_mac_address_into_buffer(this->mac_address_);
|
||||
char *mac_ptr = this->mac_address_;
|
||||
format_hex_to(this->config_hash_str_, App.get_config_hash());
|
||||
|
||||
@@ -26,16 +26,22 @@ from esphome.const import (
|
||||
CONF_OFFSET_HEIGHT,
|
||||
CONF_OFFSET_WIDTH,
|
||||
CONF_PAGES,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.core import TimePeriod
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
|
||||
LOGGER = cv.logging.getLogger(__name__)
|
||||
|
||||
CONF_TRANSFORMS = "transforms"
|
||||
|
||||
# All axis transforms a model may support, in the order they appear in the schema.
|
||||
ALL_TRANSFORMS = (CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY)
|
||||
|
||||
ColorOrder = display_ns.enum("ColorMode")
|
||||
|
||||
NOP = 0x00
|
||||
@@ -302,7 +308,8 @@ class DriverChip:
|
||||
"""
|
||||
A class representing a MIPI DBI driver chip model.
|
||||
The parameters supplied as defaults will be used to provide default values for the display configuration.
|
||||
Setting swap_xy to cv.UNDEFINED will indicate that the model does not support swapping X and Y axes.
|
||||
Pass a ``transforms`` set to restrict which axis transforms (mirror_x, mirror_y, swap_xy) the model
|
||||
supports; by default all three are available.
|
||||
"""
|
||||
|
||||
models: dict[str, Self] = {}
|
||||
@@ -387,11 +394,15 @@ class DriverChip:
|
||||
"""
|
||||
Return the available transforms for this model.
|
||||
"""
|
||||
if (transforms := self.get_default(CONF_TRANSFORMS, None)) is not None:
|
||||
return transforms
|
||||
if self.get_default("no_transform", False):
|
||||
return set()
|
||||
if self.get_default(CONF_SWAP_XY) != cv.UNDEFINED:
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
|
||||
return {CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
raise ValueError(
|
||||
"Setting 'swap_xy' to 'cv.UNDEFINED' is no longer supported; set 'transforms' instead"
|
||||
)
|
||||
|
||||
def has_hardware_transform(self, config) -> bool:
|
||||
"""
|
||||
@@ -533,17 +544,31 @@ class DriverChip:
|
||||
transform[CONF_TRANSFORM] = self.rotation_as_transform(config)
|
||||
return transform
|
||||
|
||||
def swap_xy_schema(self):
|
||||
uses_swap = self.get_default(CONF_SWAP_XY, None) != cv.UNDEFINED
|
||||
def transform_schema(self):
|
||||
"""
|
||||
Build the schema for the ``transform`` config option of this model.
|
||||
|
||||
def validator(value):
|
||||
if value:
|
||||
raise cv.Invalid("Axis swapping not supported by this model")
|
||||
return cv.boolean(value)
|
||||
Each transform the model supports is a required boolean. A transform the model does not
|
||||
support may be omitted or set to ``false``; setting it to ``true`` reports a clear error
|
||||
naming the unsupported transform instead of a generic "extra keys not allowed".
|
||||
"""
|
||||
supported = self.transforms
|
||||
|
||||
if uses_swap:
|
||||
return {cv.Required(CONF_SWAP_XY): cv.boolean}
|
||||
return {cv.Optional(CONF_SWAP_XY, default=False): validator}
|
||||
def unsupported(name):
|
||||
def validator(value):
|
||||
if cv.boolean(value):
|
||||
raise cv.Invalid(f"'{name}' is not supported by this model")
|
||||
return False
|
||||
|
||||
return validator
|
||||
|
||||
schema = {}
|
||||
for name in ALL_TRANSFORMS:
|
||||
if name in supported:
|
||||
schema[cv.Required(name)] = cv.boolean
|
||||
else:
|
||||
schema[cv.Optional(name, default=False)] = unsupported(name)
|
||||
return cv.Any(cv.Schema(schema), cv.one_of(CONF_DISABLED, lower=True))
|
||||
|
||||
def get_madctl(self, transform: dict, config: dict) -> int:
|
||||
"""
|
||||
@@ -577,12 +602,15 @@ class DriverChip:
|
||||
"""
|
||||
return self.get_default(f"no_{command.lower()}", False)
|
||||
|
||||
def get_sequence(self, config, add_madctl=True) -> tuple[int, ...]:
|
||||
def get_sequence(self, config, add_madctl=True, add_reset=False) -> tuple[int, ...]:
|
||||
"""
|
||||
Create the init sequence for the display.
|
||||
Use the default sequence from the model, if any, and append any custom sequence provided in the config.
|
||||
Append SLPOUT (if not already in the sequence) and DISPON to the end of the sequence
|
||||
MADCTL will be set if add_madctl is True
|
||||
If add_reset is True, a reset is prepended: a software reset when no reset pin
|
||||
is configured (and the model doesn't skip it), followed by a settling delay that
|
||||
both a software and a hardware reset require.
|
||||
Returns the init sequence
|
||||
"""
|
||||
sequence = list(self.initsequence or ())
|
||||
@@ -591,6 +619,15 @@ class DriverChip:
|
||||
# Ensure each command is a tuple
|
||||
sequence = [x if isinstance(x, tuple) else (x,) for x in sequence]
|
||||
|
||||
if add_reset:
|
||||
reset: list = []
|
||||
# A software reset is only needed when there is no hardware reset pin.
|
||||
if CONF_RESET_PIN not in config and not self.skip_command("SWRESET"):
|
||||
reset.append((SWRESET,))
|
||||
# Both a software and a hardware reset need a settling delay before further commands.
|
||||
reset.append(delay(10))
|
||||
sequence = reset + sequence
|
||||
|
||||
# Set pixel format if not already in the custom sequence
|
||||
pixel_mode = config[CONF_PIXEL_MODE]
|
||||
if not isinstance(pixel_mode, int):
|
||||
@@ -611,13 +648,47 @@ class DriverChip:
|
||||
sequence.append((BRIGHTNESS, brightness))
|
||||
# Add a SLPOUT command if required.
|
||||
if not self.skip_command("SLPOUT"):
|
||||
# A zero delay will delay until 120ms after reset
|
||||
sequence.append(delay(0))
|
||||
sequence.append((SLPOUT,))
|
||||
sequence.append(delay(10))
|
||||
sequence.append((DISPON,))
|
||||
# Add a delay here because additional commands may be added after this at runtime.
|
||||
sequence.append(delay(10))
|
||||
|
||||
# Flatten the sequence into a list of bytes, with the length of each command
|
||||
# or the delay flag inserted where needed
|
||||
return flatten_sequence(sequence)
|
||||
|
||||
def check_requirements(self) -> None:
|
||||
"""
|
||||
Raise a friendly error if any component this model requires is not configured.
|
||||
|
||||
This runs during schema validation (before ID references are resolved) so that a
|
||||
model whose default pins live on a pin expander reports the missing expander clearly
|
||||
instead of a cryptic "Couldn't find ID" from the unresolved pin reference.
|
||||
|
||||
Also logs a warning if the model is deprecated.
|
||||
"""
|
||||
if deprecation_reason := self.get_default("deprecation_reason"):
|
||||
LOGGER.warning(
|
||||
"Display model %s is deprecated: %s", self.name, deprecation_reason
|
||||
)
|
||||
if requirements := self.get_default("requires", set()):
|
||||
# ``raw_config`` is populated before any component schema runs during a real
|
||||
# validation, so presence of a required component is simply a top-level key.
|
||||
# When it is absent (e.g. a unit test that invokes the schema directly) there
|
||||
# is no config to check against, so skip.
|
||||
global_config = CORE.raw_config
|
||||
if global_config is None:
|
||||
return
|
||||
missing = {x for x in requirements if x not in global_config}
|
||||
if missing:
|
||||
reqstr = ", ".join(f"'{x}'" for x in sorted(missing))
|
||||
raise cv.Invalid(
|
||||
f"{self.name} requires component{'s' if len(missing) > 1 else ''} {reqstr} to be configured"
|
||||
)
|
||||
|
||||
|
||||
def requires_buffer(config) -> bool:
|
||||
"""
|
||||
|
||||
@@ -41,24 +41,21 @@ from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
CONF_COLOR_ORDER,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_DISABLED,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_ID,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
CONF_SWAP_XY,
|
||||
CONF_TRANSFORM,
|
||||
CONF_WIDTH,
|
||||
)
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
from . import mipi_dsi_ns, models
|
||||
from .models import DsiDriverChip
|
||||
|
||||
# Currently only ESP32-P4 is supported, so esp_ldo and psram are required
|
||||
DEPENDENCIES = ["esp32", "esp_ldo", "psram"]
|
||||
@@ -73,7 +70,7 @@ ColorBitness = display.display_ns.enum("ColorBitness")
|
||||
CONF_LANE_BIT_RATE = "lane_bit_rate"
|
||||
CONF_LANES = "lanes"
|
||||
|
||||
DriverChip("CUSTOM")
|
||||
DsiDriverChip("CUSTOM")
|
||||
|
||||
# Import all models dynamically from the models package
|
||||
|
||||
@@ -90,19 +87,7 @@ COLOR_DEPTHS = {
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.defaults[CONF_SWAP_XY] = cv.UNDEFINED
|
||||
transform = cv.Any(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||
cv.Optional(CONF_SWAP_XY): cv.invalid(
|
||||
"Axis swapping not supported by DSI displays"
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.one_of(CONF_DISABLED, lower=True),
|
||||
)
|
||||
transform = model.transform_schema()
|
||||
# CUSTOM model will need to provide a custom init sequence
|
||||
iseqconf = (
|
||||
cv.Required(CONF_INIT_SEQUENCE)
|
||||
@@ -172,6 +157,7 @@ def _config_schema(config):
|
||||
)(config)
|
||||
config = model_schema(config)(config)
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.check_requirements()
|
||||
width, height, _offset_width, _offset_height, _pad_width, _pad_height = (
|
||||
model.get_dimensions(config)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.const import CONF_SWAP_XY
|
||||
|
||||
|
||||
class DsiDriverChip(DriverChip):
|
||||
"""A driver chip for MIPI DSI displays."""
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
Return the set of transformations supported by this driver chip.
|
||||
DSI displays do not support axis swapping, so this method removes CONF_SWAP_XY
|
||||
"""
|
||||
return super().transforms - {CONF_SWAP_XY}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
from . import DsiDriverChip
|
||||
|
||||
# fmt: off
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"JC1060P470",
|
||||
width=1024,
|
||||
height=600,
|
||||
@@ -14,7 +13,6 @@ DriverChip(
|
||||
vsync_front_porch=12,
|
||||
pclk_frequency="54MHz",
|
||||
lane_bit_rate="750Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00),
|
||||
@@ -46,7 +44,7 @@ DriverChip(
|
||||
# * Horizontal Timing (hsync_pulse_width=12, hsync_back_porch=42, hsync_front_porch=42)
|
||||
# * Vertical Timing (vsync_pulse_width=2, vsync_back_porch=8, vsync_front_porch=166)
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"JC4880P443",
|
||||
width=480,
|
||||
height=800,
|
||||
@@ -58,7 +56,6 @@ DriverChip(
|
||||
vsync_front_porch=166,
|
||||
pclk_frequency="34MHz",
|
||||
lane_bit_rate="500Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=5,
|
||||
initsequence=[
|
||||
@@ -111,7 +108,7 @@ DriverChip(
|
||||
# * Horizontal Timing (hsync_pulse_width=20, hsync_back_porch=20, hsync_front_porch=40)
|
||||
# * Vertical Timing (vsync_pulse_width=4, vsync_back_porch=8, vsync_front_porch=20)
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"JC8012P4A1",
|
||||
width=800,
|
||||
height=1280,
|
||||
@@ -123,7 +120,6 @@ DriverChip(
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="60MHz",
|
||||
lane_bit_rate="1Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
from . import DsiDriverChip
|
||||
|
||||
# fmt: off
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"M5STACK-TAB5",
|
||||
height=1280,
|
||||
width=720,
|
||||
@@ -14,7 +13,6 @@ DriverChip(
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="60MHz",
|
||||
lane_bit_rate="730Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xFF, 0x98, 0x81, 0x01), # Select Page 1
|
||||
@@ -56,8 +54,8 @@ DriverChip(
|
||||
],
|
||||
)
|
||||
|
||||
DriverChip(
|
||||
"M5STACK-TAB5-V2",
|
||||
TAB5_ST7123 = DsiDriverChip(
|
||||
"M5STACK-TAB5-ST7123",
|
||||
height=1280,
|
||||
width=720,
|
||||
hsync_back_porch=40,
|
||||
@@ -68,7 +66,6 @@ DriverChip(
|
||||
vsync_front_porch=220,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="960Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0x01,),
|
||||
@@ -97,3 +94,58 @@ DriverChip(
|
||||
(0xC9, 0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF),
|
||||
],
|
||||
)
|
||||
|
||||
TAB5_ST7123.extend(
|
||||
"M5STACK-TAB5-V2",
|
||||
deprecation_reason="Use 'M5STACK-TAB5-ST7123' or 'M5STACK-TAB5-ST7121' instead."
|
||||
)
|
||||
|
||||
# Some Tab5 "v2" units ship with an ST7121 controller instead of the ST7123.
|
||||
# The two are distinguishable at runtime by the touch controller firmware version (the M5
|
||||
# factory firmware branches on it), but ESPHome selects the panel at compile time, so ST7121
|
||||
# units must select this model explicitly. Values taken from M5's factory source
|
||||
# (m5stack/M5Tab5-UserDemo: m5stack_tab5.c is_st7121 path + esp_lcd_st7121.c default table).
|
||||
DsiDriverChip(
|
||||
"M5STACK-TAB5-ST7121",
|
||||
height=1280,
|
||||
width=720,
|
||||
hsync_back_porch=40,
|
||||
hsync_pulse_width=2,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=24,
|
||||
vsync_pulse_width=20,
|
||||
vsync_front_porch=200,
|
||||
pclk_frequency="70MHz",
|
||||
lane_bit_rate="965Mbps",
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0x01,),
|
||||
(0x60, 0x71, 0x21, 0xA2),
|
||||
(0x60, 0x71, 0x21, 0xA3),
|
||||
(0x60, 0x71, 0x21, 0xA4),
|
||||
(0x78, 0x21),
|
||||
(0x79, 0xEF),
|
||||
(0xA4, 0x31),
|
||||
(0xB7, 0x00, 0x00, 0x5F, 0x5F, 0x44, 0x1A),
|
||||
(0xB0, 0x22, 0x6B, 0x11, 0x89, 0x25, 0x43, 0x43),
|
||||
(0xBF, 0xA7, 0xA7),
|
||||
(0xA5, 0xF0, 0x03),
|
||||
(0xD7, 0x10, 0x2C, 0x14, 0x2A, 0x80, 0x80),
|
||||
(0x90, 0x71, 0x23, 0x5A, 0x20, 0x24, 0x11, 0x21),
|
||||
(0xA3, 0x80, 0x01, 0x8C, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x10, 0xEF, 0x58, 0x00, 0x00, 0x00, 0xFF),
|
||||
(0xA6, 0x0A, 0x00, 0x24, 0x71, 0x36, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x00, 0x24, 0x71, 0x37, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x00, 0x24, 0x71, 0x00, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x00, 0x2C, 0x71, 0x00, 0x01, 0x00, 0x00, 0x68, 0x68, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00),
|
||||
(0xA7, 0x1A, 0x1A, 0xC0, 0x64, 0x40, 0x04, 0x15, 0x40, 0x00, 0x40, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x26, 0x37, 0x40, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0x8C, 0x9D, 0x40, 0x00, 0x00, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x64, 0x40, 0xAE, 0xBF, 0x00, 0x00, 0x20, 0x00, 0x68, 0x68, 0x91, 0xFF, 0x08, 0x80, 0x79),
|
||||
(0xAC, 0x1D, 0x18, 0x19, 0x1D, 0x18, 0x19, 0x04, 0x1C, 0x1D, 0x08, 0x0A, 0x10, 0x12, 0x0C, 0x0E, 0x14, 0x16, 0x00, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x18, 0x19, 0x1D, 0x18, 0x19, 0x06, 0x1C, 0x1D, 0x09, 0x0B, 0x11, 0x13, 0x0D, 0x0F, 0x15, 0x17, 0x02, 0x1D, 0x1D, 0x1D, 0x1D),
|
||||
(0xAD, 0x0C, 0x40, 0x46, 0x00, 0x07, 0x4B, 0x4B, 0xFF, 0xFF, 0xF0, 0x40, 0x0E, 0x01, 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF),
|
||||
(0xAE, 0xF0, 0xFF, 0x03, 0xF0, 0xFF, 0x03, 0x00),
|
||||
(0xB2, 0x15, 0x19, 0x05, 0x23, 0x49, 0x2D, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x60, 0xFD, 0x20, 0xC0, 0x00),
|
||||
(0xE8, 0x20, 0x60, 0x04, 0x8E, 0x8E, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E),
|
||||
(0x75, 0x03, 0x04),
|
||||
(0xE7, 0x4B, 0x00, 0x00, 0xBE, 0x4B, 0x8C, 0x20, 0x1A, 0xF0, 0x7D, 0x14, 0x7D, 0x14, 0x7D, 0x14, 0x7D, 0x14, 0xFF, 0x00, 0x32, 0x30, 0x73, 0x00, 0x00, 0xC8, 0x6A, 0xFF, 0x5A, 0x64, 0x38, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x64, 0x01, 0x01, 0x7C, 0xFF, 0x1A, 0x51),
|
||||
(0xE1, 0x0C, 0x0C),
|
||||
(0xEA, 0x15, 0x00, 0x01),
|
||||
(0xC8, 0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1F, 0x01, 0x39, 0x3E, 0x00, 0x78, 0x06, 0xE2, 0x02, 0x11, 0x33, 0x01, 0x7A, 0x0D, 0x21, 0xC4, 0x0B, 0x19, 0x08, 0x32, 0xA0, 0x08, 0x1A, 0x0A, 0xF3, 0x7F, 0x0E, 0xC5, 0xE8, 0x03, 0xFF),
|
||||
(0xC9, 0x00, 0x00, 0x04, 0x08, 0x10, 0x00, 0x1F, 0x01, 0x39, 0x3E, 0x00, 0x78, 0x06, 0xE2, 0x02, 0x11, 0x33, 0x01, 0x7A, 0x0D, 0x21, 0xC4, 0x0B, 0x19, 0x08, 0x32, 0xA0, 0x08, 0x1A, 0x0A, 0xF3, 0x7F, 0x0E, 0xC5, 0xE8, 0x03, 0xFF),
|
||||
(0x60, 0x71, 0x21, 0x00),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
from . import DsiDriverChip
|
||||
|
||||
# Standalone display
|
||||
# Product page: https://www.seeedstudio.com/reTerminal-D1001-p-6729.html
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"SEEED-RETERMINAL-D1001",
|
||||
height=1280,
|
||||
width=800,
|
||||
@@ -15,10 +14,10 @@ DriverChip(
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
enable_pin=[{"xl9535": None, "number": 0}, {"xl9535": None, "number": 7}],
|
||||
reset_pin={"xl9535": None, "number": 2},
|
||||
requires={"psram", "xl9535"},
|
||||
initsequence=(
|
||||
(0xE0, 0x00),
|
||||
(0xE1, 0x93),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
from . import DsiDriverChip
|
||||
|
||||
# fmt: off
|
||||
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1
|
||||
# Product page: https://www.waveshare.com/wiki/ESP32-P4-Nano-StartPage
|
||||
JD9365_10_1_DSI_TOUCH_A = DriverChip(
|
||||
JD9365_10_1_DSI_TOUCH_A = DsiDriverChip(
|
||||
"WAVESHARE-P4-NANO-10.1",
|
||||
height=1280,
|
||||
width=800,
|
||||
@@ -18,7 +17,6 @@ JD9365_10_1_DSI_TOUCH_A = DriverChip(
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xE0, 0x00), # select userpage
|
||||
@@ -65,7 +63,7 @@ JD9365_10_1_DSI_TOUCH_A.extend(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_st7703
|
||||
# Product page: https://www.waveshare.com/wiki/ESP32-P4-86-Panel-ETH-2RO
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"WAVESHARE-P4-86-PANEL",
|
||||
height=720,
|
||||
width=720,
|
||||
@@ -77,7 +75,6 @@ DriverChip(
|
||||
vsync_front_porch=20,
|
||||
pclk_frequency="38MHz",
|
||||
lane_bit_rate="480Mbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
reset_pin=27,
|
||||
initsequence=[
|
||||
@@ -109,7 +106,7 @@ DriverChip(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_ek79007
|
||||
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-7B
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B",
|
||||
height=600,
|
||||
width=1024,
|
||||
@@ -139,7 +136,7 @@ DriverChip(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
|
||||
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-3.4C
|
||||
JD9365_3_4_DSI_TOUCH_C = DriverChip(
|
||||
JD9365_3_4_DSI_TOUCH_C = DsiDriverChip(
|
||||
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-3.4C",
|
||||
height=800,
|
||||
width=800,
|
||||
@@ -151,7 +148,6 @@ JD9365_3_4_DSI_TOUCH_C = DriverChip(
|
||||
vsync_front_porch=24,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xE0, 0x00), # select userpage
|
||||
@@ -197,7 +193,7 @@ JD9365_3_4_DSI_TOUCH_C.extend(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
|
||||
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-4C
|
||||
JD9365_4_DSI_TOUCH_C = DriverChip(
|
||||
JD9365_4_DSI_TOUCH_C = DsiDriverChip(
|
||||
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-4C",
|
||||
height=720,
|
||||
width=720,
|
||||
@@ -209,7 +205,6 @@ JD9365_4_DSI_TOUCH_C = DriverChip(
|
||||
vsync_front_porch=24,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xE0, 0x00), # select userpage
|
||||
@@ -255,7 +250,7 @@ JD9365_4_DSI_TOUCH_C.extend(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
|
||||
# Product page: https://www.waveshare.com/wiki/8-DSI-TOUCH-A
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"WAVESHARE-8-DSI-TOUCH-A",
|
||||
height=1280,
|
||||
width=800,
|
||||
@@ -267,7 +262,6 @@ DriverChip(
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
initsequence=[
|
||||
(0xE0, 0x00), # select userpage
|
||||
@@ -304,7 +298,7 @@ DriverChip(
|
||||
# Source for parameters and initsequence:
|
||||
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_ili9881c
|
||||
# Product page: https://www.waveshare.com/wiki/7-DSI-TOUCH-A
|
||||
DriverChip(
|
||||
DsiDriverChip(
|
||||
"WAVESHARE-7-DSI-TOUCH-A",
|
||||
height=1280,
|
||||
width=720,
|
||||
|
||||
@@ -18,6 +18,8 @@ from esphome.components.mipi import (
|
||||
CONF_HSYNC_BACK_PORCH,
|
||||
CONF_HSYNC_FRONT_PORCH,
|
||||
CONF_HSYNC_PULSE_WIDTH,
|
||||
CONF_PCLK_FREQUENCY,
|
||||
CONF_PCLK_INVERTED,
|
||||
CONF_PCLK_PIN,
|
||||
CONF_PIXEL_MODE,
|
||||
CONF_USE_AXIS_FLIPS,
|
||||
@@ -34,9 +36,11 @@ from esphome.components.mipi import (
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
from esphome.components.rpi_dpi_rgb.display import (
|
||||
CONF_PCLK_FREQUENCY,
|
||||
CONF_PCLK_INVERTED,
|
||||
from esphome.components.spi import (
|
||||
CONF_SPI_MODE,
|
||||
SPI_DATA_RATE_SCHEMA,
|
||||
SPI_MODE_OPTIONS,
|
||||
SPIComponent,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -48,7 +52,6 @@ from esphome.const import (
|
||||
CONF_DATA_RATE,
|
||||
CONF_DC_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_DISABLED,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_GREEN,
|
||||
CONF_HSYNC_PIN,
|
||||
@@ -57,8 +60,6 @@ from esphome.const import (
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_NUMBER,
|
||||
CONF_RED,
|
||||
@@ -72,10 +73,10 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.final_validate import full_config
|
||||
|
||||
from ..spi import CONF_SPI_MODE, SPI_DATA_RATE_SCHEMA, SPI_MODE_OPTIONS, SPIComponent
|
||||
from . import models
|
||||
from .models import RgbDriverChip
|
||||
|
||||
DEPENDENCIES = ["esp32", "psram"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
mipi_rgb_ns = cg.esphome_ns.namespace("mipi_rgb")
|
||||
mipi_rgb = mipi_rgb_ns.class_("MipiRgb", display.Display, cg.Component)
|
||||
@@ -86,7 +87,7 @@ ColorOrder = display.display_ns.enum("ColorMode")
|
||||
|
||||
DATA_PIN_SCHEMA = pins.internal_gpio_output_pin_schema
|
||||
|
||||
DriverChip("CUSTOM")
|
||||
RgbDriverChip("CUSTOM")
|
||||
|
||||
# Import all models dynamically from the models package
|
||||
|
||||
@@ -120,16 +121,7 @@ def data_pin_set(length):
|
||||
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
transform = cv.Any(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||
**model.swap_xy_schema(),
|
||||
}
|
||||
),
|
||||
cv.one_of(CONF_DISABLED, lower=True),
|
||||
)
|
||||
transform = model.transform_schema()
|
||||
# RPI model does not use an init sequence, indicates with empty list
|
||||
if model.initsequence is None:
|
||||
# Custom model requires an init sequence
|
||||
@@ -235,6 +227,7 @@ def _config_schema(config):
|
||||
only_on_variant(supported=[VARIANT_ESP32S3, VARIANT_ESP32P4]),
|
||||
)(config)
|
||||
model = MODELS[config[CONF_MODEL].upper()]
|
||||
model.check_requirements()
|
||||
width, height, _offset_width, _offset_height, _pad_width, _pad_height = (
|
||||
model.get_dimensions(config)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.const import CONF_SWAP_XY
|
||||
|
||||
|
||||
class RgbDriverChip(DriverChip):
|
||||
"""A driver chip for MIPI RGB displays."""
|
||||
|
||||
@property
|
||||
def transforms(self) -> set[str]:
|
||||
"""
|
||||
Return the set of transformations supported by this driver chip.
|
||||
RGB displays do not support axis swapping, so this method removes CONF_SWAP_XY
|
||||
"""
|
||||
return super().transforms - {CONF_SWAP_XY}
|
||||
@@ -5,6 +5,7 @@ st7701s.extend(
|
||||
width=480,
|
||||
height=480,
|
||||
data_rate="2MHz",
|
||||
requires={"psram"},
|
||||
cs_pin=39,
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
from .st7701s import ST7701S
|
||||
|
||||
# fmt: off
|
||||
@@ -8,10 +6,10 @@ ST7701S(
|
||||
width=480,
|
||||
height=480,
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 17},
|
||||
reset_pin={"xl9535": None, "number": 5},
|
||||
requires={"psram", "xl9535"},
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
pclk_pin=41,
|
||||
@@ -57,9 +55,9 @@ t_rgb = ST7701S(
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
invert_colors=False,
|
||||
swap_xy=UNDEFINED,
|
||||
spi_mode="MODE3",
|
||||
cs_pin={"xl9535": None, "number": 3},
|
||||
requires={"psram", "xl9535"},
|
||||
de_pin=45,
|
||||
hsync_pin=47,
|
||||
vsync_pin=41,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from . import RgbDriverChip
|
||||
|
||||
# A driver chip for Raspberry Pi MIPI RGB displays. These require no init sequence
|
||||
DriverChip(
|
||||
RgbDriverChip(
|
||||
"RPI",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
)
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
from esphome.components.mipi import (
|
||||
MADCTL,
|
||||
MADCTL_ML,
|
||||
MADCTL_XFLIP,
|
||||
MODE_BGR,
|
||||
DriverChip,
|
||||
)
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from esphome.components.mipi import MADCTL, MADCTL_ML, MADCTL_XFLIP, MODE_BGR
|
||||
from esphome.const import CONF_COLOR_ORDER, CONF_HEIGHT, CONF_MIRROR_X, CONF_MIRROR_Y
|
||||
|
||||
from . import RgbDriverChip
|
||||
|
||||
SDIR_CMD = 0xC7
|
||||
|
||||
|
||||
class ST7701S(DriverChip):
|
||||
class ST7701S(RgbDriverChip):
|
||||
# The ST7701s does not use the standard MADCTL bits for x/y mirroring
|
||||
def add_madctl(self, sequence: list, config: dict):
|
||||
transform = self.get_transform(config)
|
||||
@@ -45,7 +40,6 @@ st7701s = ST7701S(
|
||||
"ST7701S",
|
||||
width=480,
|
||||
height=864,
|
||||
swap_xy=UNDEFINED,
|
||||
hsync_front_porch=20,
|
||||
hsync_back_porch=10,
|
||||
hsync_pulse_width=10,
|
||||
@@ -85,6 +79,7 @@ st7701s.extend(
|
||||
height=480,
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
requires={"psram"},
|
||||
cs_pin=1,
|
||||
de_pin={
|
||||
"number": 45,
|
||||
@@ -117,6 +112,7 @@ st7701s.extend(
|
||||
vsync_pulse_width=8,
|
||||
vsync_back_porch=20,
|
||||
cs_pin={"pca9554": None, "number": 4},
|
||||
requires={"psram", "pca9554"},
|
||||
de_pin=18,
|
||||
hsync_pin=16,
|
||||
vsync_pin=17,
|
||||
@@ -134,6 +130,7 @@ st7701s.extend(
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
requires={"psram"},
|
||||
cs_pin=18,
|
||||
reset_pin=8,
|
||||
de_pin=17,
|
||||
@@ -177,6 +174,7 @@ st7701s.extend(
|
||||
width=480,
|
||||
height=480,
|
||||
pixel_mode="18bit",
|
||||
requires={"psram"},
|
||||
cs_pin=21,
|
||||
de_pin=39,
|
||||
vsync_pin=48,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from . import RgbDriverChip
|
||||
|
||||
# fmt: off
|
||||
sunton = DriverChip(
|
||||
sunton = RgbDriverChip(
|
||||
"ESP32-8048S070",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="12.5MHz",
|
||||
requires={"psram"},
|
||||
de_pin=41,
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
@@ -28,7 +27,6 @@ sunton = DriverChip(
|
||||
|
||||
sunton.extend(
|
||||
"ESP32-8048S050",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from esphome.components.mipi import DriverChip, delay
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from esphome.components.mipi import delay
|
||||
|
||||
from . import RgbDriverChip
|
||||
from .st7701s import st7701s
|
||||
|
||||
# fmt: off
|
||||
wave_4_3 = DriverChip(
|
||||
wave_4_3 = RgbDriverChip(
|
||||
"ESP32-S3-TOUCH-LCD-4.3",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="16MHz",
|
||||
reset_pin={"ch422g": None, "number": 3},
|
||||
enable_pin={"ch422g": None, "number": 2},
|
||||
requires={"psram", "ch422g"},
|
||||
de_pin=5,
|
||||
hsync_pin={"number": 46, "ignore_strapping_warning": True},
|
||||
vsync_pin={"number": 3, "ignore_strapping_warning": True},
|
||||
@@ -69,6 +69,7 @@ st7701s.extend(
|
||||
pclk_pin=41,
|
||||
pclk_frequency="12MHz",
|
||||
pclk_inverted=False,
|
||||
requires={"psram"},
|
||||
data_pins={
|
||||
"red": [46, 3, 8, 18, 17],
|
||||
"green": [14, 13, 12, 11, 10, 9],
|
||||
@@ -80,6 +81,7 @@ st7701s.extend(
|
||||
"WAVESHARE-3.16-320X820",
|
||||
width=320,
|
||||
height=820,
|
||||
requires={"psram"},
|
||||
de_pin=40,
|
||||
hsync_pin=38,
|
||||
vsync_pin=39,
|
||||
|
||||
@@ -41,14 +41,11 @@ from esphome.const import (
|
||||
CONF_DATA_RATE,
|
||||
CONF_DC_PIN,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_DISABLED,
|
||||
CONF_ENABLE_PIN,
|
||||
CONF_ID,
|
||||
CONF_INIT_SEQUENCE,
|
||||
CONF_INVERT_COLORS,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_RESET_PIN,
|
||||
CONF_ROTATION,
|
||||
@@ -138,16 +135,7 @@ def denominator(config):
|
||||
def model_schema(config):
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
bus_mode = config[CONF_BUS_MODE]
|
||||
transform = cv.Any(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MIRROR_X): cv.boolean,
|
||||
cv.Required(CONF_MIRROR_Y): cv.boolean,
|
||||
**model.swap_xy_schema(),
|
||||
}
|
||||
),
|
||||
cv.one_of(CONF_DISABLED, lower=True),
|
||||
)
|
||||
transform = model.transform_schema()
|
||||
# CUSTOM model will need to provide a custom init sequence
|
||||
iseqconf = (
|
||||
cv.Required(CONF_INIT_SEQUENCE)
|
||||
@@ -265,6 +253,7 @@ def customise_schema(config):
|
||||
extra=ALLOW_EXTRA,
|
||||
)(config)
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
model.check_requirements()
|
||||
bus_modes = (TYPE_SINGLE, TYPE_QUAD, TYPE_OCTAL)
|
||||
config = cv.Schema(
|
||||
{
|
||||
@@ -408,7 +397,7 @@ def get_instance(config):
|
||||
async def to_code(config):
|
||||
model = MODELS[config[CONF_MODEL]]
|
||||
var_id = config[CONF_ID]
|
||||
init_sequence = model.get_sequence(config, False)
|
||||
init_sequence = model.get_sequence(config, add_madctl=False, add_reset=True)
|
||||
var_id.type, templateargs = get_instance(config)
|
||||
var = cg.new_Pvariable(var_id, TemplateArguments(*templateargs))
|
||||
cg.add(var.set_init_sequence(init_sequence))
|
||||
|
||||
@@ -13,6 +13,8 @@ constexpr static const char *const TAG = "display.mipi_spi";
|
||||
|
||||
// Maximum bytes to log for commands (truncated if larger)
|
||||
static constexpr size_t MIPI_SPI_MAX_CMD_LOG_BYTES = 64;
|
||||
|
||||
// Command codes for MIPI SPI displays. Not all currently used, kept here for reference.
|
||||
static constexpr uint8_t SW_RESET_CMD = 0x01;
|
||||
static constexpr uint8_t SLEEP_OUT = 0x11;
|
||||
static constexpr uint8_t NORON = 0x13;
|
||||
@@ -151,14 +153,11 @@ class MipiSpi : public display::Display,
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(5);
|
||||
this->reset_pin_->digital_write(true);
|
||||
} else {
|
||||
// no reset pin, send software reset command
|
||||
this->write_command_(SW_RESET_CMD);
|
||||
// required delay after reset is already in the init sequence, don't duplicate
|
||||
}
|
||||
|
||||
// need to know when the display is ready for SLPOUT command - will be 120ms after reset
|
||||
auto when = millis() + 120;
|
||||
delay(10);
|
||||
size_t index = 0;
|
||||
auto &vec = this->init_sequence_;
|
||||
while (index != vec.size()) {
|
||||
@@ -170,6 +169,9 @@ class MipiSpi : public display::Display,
|
||||
uint8_t cmd = vec[index++];
|
||||
uint8_t x = vec[index++];
|
||||
if (x == DELAY_FLAG) {
|
||||
if (cmd == 0) {
|
||||
cmd = clamp_at_least((int) (when - millis()), 0);
|
||||
}
|
||||
esph_log_d(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
@@ -179,24 +181,9 @@ class MipiSpi : public display::Display,
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
switch (cmd) {
|
||||
case SLEEP_OUT: {
|
||||
// are we ready, boots?
|
||||
int duration = when - millis();
|
||||
if (duration > 0) {
|
||||
esph_log_d(TAG, "Sleep %dms", duration);
|
||||
delay(duration);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const auto *ptr = vec.data() + index;
|
||||
this->write_command_(cmd, ptr, num_args);
|
||||
index += num_args;
|
||||
if (cmd == SLEEP_OUT)
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
this->reset_params_();
|
||||
|
||||
@@ -13,6 +13,7 @@ ST7789V.extend(
|
||||
mirror_x=True,
|
||||
mirror_y=True,
|
||||
data_rate="80MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
ST7789V.extend(
|
||||
@@ -25,4 +26,5 @@ ST7789V.extend(
|
||||
dc_pin=39,
|
||||
reset_pin=40,
|
||||
invert_colors=True,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ from esphome.components.mipi import (
|
||||
delay,
|
||||
)
|
||||
from esphome.components.spi import TYPE_QUAD
|
||||
from esphome.config_validation import UNDEFINED
|
||||
from esphome.const import CONF_MIRROR_X, CONF_MIRROR_Y
|
||||
|
||||
DriverChip(
|
||||
"T-DISPLAY-S3-AMOLED",
|
||||
@@ -29,6 +29,7 @@ DriverChip(
|
||||
brightness=0xD0,
|
||||
color_order=MODE_RGB,
|
||||
no_slpout=True, # SLPOUT is in the init sequence, early
|
||||
requires={"psram"},
|
||||
initsequence=(SLPOUT,),
|
||||
)
|
||||
|
||||
@@ -43,6 +44,7 @@ DriverChip(
|
||||
data_rate="40MHz",
|
||||
brightness=0xD0,
|
||||
color_order=MODE_RGB,
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(PAGESEL, 4),
|
||||
(0x6A, 0x00),
|
||||
@@ -90,6 +92,7 @@ T4_S3_AMOLED = RM690B0.extend(
|
||||
reset_pin=13,
|
||||
enable_pin=9,
|
||||
bus_mode=TYPE_QUAD,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
CO5300 = DriverChip(
|
||||
@@ -98,7 +101,7 @@ CO5300 = DriverChip(
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
no_slpout=True,
|
||||
swap_xy=UNDEFINED,
|
||||
transforms={CONF_MIRROR_X, CONF_MIRROR_Y},
|
||||
width=480,
|
||||
height=480,
|
||||
initsequence=(
|
||||
|
||||
@@ -314,6 +314,7 @@ DriverChip(
|
||||
data_rate="40MHz",
|
||||
dc_pin=4,
|
||||
cs_pin=5,
|
||||
requires={"psram"},
|
||||
# reset_pin={CONF_INVERTED: True, CONF_NUMBER: 48},
|
||||
initsequence=(
|
||||
(0xEF, 0x03, 0x80, 0x02),
|
||||
@@ -379,6 +380,7 @@ DriverChip(
|
||||
cs_pin=5,
|
||||
dc_pin=4,
|
||||
reset_pin=48,
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(0xEF, 0x03, 0x80, 0x02),
|
||||
(0xCF, 0x00, 0xC1, 0x30),
|
||||
@@ -711,6 +713,7 @@ ST7796.extend(
|
||||
reset_pin=4,
|
||||
dc_pin={"number": 0, "ignore_strapping_warning": True},
|
||||
invert_colors=True,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
ST7789V.extend(
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
from esphome.components.mipi import MODE_RGB, DriverChip
|
||||
from esphome.components.spi import TYPE_QUAD
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_IGNORE_STRAPPING_WARNING, CONF_NUMBER
|
||||
from esphome.const import (
|
||||
CONF_IGNORE_STRAPPING_WARNING,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_NUMBER,
|
||||
)
|
||||
|
||||
AXS15231 = DriverChip(
|
||||
"AXS15231",
|
||||
draw_rounding=8,
|
||||
swap_xy=cv.UNDEFINED,
|
||||
transforms={CONF_MIRROR_X, CONF_MIRROR_Y},
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
no_swreset=True,
|
||||
initsequence=(
|
||||
(0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5),
|
||||
(0xC1, 0x33),
|
||||
@@ -22,6 +27,7 @@ AXS15231.extend(
|
||||
height=480,
|
||||
cs_pin={CONF_NUMBER: 45, CONF_IGNORE_STRAPPING_WARNING: True},
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
DriverChip(
|
||||
@@ -36,6 +42,7 @@ DriverChip(
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(0xF0, 0x08),
|
||||
(0xF2, 0x08),
|
||||
@@ -267,6 +274,7 @@ DriverChip(
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(0xF0, 0x28),
|
||||
(0xF2, 0x28),
|
||||
@@ -495,6 +503,7 @@ DriverChip(
|
||||
color_order=MODE_RGB,
|
||||
bus_mode=TYPE_QUAD,
|
||||
data_rate="20MHz",
|
||||
requires={"psram"},
|
||||
initsequence=(
|
||||
(0xFF, 0xA5),
|
||||
(0x41, 0x03),
|
||||
|
||||
@@ -10,4 +10,5 @@ ST7789V.extend(
|
||||
cs_pin=22,
|
||||
dc_pin=21,
|
||||
reset_pin=18,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ ST7789V.extend(
|
||||
dc_pin=13,
|
||||
reset_pin=9,
|
||||
data_rate="80MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
ST7789V.extend(
|
||||
@@ -42,6 +43,7 @@ ST7789V.extend(
|
||||
enable_pin=[9, 15],
|
||||
data_rate="10MHz",
|
||||
bus_mode=TYPE_OCTAL,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
ST7796.extend(
|
||||
@@ -55,4 +57,5 @@ ST7796.extend(
|
||||
dc_pin=9,
|
||||
backlight_pin=48,
|
||||
invert_colors=True,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ ILI9341.extend(
|
||||
invert_colors=True,
|
||||
pixel_mode="18bit",
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
GC9107 = ST7789V.extend(
|
||||
@@ -68,4 +69,5 @@ GC9107.extend(
|
||||
reset_pin=48,
|
||||
dc_pin=42,
|
||||
cs_pin=14,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from esphome.components.mipi import (
|
||||
PWSET,
|
||||
DriverChip,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MIRROR_X, CONF_MIRROR_Y
|
||||
|
||||
from .amoled import CO5300
|
||||
from .ili import ILI9488_A, ST7789V
|
||||
@@ -155,7 +155,7 @@ ST7789P = DriverChip(
|
||||
|
||||
ILI9488_A.extend(
|
||||
"PICO-RESTOUCH-LCD-3.5",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
transforms={CONF_MIRROR_X, CONF_MIRROR_Y},
|
||||
spi_16=True,
|
||||
pixel_mode="16bit",
|
||||
mirror_x=True,
|
||||
@@ -175,6 +175,7 @@ CO5300.extend(
|
||||
offset_width=6,
|
||||
cs_pin=12,
|
||||
reset_pin=39,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
# Waveshare ESP32-S3 Touch AMOLED 2.16" (CO5300 controller)
|
||||
@@ -189,6 +190,7 @@ CO5300.extend(
|
||||
cs_pin=12,
|
||||
reset_pin=39,
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
AXS15231.extend(
|
||||
@@ -198,6 +200,7 @@ AXS15231.extend(
|
||||
data_rate="80MHz",
|
||||
cs_pin=9,
|
||||
reset_pin=21,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
# Waveshare 1.83-v2
|
||||
@@ -281,6 +284,7 @@ ST7789V.extend(
|
||||
offset_height=40,
|
||||
invert_colors=True,
|
||||
data_rate="40MHz",
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
CO5300.extend(
|
||||
@@ -291,4 +295,5 @@ CO5300.extend(
|
||||
cs_pin=9,
|
||||
reset_pin=21,
|
||||
enable_pin=1,
|
||||
requires={"psram"},
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user