#include "epaper_spi.h" #include #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome::epaper_spi { static const char *const TAG = "epaper_spi"; static const LogString *epaper_state_to_string(EPaperState state) { switch (state) { case EPaperState::IDLE: return LOG_STR("IDLE"); case EPaperState::UPDATE: return LOG_STR("UPDATE"); case EPaperState::RESET: return LOG_STR("RESET"); case EPaperState::INITIALISE: return LOG_STR("INITIALISE"); case EPaperState::TRANSFER_DATA: return LOG_STR("TRANSFER_DATA"); case EPaperState::POWER_ON: return LOG_STR("POWER_ON"); case EPaperState::REFRESH_SCREEN: return LOG_STR("REFRESH_SCREEN"); case EPaperState::POWER_OFF: return LOG_STR("POWER_OFF"); case EPaperState::DEEP_SLEEP: return LOG_STR("DEEP_SLEEP"); default: return LOG_STR("UNKNOWN"); } } void EPaperBase::setup() { if (!this->init_buffer_(this->get_buffer_length())) { this->mark_failed("Failed to initialise buffer"); return; } this->setup_pins_(); this->spi_setup(); } bool EPaperBase::init_buffer_(size_t buffer_length) { if (!this->buffer_.init(buffer_length)) { return false; } this->clear(); return true; } void EPaperBase::setup_pins_() { this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); if (this->reset_pin_ != nullptr) { this->reset_pin_->setup(); // OUTPUT this->reset_pin_->digital_write(true); } if (this->busy_pin_ != nullptr) { this->busy_pin_->setup(); // INPUT } } float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } void EPaperBase::command(uint8_t value) { this->start_command_(); this->write_byte(value); this->end_command_(); } void EPaperBase::data(uint8_t value) { this->start_data_(); this->write_byte(value); this->end_data_(); } // write a command followed by zero or more bytes of data. // The command is the first byte, length is the length of data only in the second byte, followed by the data. // [COMMAND, LENGTH, DATA...] void EPaperBase::cmd_data(const uint8_t *data) { const uint8_t command = data[0]; const uint8_t length = data[1]; const uint8_t *ptr = data + 2; ESP_LOGVV(TAG, "Command: 0x%02X, Length: %d, Data: %s", command, length, format_hex_pretty(ptr, length, '.', false).c_str()); this->dc_pin_->digital_write(false); this->enable(); this->write_byte(command); if (length > 0) { this->dc_pin_->digital_write(true); this->write_array(ptr, length); } this->disable(); } bool EPaperBase::is_idle_() { if (this->busy_pin_ == nullptr) { return true; } return !this->busy_pin_->digital_read(); } void EPaperBase::reset() { if (this->reset_pin_ != nullptr) { this->reset_pin_->digital_write(false); this->disable_loop(); this->set_timeout(this->reset_duration_, [this] { this->reset_pin_->digital_write(true); this->set_timeout(20, [this] { this->enable_loop(); }); }); } } void EPaperBase::update() { if (!this->state_queue_.empty()) { ESP_LOGE(TAG, "Display update already in progress - %s", LOG_STR_ARG(epaper_state_to_string(this->state_queue_.front()))); return; } this->state_queue_.push(EPaperState::UPDATE); this->state_queue_.push(EPaperState::RESET); this->state_queue_.push(EPaperState::INITIALISE); this->state_queue_.push(EPaperState::TRANSFER_DATA); this->state_queue_.push(EPaperState::POWER_ON); this->state_queue_.push(EPaperState::REFRESH_SCREEN); this->state_queue_.push(EPaperState::POWER_OFF); this->state_queue_.push(EPaperState::DEEP_SLEEP); this->state_queue_.push(EPaperState::IDLE); this->enable_loop(); } void EPaperBase::loop() { if (this->waiting_for_idle_) { if (this->is_idle_()) { this->waiting_for_idle_ = false; } else { if (App.get_loop_component_start_time() - this->waiting_for_idle_last_print_ >= 1000) { ESP_LOGV(TAG, "Waiting for idle"); this->waiting_for_idle_last_print_ = App.get_loop_component_start_time(); } return; } } auto state = this->state_queue_.front(); switch (state) { case EPaperState::IDLE: this->disable_loop(); break; case EPaperState::UPDATE: this->do_update_(); // Calls ESPHome (current page) lambda break; case EPaperState::RESET: this->reset(); break; case EPaperState::INITIALISE: this->initialise_(); break; case EPaperState::TRANSFER_DATA: if (!this->transfer_data()) { return; // Not done yet, come back next loop } break; case EPaperState::POWER_ON: this->power_on(); break; case EPaperState::REFRESH_SCREEN: this->refresh_screen(); break; case EPaperState::POWER_OFF: this->power_off(); break; case EPaperState::DEEP_SLEEP: this->deep_sleep(); break; } this->state_queue_.pop(); } void EPaperBase::start_command_() { this->dc_pin_->digital_write(false); this->enable(); } void EPaperBase::end_command_() { this->disable(); } void EPaperBase::start_data_() { this->dc_pin_->digital_write(true); this->enable(); } void EPaperBase::end_data_() { this->disable(); } void EPaperBase::on_safe_shutdown() { this->deep_sleep(); } void EPaperBase::initialise_() { size_t index = 0; const auto &sequence = this->init_sequence_; const size_t sequence_size = this->init_sequence_length_; while (index != sequence_size) { if (sequence_size - index < 2) { this->mark_failed("Malformed init sequence"); return; } const auto *ptr = sequence + index; const uint8_t length = ptr[1]; if (sequence_size - index < length + 2) { this->mark_failed("Malformed init sequence"); return; } this->cmd_data(ptr); index += length + 2; } this->power_on(); } } // namespace esphome::epaper_spi