Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: edenhaus <edenhaus@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick+github@koston.org>
215 lines
7.3 KiB
C++
215 lines
7.3 KiB
C++
#include "micronova.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome::micronova {
|
|
|
|
static const char *const TAG = "micronova";
|
|
static constexpr uint8_t STOVE_REPLY_SIZE = 2;
|
|
static constexpr uint32_t STOVE_REPLY_TIMEOUT = 200; // ms
|
|
static constexpr uint8_t WRITE_BIT = 1 << 7; // 0x80
|
|
|
|
bool MicroNovaCommand::is_write() const { return this->memory_location & WRITE_BIT; }
|
|
|
|
void MicroNovaBaseListener::dump_base_config() {
|
|
ESP_LOGCONFIG(TAG,
|
|
" Memory Location: %02X\n"
|
|
" Memory Address: %02X",
|
|
this->memory_location_, this->memory_address_);
|
|
}
|
|
|
|
void MicroNovaListener::dump_base_config() {
|
|
MicroNovaBaseListener::dump_base_config();
|
|
LOG_UPDATE_INTERVAL(this);
|
|
}
|
|
|
|
void MicroNovaListener::request_value_from_stove_() {
|
|
this->micronova_->queue_read_request(this->memory_location_, this->memory_address_);
|
|
}
|
|
|
|
void MicroNova::setup() {
|
|
this->enable_rx_pin_->setup();
|
|
this->enable_rx_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
|
this->enable_rx_pin_->digital_write(false);
|
|
}
|
|
|
|
void MicroNova::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "MicroNova:");
|
|
LOG_PIN(" Enable RX Pin: ", this->enable_rx_pin_);
|
|
}
|
|
|
|
#ifdef MICRONOVA_LISTENER_COUNT
|
|
void MicroNova::register_micronova_listener(MicroNovaListener *listener) {
|
|
this->listeners_.push_back(listener);
|
|
// Request initial value
|
|
this->queue_read_request(listener->get_memory_location(), listener->get_memory_address());
|
|
}
|
|
|
|
void MicroNova::request_update_listeners_() {
|
|
ESP_LOGD(TAG, "Requesting update from all listeners");
|
|
for (auto *listener : this->listeners_) {
|
|
this->queue_read_request(listener->get_memory_location(), listener->get_memory_address());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void MicroNova::loop() {
|
|
// Check if we're processing a command and waiting for reply
|
|
if (this->reply_pending_) {
|
|
// Check if all reply bytes have arrived
|
|
if (this->available() >= STOVE_REPLY_SIZE) {
|
|
#ifdef MICRONOVA_LISTENER_COUNT
|
|
int stove_reply_value = this->read_stove_reply_();
|
|
if (this->current_command_.is_write()) {
|
|
if (stove_reply_value == -1) {
|
|
ESP_LOGW(TAG, "Write to [0x%02X:0x%02X] may have failed (checksum mismatch in reply)",
|
|
this->current_command_.memory_location & ~WRITE_BIT, this->current_command_.memory_address);
|
|
}
|
|
} else {
|
|
// For READ commands, notify all listeners registered for this address
|
|
uint8_t loc = this->current_command_.memory_location;
|
|
uint8_t addr = this->current_command_.memory_address;
|
|
for (auto *listener : this->listeners_) {
|
|
if (listener->get_memory_location() == loc && listener->get_memory_address() == addr) {
|
|
listener->process_value_from_stove(stove_reply_value);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
this->read_stove_reply_();
|
|
#endif
|
|
this->reply_pending_ = false;
|
|
} else if (millis() - this->transmission_time_ > STOVE_REPLY_TIMEOUT) {
|
|
// Timeout - no reply received (buffer cleared before next command)
|
|
ESP_LOGW(TAG, "Timeout waiting for reply from [0x%02X:0x%02X], available: %d",
|
|
this->current_command_.memory_location, this->current_command_.memory_address, this->available());
|
|
this->reply_pending_ = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// No reply pending - process next command (writes have priority over reads)
|
|
#ifdef USE_MICRONOVA_WRITER
|
|
if (!this->write_queue_.empty()) {
|
|
this->current_command_ = this->write_queue_.front();
|
|
this->write_queue_.pop();
|
|
this->send_current_command_();
|
|
return;
|
|
}
|
|
#endif
|
|
#ifdef MICRONOVA_LISTENER_COUNT
|
|
if (!this->read_queue_.empty()) {
|
|
this->current_command_ = this->read_queue_.front();
|
|
this->read_queue_.pop();
|
|
this->send_current_command_();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef MICRONOVA_LISTENER_COUNT
|
|
void MicroNova::queue_read_request(uint8_t location, uint8_t address) {
|
|
// Check if this read is already queued
|
|
for (const auto &queued : this->read_queue_) {
|
|
if (queued.memory_location == location && queued.memory_address == address) {
|
|
ESP_LOGV(TAG, "Read [%02X,%02X] already queued, skipping", location, address);
|
|
return;
|
|
}
|
|
}
|
|
|
|
MicroNovaCommand cmd;
|
|
cmd.memory_location = location;
|
|
cmd.memory_address = address;
|
|
cmd.data = 0;
|
|
|
|
if (!this->read_queue_.push(cmd)) {
|
|
ESP_LOGW(TAG, "Read queue full, dropping read [%02X,%02X]", location, address);
|
|
return;
|
|
}
|
|
ESP_LOGV(TAG, "Queued read [%02X,%02X] (queue size: %u)", location, address, this->read_queue_.size());
|
|
}
|
|
#endif
|
|
|
|
void MicroNova::send_current_command_() {
|
|
uint8_t trash_rx;
|
|
|
|
// Clear rx buffer - stove hiccups may cause late replies in the rx
|
|
while (this->available()) {
|
|
this->read_byte(&trash_rx);
|
|
ESP_LOGW(TAG, "Reading excess byte 0x%02X", trash_rx);
|
|
}
|
|
|
|
uint8_t write_data[4] = {this->current_command_.memory_location, this->current_command_.memory_address, 0, 0};
|
|
size_t write_len;
|
|
|
|
if (this->current_command_.is_write()) {
|
|
write_len = 4;
|
|
write_data[2] = this->current_command_.data;
|
|
// calculate checksum
|
|
write_data[3] = write_data[0] + write_data[1] + write_data[2];
|
|
ESP_LOGV(TAG, "Sending write request [%02X,%02X,%02X,%02X]", write_data[0], write_data[1], write_data[2],
|
|
write_data[3]);
|
|
} else {
|
|
write_len = 2;
|
|
ESP_LOGV(TAG, "Sending read request [%02X,%02X]", write_data[0], write_data[1]);
|
|
}
|
|
|
|
this->enable_rx_pin_->digital_write(true);
|
|
this->write_array(write_data, write_len);
|
|
this->flush();
|
|
this->enable_rx_pin_->digital_write(false);
|
|
|
|
this->transmission_time_ = millis();
|
|
this->reply_pending_ = true;
|
|
}
|
|
|
|
int MicroNova::read_stove_reply_() {
|
|
uint8_t reply_data[2] = {0, 0};
|
|
|
|
this->read_array(reply_data, 2);
|
|
|
|
ESP_LOGV(TAG, "Reply from stove [%02X,%02X]", reply_data[0], reply_data[1]);
|
|
|
|
uint8_t checksum = this->current_command_.memory_location + this->current_command_.memory_address + reply_data[1];
|
|
if (reply_data[0] != checksum) {
|
|
ESP_LOGE(TAG, "Checksum mismatch! From [0x%02X:0x%02X] received [0x%02X,0x%02X]. Expected 0x%02X, got 0x%02X",
|
|
this->current_command_.memory_location, this->current_command_.memory_address, reply_data[0],
|
|
reply_data[1], checksum, reply_data[0]);
|
|
return -1;
|
|
}
|
|
return ((int) reply_data[1]);
|
|
}
|
|
|
|
#ifdef USE_MICRONOVA_WRITER
|
|
bool MicroNova::queue_write_command(uint8_t location, uint8_t address, uint8_t data) {
|
|
MicroNovaCommand cmd;
|
|
cmd.memory_location = location | WRITE_BIT;
|
|
cmd.memory_address = address;
|
|
cmd.data = data;
|
|
|
|
// Check if a write to the same address is already queued - update data in-place
|
|
for (auto &queued : this->write_queue_) {
|
|
if (queued.memory_location == cmd.memory_location && queued.memory_address == cmd.memory_address) {
|
|
if (queued.data != cmd.data) {
|
|
ESP_LOGD(TAG, "Updating queued write [%02X,%02X] data 0x%02X -> 0x%02X", location, address, queued.data, data);
|
|
queued.data = cmd.data;
|
|
} else {
|
|
ESP_LOGV(TAG, "Write [%02X,%02X] with data 0x%02X already queued, skipping", location, address, data);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!this->write_queue_.push(cmd)) {
|
|
ESP_LOGW(TAG, "Write queue full, dropping command");
|
|
return false;
|
|
}
|
|
ESP_LOGD(TAG, "Queued write [%02X,%02X] (queue size: %u)", location, address, this->write_queue_.size());
|
|
#ifdef MICRONOVA_LISTENER_COUNT
|
|
// Automatically queue sensor updates after write commands
|
|
this->request_update_listeners_();
|
|
#endif
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
} // namespace esphome::micronova
|