diff --git a/esphome/components/mcp4461/mcp4461.cpp b/esphome/components/mcp4461/mcp4461.cpp index 43fa59f746..be6054fa5f 100644 --- a/esphome/components/mcp4461/mcp4461.cpp +++ b/esphome/components/mcp4461/mcp4461.cpp @@ -8,6 +8,7 @@ namespace esphome { namespace mcp4461 { static const char *const TAG = "mcp4461"; +constexpr uint8_t EEPROM_WRITE_TIMEOUT_MS = 10; void Mcp4461Component::setup() { ESP_LOGCONFIG(TAG, "Setting up mcp4461 (0x%02" PRIX8 ")...", this->address_); @@ -20,34 +21,90 @@ void Mcp4461Component::setup() { } void Mcp4461Component::begin_() { + // save WP/WL status + this->set_write_protection_status_(); + this->previous_write_exec_time_ = 0; for (uint8_t i = 0; i < 8; i++) { if (this->reg_[i].enabled) { this->reg_[i].state = this->read_wiper_level_(i); + } else { + // only volatile wipers can be disabled + if (i < 4) { + this->reg_[i].state = 0; + Mcp4461WiperIdx wiper_idx; + wiper_idx = static_cast(i); + this->disable_wiper(wiper_idx); + } } } } +void Mcp4461Component::set_write_protection_status_() { + uint8_t status_register_value; + status_register_value = this->get_status_register(); + this->write_protected_ = static_cast((status_register_value >> 0) & 0x01); + this->reg_[0].wiper_lock_active = static_cast((status_register_value >> 2) & 0x01); + this->reg_[1].wiper_lock_active = static_cast((status_register_value >> 3) & 0x01); + this->reg_[2].wiper_lock_active = static_cast((status_register_value >> 5) & 0x01); + this->reg_[3].wiper_lock_active = static_cast((status_register_value >> 6) & 0x01); +} + void Mcp4461Component::dump_config() { ESP_LOGCONFIG(TAG, "mcp4461:"); LOG_I2C_DEVICE(this); + // log wiper status for (uint8_t i = 0; i < 8; ++i) { - ESP_LOGCONFIG(TAG, "Wiper [%" PRIu8 "] level: %" PRIu16, i, this->reg_[i].state); // terminals only valid for volatile wipers 0-3 - enable/disable is terminal hw // so also invalid for nonvolatile. For these, only print current level. + // reworked to be a one-line intentionally, as output would not be in order if (i < 4) { - ESP_LOGCONFIG(TAG, " ├── Status: %s", ONOFF(this->reg_[i].enabled)); - ESP_LOGCONFIG(TAG, " ├── Terminal A: %s", ONOFF(this->reg_[i].terminal_a)); - ESP_LOGCONFIG(TAG, " ├── Terminal B: %s", ONOFF(this->reg_[i].terminal_b)); - ESP_LOGCONFIG(TAG, " ├── Terminal W: %s", ONOFF(this->reg_[i].terminal_w)); - ESP_LOGCONFIG(TAG, " └── Terminal HW: %s", ONOFF(this->reg_[i].terminal_hw)); + ESP_LOGCONFIG(TAG, " ├── Volatile wiper [%" PRIu8 "] level: %" PRIu16 ", Status: %s, HW: %s, A: %s, B: %s, W: %s", + i, + this->reg_[i].state, + ONOFF(this->reg_[i].terminal_hw), + ONOFF(this->reg_[i].terminal_a), + ONOFF(this->reg_[i].terminal_b), + ONOFF(this->reg_[i].terminal_w), + ONOFF(this->reg_[i].enabled)); + } else { + ESP_LOGCONFIG(TAG, " ├── Nonvolatile wiper [%" PRIu8 "] level: %" PRIu16 "", i, this->reg_[i].state); } } + // log current device status register at start + // from datasheet: + // (1) means, bit is hard-locked to value 1 + // Bit 0 is WP status (=>pin) + // Bit 1 is named "R-1"-pin in datasheet an declared "reserved" and forced/locked to 1 + // Bit 2 is WiperLock-Status resistor-network 0 + // Bit 3 is WiperLock-Status resistor-network 1 + // Bit 4 is EEPROM-Write-Active-Status bit + // Bit 5 is WiperLock-Status resistor-network 2 + // Bit 6 is WiperLock-Status resistor-network 3 + // Bit 7+8 are referenced in datasheet as D7 + D8 and both locked to 1 + // Default status register reading should be 0x182h or 386 decimal + // "Default" means without any WiperLocks or WriteProtection enabled and EEPRom not active writing + // get_status_register() will automatically check, if D8, D7 & R1 bits (locked to 1) are 1 and bail out using error-routine otherwise + uint8_t status_register_value; + status_register_value = this->get_status_register(); + ESP_LOGCONFIG(TAG, " └── Status register: D7: %" PRIu8 ", WL3: %" PRIu8 ", WL2: %" PRIu8 ", EEWA: %" PRIu8 ", WL1: %" PRIu8 ", WL0: %" PRIu8 ", R1: %" PRIu8 ", WP: %" PRIu8 "", + ((status_register_value >> 7) & 0x01), + ((status_register_value >> 6) & 0x01), + ((status_register_value >> 5) & 0x01), + ((status_register_value >> 4) & 0x01), + ((status_register_value >> 3) & 0x01), + ((status_register_value >> 2) & 0x01), + ((status_register_value >> 1) & 0x01), + ((status_register_value >> 0) & 0x01) + ); if (this->is_failed()) { ESP_LOGE(TAG, "Communication with mcp4461 failed!"); } } void Mcp4461Component::loop() { + if (status_has_warning()) { + this->get_status_register(); + } if (this->update_) { uint8_t i; for (i = 0; i < 8; i++) { @@ -76,21 +133,32 @@ void Mcp4461Component::loop() { } } -uint16_t Mcp4461Component::get_status_register() { +uint8_t Mcp4461Component::get_status_register() { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Returning 0"); + return 0; + } uint8_t reg = 0; reg |= static_cast(Mcp4461Addresses::MCP4461_STATUS); reg |= static_cast(Mcp4461Commands::READ); uint16_t buf; if (!this->read_byte_16(reg, &buf)) { - this->status_set_warning(); - ESP_LOGW(TAG, "Error fetching status register value"); + this->mark_failed(); + ESP_LOGE(TAG, "Error fetching status register value"); return 0; } - return buf; + uint8_t msb = buf >> 8; + uint8_t lsb; + lsb = static_cast(buf & 0x00ff); + if (msb != 1 || ((lsb >> 7) & 0x01) != 1 || ((lsb >> 1) & 0x01) != 1) { + // D8, D7 and R1 bits are hardlocked to 1 -> a status msb bit 0 (bit 9 of status register) of 0 or lsb bit 1/7 = 0 indicate device/communication issues, therefore mark component failed + this->mark_failed(); + return 0; + } + this->status_clear_warning(); + return lsb; } -bool Mcp4461Component::is_writing_() { return static_cast((this->get_status_register() >> 4) & 0x01); } - uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) { uint8_t addr; bool nonvolatile = false; @@ -112,7 +180,7 @@ uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) { addr = static_cast(Mcp4461Addresses::MCP4461_VW3); break; default: - ESP_LOGE(TAG, "unknown wiper specified"); + ESP_LOGW(TAG, "unknown wiper specified"); return 0; } if (nonvolatile) { @@ -122,6 +190,10 @@ uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) { } uint16_t Mcp4461Component::get_wiper_level(Mcp4461WiperIdx wiper) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Returning 0"); + return 0; + } uint8_t wiper_idx = static_cast(wiper); if (!(this->reg_[wiper_idx].enabled)) { ESP_LOGW(TAG, "reading from disabled volatile wiper %" PRIu8 ", returning 0", wiper_idx); @@ -136,12 +208,11 @@ uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper) { reg |= this->get_wiper_address_(wiper); reg |= static_cast(Mcp4461Commands::READ); if (wiper > 3) { - while (this->is_writing_()) { - ESP_LOGVV(TAG, "delaying during eeprom write"); - yield(); + if(this->is_eeprom_busy_()) { + return 0; } } - if (!this->read_byte_16(reg, &buf)) { + if (!(this->read_byte_16(reg, &buf))) { this->status_set_warning(); ESP_LOGW(TAG, "Error fetching %swiper %" PRIu8 " value", (wiper > 3) ? "nonvolatile " : "", wiper); return 0; @@ -158,26 +229,44 @@ void Mcp4461Component::update_wiper_level(Mcp4461WiperIdx wiper) { } void Mcp4461Component::set_wiper_level(Mcp4461WiperIdx wiper, uint16_t value) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting write."); + return; + } uint8_t wiper_idx = static_cast(wiper); + if (this->reg_[wiper_idx].wiper_lock_active) { + ESP_LOGW(TAG, "Ignoring request to set the state for wiper %" PRIu8 " as it is locked by WiperLock", wiper_idx); + return; + } if (value > 0x100) { ESP_LOGW(TAG, "ignoring invalid wiper level %" PRIu16 "!"); return; } + if (!(this->reg_[wiper_idx].enabled)) { + ESP_LOGW(TAG, "writing to disabled volatile wiper %" PRIu8 " is prohibited", wiper_idx); + return; + } ESP_LOGV(TAG, "Setting MCP4461 wiper %" PRIu8 " to %" PRIu16 "!", wiper_idx, value); this->reg_[wiper_idx].state = value; this->update_ = true; } void Mcp4461Component::write_wiper_level_(uint8_t wiper, uint16_t value) { - uint8_t wiper_idx = static_cast(wiper); bool nonvolatile = false; - if (wiper_idx > 3) { + if (wiper > 3) { nonvolatile = true; } - this->mcp4461_write_(this->get_wiper_address_(wiper_idx), value, nonvolatile); + if (!(this->mcp4461_write_(this->get_wiper_address_(wiper), value, nonvolatile))) { + ESP_LOGW(TAG, "Error writing %swiper %" PRIu8 " level %" PRIu16 "", (wiper > 3) ? "nonvolatile " : "", wiper, value); + this->status_set_warning(); + } } void Mcp4461Component::enable_wiper(Mcp4461WiperIdx wiper) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(wiper); ESP_LOGV(TAG, "Enabling wiper %" PRIu8, wiper_idx); this->reg_[wiper_idx].terminal_hw = true; @@ -185,34 +274,72 @@ void Mcp4461Component::enable_wiper(Mcp4461WiperIdx wiper) { } void Mcp4461Component::disable_wiper(Mcp4461WiperIdx wiper) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(wiper); ESP_LOGV(TAG, "Disabling wiper %" PRIu8, wiper_idx); this->reg_[wiper_idx].terminal_hw = false; this->update_ = true; } -void Mcp4461Component::increase_wiper(Mcp4461WiperIdx wiper) { +bool Mcp4461Component::increase_wiper(Mcp4461WiperIdx wiper) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return false; + } uint8_t wiper_idx = static_cast(wiper); + if (!(this->reg_[wiper_idx].enabled)) { + ESP_LOGW(TAG, "increasing disabled volatile wiper %" PRIu8 " is prohibited", wiper_idx); + return false; + } + if (this->reg_[wiper_idx].wiper_lock_active) { + ESP_LOGW(TAG, "Ignoring request to increase wiper %" PRIu8 " as it is locked by WiperLock", wiper_idx); + return false; + } ESP_LOGV(TAG, "Increasing wiper %" PRIu8 "", wiper_idx); uint8_t reg = 0; uint8_t addr; addr = this->get_wiper_address_(wiper_idx); reg |= addr; reg |= static_cast(Mcp4461Commands::INCREMENT); - this->write(&this->address_, reg, sizeof(reg)); + auto err = this->write(&this->address_, reg, sizeof(reg)); + if (err != i2c::ERROR_OK) { + this->status_set_warning(); + return false; + } this->reg_[wiper_idx].state++; + return true; } -void Mcp4461Component::decrease_wiper(Mcp4461WiperIdx wiper) { +bool Mcp4461Component::decrease_wiper(Mcp4461WiperIdx wiper) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return false; + } uint8_t wiper_idx = static_cast(wiper); + if (!(this->reg_[wiper_idx].enabled)) { + ESP_LOGW(TAG, "decreasing disabled volatile wiper %" PRIu8 " is prohibited", wiper_idx); + return false; + } + if (this->reg_[wiper_idx].wiper_lock_active) { + ESP_LOGW(TAG, "Ignoring request to decrease wiper %" PRIu8 " as it is locked by WiperLock", wiper_idx); + return false; + } ESP_LOGV(TAG, "Decreasing wiper %" PRIu8 "", wiper_idx); uint8_t reg = 0; uint8_t addr; addr = this->get_wiper_address_(wiper_idx); reg |= addr; reg |= static_cast(Mcp4461Commands::DECREMENT); - this->write(&this->address_, reg, sizeof(reg)); + auto err = this->write(&this->address_, reg, sizeof(reg)); + if (err != i2c::ERROR_OK) { + this->status_set_warning(); + return false; + } this->reg_[wiper_idx].state--; + return true; } uint8_t Mcp4461Component::calc_terminal_connector_byte_(Mcp4461TerminalIdx terminal_connector) { @@ -240,6 +367,10 @@ uint8_t Mcp4461Component::calc_terminal_connector_byte_(Mcp4461TerminalIdx termi } uint8_t Mcp4461Component::get_terminal_register(Mcp4461TerminalIdx terminal_connector) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Returning 0"); + return 0; + } uint8_t reg = 0; if (static_cast(terminal_connector) == 0) { reg |= static_cast(Mcp4461Addresses::MCP4461_TCON0); @@ -248,12 +379,13 @@ uint8_t Mcp4461Component::get_terminal_register(Mcp4461TerminalIdx terminal_conn } reg |= static_cast(Mcp4461Commands::READ); uint16_t buf; - if (!this->read_byte_16(reg, &buf)) { + if (this->read_byte_16(reg, &buf)) { + return static_cast(buf & 0x00ff); + } else { this->status_set_warning(); ESP_LOGW(TAG, "Error fetching terminal register value"); return 0; } - return static_cast(buf & 0x00ff); } void Mcp4461Component::update_terminal_register(Mcp4461TerminalIdx terminal_connector) { @@ -280,7 +412,11 @@ void Mcp4461Component::update_terminal_register(Mcp4461TerminalIdx terminal_conn this->reg_[(wiper_index + 1)].terminal_hw = ((terminal_data >> 7) & 0x01); } -void Mcp4461Component::set_terminal_register(Mcp4461TerminalIdx terminal_connector, uint8_t data) { +bool Mcp4461Component::set_terminal_register(Mcp4461TerminalIdx terminal_connector, uint8_t data) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return false; + } uint8_t addr; if (static_cast(terminal_connector) == 0) { addr = static_cast(Mcp4461Addresses::MCP4461_TCON0); @@ -288,12 +424,20 @@ void Mcp4461Component::set_terminal_register(Mcp4461TerminalIdx terminal_connect addr = static_cast(Mcp4461Addresses::MCP4461_TCON1); } else { ESP_LOGW(TAG, "Invalid terminal connector id %" PRIu8 " specified", static_cast(terminal_connector)); - return; + return false; } - this->mcp4461_write_(addr, data); + if (!(this->mcp4461_write_(addr, data))) { + this->status_set_warning(); + return false; + } + return true; } void Mcp4461Component::enable_terminal(Mcp4461WiperIdx wiper, char terminal) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(wiper); if (wiper_idx > 3) { return; @@ -313,7 +457,6 @@ void Mcp4461Component::enable_terminal(Mcp4461WiperIdx wiper, char terminal) { this->reg_[wiper_idx].terminal_w = true; break; default: - this->status_set_warning(); ESP_LOGW(TAG, "Unknown terminal %c specified", terminal); return; } @@ -321,6 +464,10 @@ void Mcp4461Component::enable_terminal(Mcp4461WiperIdx wiper, char terminal) { } void Mcp4461Component::disable_terminal(Mcp4461WiperIdx wiper, char terminal) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(wiper); if (wiper_idx > 3) { return; @@ -340,7 +487,6 @@ void Mcp4461Component::disable_terminal(Mcp4461WiperIdx wiper, char terminal) { this->reg_[wiper_idx].terminal_w = false; break; default: - this->status_set_warning(); ESP_LOGW(TAG, "Unknown terminal %c specified", terminal); return; } @@ -348,10 +494,17 @@ void Mcp4461Component::disable_terminal(Mcp4461WiperIdx wiper, char terminal) { } uint16_t Mcp4461Component::get_eeprom_value(Mcp4461EepromLocation location) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Returning 0"); + return 0; + } uint8_t reg = 0; reg |= static_cast(Mcp4461EepromLocation::MCP4461_EEPROM_1) + (static_cast(location) * 0x10); reg |= static_cast(Mcp4461Commands::READ); uint16_t buf; + if(this->is_eeprom_busy_()) { + return 0; + } if (!this->read_byte_16(reg, &buf)) { this->status_set_warning(); ESP_LOGW(TAG, "Error fetching EEPRom location value"); @@ -360,19 +513,42 @@ uint16_t Mcp4461Component::get_eeprom_value(Mcp4461EepromLocation location) { return buf; } -void Mcp4461Component::set_eeprom_value(Mcp4461EepromLocation location, uint16_t value) { +bool Mcp4461Component::set_eeprom_value(Mcp4461EepromLocation location, uint16_t value) { + if (this->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return false; + } uint8_t addr = 0; if (value > 511) { - return; + return false; } if (value > 256) { addr = 1; } addr |= static_cast(Mcp4461EepromLocation::MCP4461_EEPROM_1) + (static_cast(location) * 0x10); - this->mcp4461_write_(addr, value, true); + if (!(this->mcp4461_write_(addr, value, true))) { + this->status_set_warning(); + return false; + } + return true; } -void Mcp4461Component::mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolatile) { +bool Mcp4461Component::is_writing_() { return static_cast((this->get_status_register() >> 4) & 0x01); } + +bool Mcp4461Component::is_eeprom_busy_() { + while (this->is_writing_() && this->previous_write_exec_time_ != 0) { + if ((millis() - this->previous_write_exec_time_) > EEPROM_WRITE_TIMEOUT_MS) { + this->previous_write_exec_time_ = millis(); + ESP_LOGE(TAG, "EEPROM write timeout exceeded (%" PRIu8 " ms), aborting read/write from/to nonvolatile wiper/eeprom", EEPROM_WRITE_TIMEOUT_MS); + return true; + } + ESP_LOGV(TAG, "Waiting while eeprom is busy"); + yield(); + } + return false; +} + +bool Mcp4461Component::mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolatile) { uint8_t reg = 0; if (data > 0xFF) { reg = 1; @@ -383,12 +559,16 @@ void Mcp4461Component::mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolat reg |= addr; reg |= static_cast(Mcp4461Commands::WRITE); if (nonvolatile) { - while (this->is_writing_()) { - ESP_LOGV(TAG, "Waiting while eeprom busy"); - yield(); + if (this->write_protected_) { + ESP_LOGW(TAG, "Ignoring write to write protected chip"); + return false; } + if (this->is_eeprom_busy_()) { + return false; + } + this->previous_write_exec_time_ = millis(); } - this->write_byte(reg, value_byte); + return this->write_byte(reg, value_byte); } } // namespace mcp4461 } // namespace esphome diff --git a/esphome/components/mcp4461/mcp4461.h b/esphome/components/mcp4461/mcp4461.h index e94c143b94..a13f3751dc 100644 --- a/esphome/components/mcp4461/mcp4461.h +++ b/esphome/components/mcp4461/mcp4461.h @@ -11,8 +11,9 @@ struct WiperState { bool terminal_b = true; bool terminal_w = true; bool terminal_hw = true; - uint16_t state; + uint16_t state = 0; bool enabled = true; + bool wiper_lock_active = false; }; enum class Mcp4461Defaults : uint8_t { WIPER_VALUE = 0x80 }; @@ -56,11 +57,11 @@ class Mcp4461Wiper; class Mcp4461Component : public Component, public i2c::I2CDevice { public: Mcp4461Component(bool disable_wiper_0, bool disable_wiper_1, bool disable_wiper_2, bool disable_wiper_3) - : wiper_0_enabled_(false), wiper_1_enabled_(false), wiper_2_enabled_(false), wiper_3_enabled_(false) { - this->reg_[0].enabled = this->wiper_0_enabled_; - this->reg_[1].enabled = this->wiper_1_enabled_; - this->reg_[2].enabled = this->wiper_2_enabled_; - this->reg_[3].enabled = this->wiper_3_enabled_; + : wiper_0_disabled_(disable_wiper_0), wiper_1_disabled_(disable_wiper_1), wiper_2_disabled_(disable_wiper_2), wiper_3_disabled_(disable_wiper_3) { + this->reg_[0].enabled = !wiper_0_disabled_; + this->reg_[1].enabled = !wiper_1_disabled_; + this->reg_[2].enabled = !wiper_2_disabled_; + this->reg_[3].enabled = !wiper_3_disabled_; } void setup() override; @@ -68,38 +69,42 @@ class Mcp4461Component : public Component, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override; - uint16_t get_status_register(); + uint8_t get_status_register(); uint16_t get_wiper_level(Mcp4461WiperIdx wiper); void set_wiper_level(Mcp4461WiperIdx wiper, uint16_t value); void update_wiper_level(Mcp4461WiperIdx wiper); void enable_wiper(Mcp4461WiperIdx wiper); void disable_wiper(Mcp4461WiperIdx wiper); - void increase_wiper(Mcp4461WiperIdx wiper); - void decrease_wiper(Mcp4461WiperIdx wiper); + bool increase_wiper(Mcp4461WiperIdx wiper); + bool decrease_wiper(Mcp4461WiperIdx wiper); void enable_terminal(Mcp4461WiperIdx wiper, char terminal); void disable_terminal(Mcp4461WiperIdx, char terminal); void update_terminal_register(Mcp4461TerminalIdx terminal_connector); uint8_t get_terminal_register(Mcp4461TerminalIdx terminal_connector); - void set_terminal_register(Mcp4461TerminalIdx terminal_connector, uint8_t data); + bool set_terminal_register(Mcp4461TerminalIdx terminal_connector, uint8_t data); uint16_t get_eeprom_value(Mcp4461EepromLocation location); - void set_eeprom_value(Mcp4461EepromLocation location, uint16_t value); + bool set_eeprom_value(Mcp4461EepromLocation location, uint16_t value); protected: friend class Mcp4461Wiper; + void set_write_protection_status_(); bool is_writing_(); + bool is_eeprom_busy_(); uint8_t get_wiper_address_(uint8_t wiper); uint16_t read_wiper_level_(uint8_t wiper); void write_wiper_level_(uint8_t wiper, uint16_t value); - void mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolatile = false); + bool mcp4461_write_(uint8_t addr, uint16_t data, bool nonvolatile = false); uint8_t calc_terminal_connector_byte_(Mcp4461TerminalIdx terminal_connector); WiperState reg_[8]; void begin_(); bool update_{false}; - bool wiper_0_enabled_{true}; - bool wiper_1_enabled_{true}; - bool wiper_2_enabled_{true}; - bool wiper_3_enabled_{true}; + uint32_t previous_write_exec_time_; + bool write_protected_{false}; + bool wiper_0_disabled_{false}; + bool wiper_1_disabled_{false}; + bool wiper_2_disabled_{false}; + bool wiper_3_disabled_{false}; }; } // namespace mcp4461 } // namespace esphome diff --git a/esphome/components/mcp4461/output/__init__.py b/esphome/components/mcp4461/output/__init__.py index 36b249b5bc..ea15a7598a 100644 --- a/esphome/components/mcp4461/output/__init__.py +++ b/esphome/components/mcp4461/output/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import output -from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.const import CONF_CHANNEL, CONF_ID, CONF_INITIAL_VALUE from .. import Mcp4461Component, CONF_MCP4461_ID, mcp4461_ns DEPENDENCIES = ["mcp4461"] @@ -24,6 +24,7 @@ CONF_ENABLE = "enable" CONF_TERMINAL_A = "terminal_a" CONF_TERMINAL_B = "terminal_b" CONF_TERMINAL_W = "terminal_w" +CONF_INITIAL_VALUE = "initial_value" CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { @@ -34,6 +35,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( cv.Optional(CONF_TERMINAL_A, default=True): cv.boolean, cv.Optional(CONF_TERMINAL_B, default=True): cv.boolean, cv.Optional(CONF_TERMINAL_W, default=True): cv.boolean, + cv.Optional(CONF_INITIAL_VALUE): cv.float_range(min=0.000, max=0.256), } ) @@ -50,3 +52,6 @@ async def to_code(config): config[CONF_TERMINAL_W], ) await output.register_output(var, config) + await cg.register_parented(var, config[CONF_MCP4461_ID]) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/mcp4461/output/mcp4461_output.cpp b/esphome/components/mcp4461/output/mcp4461_output.cpp index 7e63f9dc5a..f9c0032575 100644 --- a/esphome/components/mcp4461/output/mcp4461_output.cpp +++ b/esphome/components/mcp4461/output/mcp4461_output.cpp @@ -10,6 +10,10 @@ namespace mcp4461 { static const char *const TAG = "mcp4461.output"; void Mcp4461Wiper::write_state(float state) { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(this->wiper_); ESP_LOGV(TAG, "Got value %02f from frontend", state); const float max_taps = 256.0; @@ -25,20 +29,46 @@ void Mcp4461Wiper::write_state(float state) { this->parent_->set_wiper_level(this->wiper_, taps); } +void Mcp4461Wiper::set_initial_value(float initial_value) { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } + if (initial_value >= 0.000 && initial_value <= 0.256) { + float state = initial_value * 1000; + this->initial_value_ = static_cast(state); + // Use the value + ESP_LOGCONFIG(TAG, "Setting initial value %" PRIu16 "", *this->initial_value_); + this->state_ = state; + this->parent_->set_wiper_level(this->wiper_, *this->initial_value_); + } else { + ESP_LOGCONFIG(TAG, "Invalid initial value set, retaining previous wiper level."); + } +} + uint16_t Mcp4461Wiper::get_wiper_level() { return this->parent_->get_wiper_level(this->wiper_); } void Mcp4461Wiper::save_level() { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(this->wiper_); if (wiper_idx > 3) { ESP_LOGW(TAG, "Cannot save level for nonvolatile wiper %" PRIu8 " !", wiper_idx); return; } uint8_t nonvolatile_wiper_idx = wiper_idx + 4; + this->parent_->reg_[nonvolatile_wiper_idx].state = this->parent_->reg_[wiper_idx].state; Mcp4461WiperIdx nonvolatile_wiper = static_cast(nonvolatile_wiper_idx); this->parent_->set_wiper_level(nonvolatile_wiper, this->state_); } void Mcp4461Wiper::enable_wiper() { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(this->wiper_); if (wiper_idx > 3) { ESP_LOGW(TAG, "Cannot enable nonvolatile wiper %" PRIu8 " !", wiper_idx); @@ -57,21 +87,33 @@ void Mcp4461Wiper::disable_wiper() { } void Mcp4461Wiper::increase_wiper() { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(this->wiper_); if (wiper_idx > 3) { ESP_LOGW(TAG, "Cannot increase nonvolatile wiper %" PRIu8 " !", wiper_idx); return; } - this->parent_->increase_wiper(this->wiper_); + if (this->parent_->increase_wiper(this->wiper_)) { + this->state_ = this->state_ + 1.0; + } } void Mcp4461Wiper::decrease_wiper() { + if (this->parent_->is_failed()) { + ESP_LOGW(TAG, "Parent MCP4461 component has failed! Aborting"); + return; + } uint8_t wiper_idx = static_cast(this->wiper_); if (wiper_idx > 3) { ESP_LOGW(TAG, "Cannot decrease nonvolatile wiper %" PRIu8 " !", wiper_idx); return; } - this->parent_->decrease_wiper(this->wiper_); + if (this->parent_->decrease_wiper(this->wiper_)) { + this->state_ = this->state_ - 1.0; + } } void Mcp4461Wiper::enable_terminal(char terminal) { diff --git a/esphome/components/mcp4461/output/mcp4461_output.h b/esphome/components/mcp4461/output/mcp4461_output.h index 91f00b83ff..236df85a02 100644 --- a/esphome/components/mcp4461/output/mcp4461_output.h +++ b/esphome/components/mcp4461/output/mcp4461_output.h @@ -8,7 +8,7 @@ namespace esphome { namespace mcp4461 { -class Mcp4461Wiper : public output::FloatOutput { +class Mcp4461Wiper : public output::FloatOutput, public Parented { public: Mcp4461Wiper(Mcp4461Component *parent, Mcp4461WiperIdx wiper, bool enable, bool terminal_a, bool terminal_b, bool terminal_w) @@ -20,7 +20,7 @@ class Mcp4461Wiper : public output::FloatOutput { terminal_w_(terminal_w) { uint8_t wiper_idx = static_cast(wiper); // update wiper connection state - if (!enable && wiper_idx < 4) { + if (!(this->enable_) && wiper_idx < 4) { parent->reg_[wiper_idx].enabled = false; parent->disable_terminal(wiper, 'h'); } @@ -39,6 +39,7 @@ class Mcp4461Wiper : public output::FloatOutput { void decrease_wiper(); void enable_terminal(char terminal); void disable_terminal(char terminal); + void set_initial_value(float initial_value); protected: void write_state(float state) override; @@ -46,6 +47,7 @@ class Mcp4461Wiper : public output::FloatOutput { Mcp4461WiperIdx wiper_; bool enable_; uint16_t state_; + optional initial_value_; bool terminal_a_; bool terminal_b_; bool terminal_w_;