From 03045de7c26517ae8f15583d38300b1dfe9f9026 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 23 Aug 2025 16:59:05 +0200 Subject: [PATCH 1/6] first commit --- README.md | 0 cb_frame.cpp | 168 +++++++++++++++++++++++++++++++++ cb_frame.h | 46 +++++++++ cbf_cache.cpp | 54 +++++++++++ cbf_cache.h | 31 ++++++ cbf_cache_item.cpp | 110 ++++++++++++++++++++++ cbf_cache_item.h | 39 ++++++++ cbf_pylon.cpp | 129 +++++++++++++++++++++++++ cbf_pylon.h | 80 ++++++++++++++++ cbf_store.cpp | 223 ++++++++++++++++++++++++++++++++++++++++++++ cbf_store.h | 58 ++++++++++++ cbf_store_pylon.cpp | 71 ++++++++++++++ cbf_store_pylon.h | 34 +++++++ common.h | 46 +++++++++ 14 files changed, 1089 insertions(+) create mode 100644 README.md create mode 100644 cb_frame.cpp create mode 100644 cb_frame.h create mode 100644 cbf_cache.cpp create mode 100644 cbf_cache.h create mode 100644 cbf_cache_item.cpp create mode 100644 cbf_cache_item.h create mode 100644 cbf_pylon.cpp create mode 100644 cbf_pylon.h create mode 100644 cbf_store.cpp create mode 100644 cbf_store.h create mode 100644 cbf_store_pylon.cpp create mode 100644 cbf_store_pylon.h create mode 100644 common.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cb_frame.cpp b/cb_frame.cpp new file mode 100644 index 0000000..bd3009a --- /dev/null +++ b/cb_frame.cpp @@ -0,0 +1,168 @@ +#include "source/solar/cb_frame.h" +// limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. +namespace solar +{ + cb_frame::cb_frame(int msg_id, uint32_t can_id) + { + this->msg_id = msg_id; + this->rtr = false; + this->can_id = can_id; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR1").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + { + this->msg_id = msg_id; + this->rtr = rtr; + this->can_id = can_id; + this->frame = frame; + // ESP_LOGI(tag("frame CTOR2").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + void cb_frame::clear() + { + this->msg_id = 0; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + } + void cb_frame::swap(cb_frame &s) + { + std::swap(this->msg_id, s.msg_id); + std::swap(this->rtr, s.rtr); + std::swap(this->can_id, s.can_id); + this->frame.swap(s.frame); + } + bool cb_frame::is_valid() const + { + return this->can_id != 0; + } + int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } else { + for (int i = 0; i < ncmpcols && !stopcompare && result == 0; i++) { + result = compare(b, comparecolumns[i], &stopcompare, true); + } + } + return result; + } + int cb_frame::compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->is_valid() && b.is_valid(); + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->is_valid() || b.is_valid()); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + ESP_LOGE("cb_frame::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + int cb_frame::compare_frame(const byte_vector& _frame) const + { + int result = 0; + auto j = _frame.begin(); + for(auto i = this->frame.begin(); i != this->frame.end() && result == 0; ++i) { + if(j == _frame.end()) { + // ESP_LOGW("cb_frame::compare", "Frame size mismatch: this.frame.size()=%zu, _frame.size()=%zu", this->frame.size(), _frame.size()); + result = 1; // this frame is longer than _frame + break; + } + // ESP_LOGI("cb_frame::compare", "Comparing frame byte %zu: %02X vs %02X", i - this->frame.begin(), *i, *j); + result = num_compare(*i, *j); + // if (result != 0) { + // ESP_LOGI("cb_frame::compare", "Frame byte %zu mismatch: %02X vs %02X", i - this->frame.begin(), *i, *j); + // } + j++; + } + return result; + } + std::string cb_frame::tag(const std::string& prefix) const + { + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); + else + snprintf(tag, sizeof(tag), "%-18s %04d 0x%03X ", prefix.c_str(), msg_id, can_id); + return std::string(tag); + } + std::string cb_frame::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments to avoid buffer overflows and ensure correct formatting. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + std::string text = ""; + char hex[10]; + snprintf(hex, sizeof(hex), " %s", rtr ? "RTR" : " "); + std::string line(hex); + if (this->frame.empty()) { + line += " "; + return line; + } + for (const auto& byte : frame) { + snprintf(hex, sizeof(hex), "%02X ", byte); + line += hex; + if(byte > 31 && byte < 127) { + text += (char) byte; + } + else { + text += "."; + } + } + return line + " " + text; + } +} // namespace solar \ No newline at end of file diff --git a/cb_frame.h b/cb_frame.h new file mode 100644 index 0000000..5bd6bca --- /dev/null +++ b/cb_frame.h @@ -0,0 +1,46 @@ +#ifndef __SOLAR_CB_FRAME // include GUARD +#define __SOLAR_CB_FRAME +#include "esphome.h" +#include +#include "common.h" +using namespace esphome; + +namespace solar +{ + class cbf_store; + class cb_frame { + public: + enum cbf_store_sortcolumns : int + { + sisortNULL, + sisortcanid, + sisortframe, + sisortrtr, + sisortmsgid, + sisortEND, + sisortcase = SORTCASE, // case sensitive + sisortreverse = SORTREVERSE, // reverse order + sisortwild = WILDNULL, // an empty, or null entry equates to wildcard + sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible + }; + int msg_id; + bool rtr; + uint32_t can_id; + byte_vector frame; + cb_frame() = default; + cb_frame(int msg_id, uint32_t can_id); + cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); + void clear(); + void swap(cb_frame &s); + virtual bool is_valid() const; + virtual int compare(const cb_frame& b, const int *comparecolumns) const; + virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; + int compare_frame(const byte_vector& _frame) const; + virtual std::string tag(const std::string& prefix = "") const; + virtual std::string to_string() const; + virtual ~cb_frame() = default; + // using default (compiler auto generated) copy and move constructors and assignment operators + }; + +} // namespace solar +#endif // __SOLAR_CB_FRAME diff --git a/cbf_cache.cpp b/cbf_cache.cpp new file mode 100644 index 0000000..442fbc1 --- /dev/null +++ b/cbf_cache.cpp @@ -0,0 +1,54 @@ +#include "source/solar/cbf_cache.h" + +namespace solar +{ + static const int comparecolumns[] = {cbf_store::sisortframe, cbf_store::sisortrtr, 0}; + + void cbf_cache::clear() + { + cache_map.clear(); + } + int cbf_cache::size() const + { + return cache_map.size(); + } + bool cbf_cache::hasitem(uint32_t can_id) const + { + return cache_map.find(can_id) != cache_map.end(); + } + cbf_cache_item& cbf_cache::getitem(uint32_t can_id) + { + return cache_map.at(can_id); + } + const cbf_cache_item& cbf_cache::getitem(uint32_t can_id) const + { + return cache_map.at(can_id); + } + bool cbf_cache::additem(const cbf_store& storeitem) + { + const auto& ret = cache_map.emplace(storeitem.can_id, cbf_cache_item(storeitem)); + if(ret.second) { + return false; // new item inserted, no publish + } + auto& kvp = *ret.first; + auto& item = kvp.second; + bool publish = false; + if(item.update(storeitem, publish, comparecolumns, 1)) { + // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); + return publish; + } + // try next store to see if it has a duplicate of new item + if(item.update(storeitem, publish, comparecolumns, 2)) { + // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); + return publish; + } + item.update(storeitem); + //cache_map.erase(kvp.first); + //ret = cache_map.emplace(storeitem.can_id, item); + //if(!ret.second) { + // ESP_LOGE(item.store0.tag("== ST1 ERR == ").c_str(), "Error re-inserting item into cache_map"); + // ESP_LOGE(item.store1.tag("== ST2 ERR == ").c_str(), "Error re-inserting item into cache_map"); + //} + return publish; + } + } // namespace solar \ No newline at end of file diff --git a/cbf_cache.h b/cbf_cache.h new file mode 100644 index 0000000..e887c3e --- /dev/null +++ b/cbf_cache.h @@ -0,0 +1,31 @@ +// NB! A lot of comments in .h and .cpp files were auto generated by CoPilot. Applicable comments have been retained, others removed. + +#ifndef __SOLAR_CBF_CACHE +#define __SOLAR_CBF_CACHE +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +#include "cbf_cache_item.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache { + private: + std::map cache_map; // map of CAN IDs to cache items + public: + cbf_cache() = default; + void clear(); + int size() const; + bool hasitem(uint32_t can_id) const; + cbf_cache_item& getitem(uint32_t can_id); + const cbf_cache_item& getitem(uint32_t can_id) const; + // Add a new item to the cache or update an existing one + bool additem(const cbf_store& item); + // using default (compiler auto generated) copy and move constructors and assignment operators + }; + +} // namespace solar + +#endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_cache_item.cpp b/cbf_cache_item.cpp new file mode 100644 index 0000000..4d503c9 --- /dev/null +++ b/cbf_cache_item.cpp @@ -0,0 +1,110 @@ +// if filename is changes, remove old .o file from \\TRUENAS\esphome\config\.esphome\build\sthome-ut8\.pioenvs\sthome-ut8\src\source\solar +#include "source/solar/cbf_cache_item.h" + +namespace solar +{ + cbf_cache_item::cbf_cache_item() + { + this->store0 = std::make_shared(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store + this->store1 = std::make_shared(0, 0, 2, byte_vector(), false, 0, 0); // make generic empty store + ESP_LOGI( this->store0->tag("cache_item CTOR0A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); + ESP_LOGI( this->store1->tag("cache_item CTOR0B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); + } + cbf_cache_item::cbf_cache_item(const cbf_store& store0) + { + this->store0 = store0.clone(); + this->store1 = store0.clone(); + this->store0->id = 1; + //this->store1->clear(); + this->store1->id = 2; + //ESP_LOGI( this->store0->tag("cache_item CTOR1A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); + //ESP_LOGI( this->store1->tag("cache_item CTOR1B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); + } + void cbf_cache_item::clear() + { + store0 = nullptr; + store1 = nullptr; + } + void cbf_cache_item::swap(cbf_cache_item &s) + { + std::swap(this->store0, s.store0); + std::swap(this->store1, s.store1); + } + bool cbf_cache_item::update(const cbf_store& newitem) + { + //std::string posttag0 = std::string("cache_item UPD ST1"); + //std::string posttag1 = std::string("cache_item UPD ST2"); + // ESP_LOGI(store0->tag(posttag0).c_str(), "VALID: %s %s", store0->is_valid() ? "Y" : "N", store0->to_string().c_str()); + // ESP_LOGI(store1->tag(posttag1).c_str(), "VALID: %s %s", store1->is_valid() ? "Y" : "N", store1->to_string().c_str()); + if(!this->store0->is_valid()) { + store0 = newitem.clone(); + store0->id = 1; + // ESP_LOGI(store0->tag("== ST1 INV NEW ==").c_str(), store0->to_string().c_str()); + return true; + } + if(!this->store1->is_valid()) { + store1 = newitem.clone(); + store1->id = 2; + // ESP_LOGI(store1->tag("== ST2 INV NEW ==").c_str(), store1->to_string().c_str()); + return true; + } + bool result = store1->last_timestamp > store0->last_timestamp; + if(result) { + store0 = newitem.clone(); + this->store0->id = 1; + // ESP_LOGI(store0->tag("== ST1 OLD NEW ==").c_str(), store0->to_string().c_str()); + } + else { + store1 = newitem.clone(); + this->store1->id = 2; + // ESP_LOGI(store1->tag("== OLD ST2 NEW ==").c_str(), store1->to_string().c_str()); + } + return result; + } + bool cbf_cache_item::update(const cbf_store& newitem, bool& publish, const int *comparecolumns, int store_selector) + { + bool result = false; + switch(store_selector) { + case 2: + result = this->store1->update(newitem, publish, comparecolumns); + break; + default: + result = this->store0->update(newitem, publish, comparecolumns); + break; + } + //std::string posttag = std::string("cache_item ") + (result ? "UPD" : "NUP") + " ST" + std::to_string(store_selector) + " "; + //if(store_selector == 1 ) { + // ESP_LOGI(store1->tag(posttag).c_str(), store1->to_string().c_str()); + //} else { + // ESP_LOGI(store0->tag(posttag).c_str(), store0->to_string().c_str()); + //} + return result; + } + std::string cbf_cache_item::tag(const std::string& prefix) const + { + char tag[24]; + snprintf(tag, sizeof(tag), "%-18s ", prefix.c_str()); + return std::string(tag); + } + std::string cbf_cache_item::tag0(const std::string& prefix) const + { + return store0->tag(prefix); + } + std::string cbf_cache_item::tag1(const std::string& prefix) const + { + return store1->tag(prefix); + } + std::string cbf_cache_item::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char buffer[180]; + // trim + auto trimmedtag0 = trim(tag0()); + auto trimmedtag1 = trim(tag1()); + snprintf(buffer, sizeof(buffer), "ST1: [%s] %s | ST2: [%s] %s", trimmedtag0.c_str(), store0->to_string().c_str(), trimmedtag1.c_str(), store1->to_string().c_str()); + return std::string(buffer); + } + +} // namespace solar + diff --git a/cbf_cache_item.h b/cbf_cache_item.h new file mode 100644 index 0000000..3057f09 --- /dev/null +++ b/cbf_cache_item.h @@ -0,0 +1,39 @@ +#ifndef __SOLAR_CBF_CACHE_ITEM +#define __SOLAR_CBF_CACHE_ITEM +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache_item { + private: + const int storecount = 2; + public: + std::shared_ptr store0; + std::shared_ptr store1; + + cbf_cache_item(); // to allow object to be used as a value element in map operator[] method + cbf_cache_item(const cbf_store& store0); + void clear(); + void swap(cbf_cache_item &s); + bool update(const cbf_store& newitem); + bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns, int store_selector); + virtual std::string tag0(const std::string& prefix = "") const; + virtual std::string tag1(const std::string& prefix = "") const; + virtual std::string tag(const std::string& prefix = "") const; + virtual std::string to_string() const; + + // the default copy and move constructors and assignment operators are fine + // because shared_ptr takes care of the underlying memory management + cbf_cache_item(const cbf_cache_item& b) = default; + cbf_cache_item& operator=(const cbf_cache_item& b) = default; + cbf_cache_item(cbf_cache_item&& src) = default; + cbf_cache_item& operator=(cbf_cache_item&& src) = default; + virtual ~cbf_cache_item() = default; // virtual destructor for base class + }; + +} // namespace solar +#endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_pylon.cpp b/cbf_pylon.cpp new file mode 100644 index 0000000..4e456cf --- /dev/null +++ b/cbf_pylon.cpp @@ -0,0 +1,129 @@ +#include "cbf_pylon.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 10, 30); // default publish spec for Pylontech battery messages + + cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_pylon::clear() + { + cb_frame::clear(); + } + // Function to build a message from the pylon canbus frame + std::string cbf_pylon::to_string() const + { + char buffer[80]; + switch (can_id) + { + case CB_BATTERY_LIMITS: + { + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_LIMITS"; + } + const auto& x = this->frame; + float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V + float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A + float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A + snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); + return buffer; + } + break; + case CB_BATTERY_STATE: + { + if (this->frame.size() < 4) { + return "Invalid frame size for CB_BATTERY_STATE"; + } + const auto& x = this->frame; + uint soc = static_cast((x[1] << 8) + x[0]); + uint soh = static_cast((x[3] << 8) + x[2]); + snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); + return buffer; + } + break; + case CB_BATTERY_STATUS: + { + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_STATUS"; + } + const auto& x = this->frame; + float system_voltage = 0.01 * static_cast((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system + float system_current = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current + float average_cell_temperature = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1°C + snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature); + return buffer; + } + break; + case CB_BATTERY_FAULT: + { + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_FAULT"; + } + const auto& x = this->frame; + uint8_t protection1 = x[0]; + uint8_t protection2 = x[1]; + uint8_t alarm1 = x[2]; + uint8_t alarm2 = x[3]; + uint8_t module_numbers = x[4]; + char ch5 = x[5]; + char ch6 = x[6]; + bool discharge_over_current = protection1 & 0x80; + bool cell_under_temperature = protection1 & 0x10; + bool cell_over_temperature = protection1 & 0x08; + bool cell_or_module_under_voltage = protection1 & 0x04; + bool cell_or_module_over_voltage = protection1 & 0x02; + bool system_error = protection2 & 0x8; + bool charge_over_current = protection2 & 0x01; + bool discharge_high_current = alarm1 & 0x80; + bool cell_low_temperature = alarm1 & 0x10; + bool cell_high_temperature = alarm1 & 0x08; + bool cell_or_module_low_voltage = alarm1 & 0x04; + bool cell_or_module_high_voltage = alarm1 & 0x02; + bool internal_communication_fail = alarm2 & 0x8; + bool charge_high_current = alarm2 & 0x01; + snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6); + return buffer; + } + break; + case CB_BATTERY_REQUEST_FLAGS: + { + if (this->frame.size() < 1) { + return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; + } + const auto& x = this->frame; + uint8_t request_flag = x[0]; + bool charge_enable = request_flag & 0x80; + bool discharge_enable = request_flag & 0x40; + bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it. + bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently. + bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1. + snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : ""); + return buffer; + } + break; + case CB_BATTERY_MANUFACTURER: + { + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_MANUFACTURER"; + } + const auto& x = this->frame; + // Manufacturer name is in the first 8 bytes, padded with spaces + // Convert to string and trim trailing spaces + std::string manufacturer(reinterpret_cast(x.data()), 8); + manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); + return "BATTERY OEM: " + manufacturer; + } + break; + } + return "Unknown CAN ID"; + } +} // namespace solar \ No newline at end of file diff --git a/cbf_pylon.h b/cbf_pylon.h new file mode 100644 index 0000000..33c64b2 --- /dev/null +++ b/cbf_pylon.h @@ -0,0 +1,80 @@ +#ifndef __SOLAR_CBF_PYLON // include GUARD +#define __SOLAR_CBF_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + + class cbf_pylon : virtual public cb_frame { + + public: + // Pylontech battery CAN IDs + // All unverified IDs are taken from the Pylontech CAN protocol documentation + // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole + // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery + static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message + static const int CB_BATTERY_SETTING2 = 0x352; // used for Pylontech batteries + static const int CB_BATTERY_SETTING3 = 0x353; // used for Pylontech batteries + static const int CB_BATTERY_SETTING4 = 0x354; // used for Pylontech batteries + static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message + static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message + static const int CB_BATTERY_ERROR = 0x357; + static const int CB_BATTERY_WARNING = 0x358; + static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message + static const int CB_BATTERY_REQUEST = 0x35A; + static const int CB_BATTERY_RESPONSE = 0x35B; + static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message + static const int CB_BATTERY_MANUFACTURER_ID = 0x35D; + static const int CB_BATTERY_MODEL_ID = 0x35E; + static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message + static const int CB_BATTERY_MODEL = 0x35D; + static const int CB_BATTERY_SERIAL_NUMBER = 0x35F; + static const int CB_BATTERY_VERSION = 0x360; + static const int CB_BATTERY_CAPACITY = 0x361; + static const int CB_BATTERY_TEMPERATURE = 0x362; + static const int CB_BATTERY_VOLTAGE = 0x363; + static const int CB_BATTERY_CURRENT = 0x364; + static const int CB_BATTERY_SOC = 0x365; + static const int CB_BATTERY_SOH = 0x366; + static const int CB_BATTERY_CHARGE_POWER = 0x367; + static const int CB_BATTERY_DISCHARGE_POWER = 0x368; + static const int CB_BATTERY_CHARGE_ENERGY = 0x369; + static const int CB_BATTERY_DISCHARGE_ENERGY = 0x36A; + static const int CB_BATTERY_CHARGE_CYCLES = 0x36B; + static const int CB_BATTERY_CHARGE_LIMIT = 0x36C; + static const int CB_BATTERY_DISCHARGE_LIMIT = 0x36D; + static const int CB_BATTERY_CHARGE_MODE = 0x36E; + static const int CB_BATTERY_DISCHARGE_MODE = 0x36F; + static const int CB_BATTERY_CHARGE_STATUS = 0x370; + static const int CB_BATTERY_DISCHARGE_STATUS = 0x371; + static const int CB_BATTERY_CHARGE_CYCLE_COUNT = 0x372; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT = 0x373; + static const int CB_BATTERY_CHARGE_CYCLE_STATUS = 0x374; + static const int CB_BATTERY_CHARGE_CYCLE_MODE = 0x375; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_STATUS = 0x376; + static const int CB_BATTERY_CHARGE_CYCLE_MODE_STATUS = 0x377; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE = 0x378; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS = 0x379; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS2 = 0x37A; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS3 = 0x37B; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS4 = 0x37C; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS5 = 0x37D; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS6 = 0x37E; + static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS7 = 0x37F; + // Pylontech battery publish spec + // This is used to determine when to publish the battery data + static const publish_spec_t publish_spec; + + cbf_pylon() = default; + void clear(); + std::string to_string() const override; + cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + }; + +} // namespace solar +#endif // __SOLAR_CBF_PYLON \ No newline at end of file diff --git a/cbf_store.cpp b/cbf_store.cpp new file mode 100644 index 0000000..df81a9d --- /dev/null +++ b/cbf_store.cpp @@ -0,0 +1,223 @@ +#include "source/solar/cbf_store.h" + +namespace solar +{ + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id) + : cb_frame(msg_id, can_id) + { + this->id = id; // used for debugging, can be omitted + this->count = 0; + this->first_timestamp = 0; + this->last_timestamp = 0; + // ESP_LOGI(tag("store CTOR1").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR2").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& frame, bool rtr, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id, frame, rtr) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR3").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store CCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store&& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store MCCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + std::shared_ptr cbf_store::clone() const + { + return clone_impl(); + } + std::shared_ptr cbf_store::clone_impl() const + { + ESP_LOGW(tag("store CLONE").c_str(), "%-20s", "Cloning store"); // this should happen as all cloning should be done by derived classes + return std::make_shared(*this); + } + bool cbf_store::is_publish_expired(time_t currenttime, int update_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= update_interval); + } + bool cbf_store::is_validity_expired(time_t currenttime, int timeout_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); + } + bool cbf_store::update(const cbf_store& newitem, bool& publish, const int *comparecolumns) + { + ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing publish_spec as a parameter + return update(newitem, publish_spec_t{1, 5, 10}, publish, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation + } + bool cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, bool& publish, const int *comparecolumns) + { + if(!is_valid()) { + return false; + } + time_t newtime = newitem.last_timestamp; + bool publish_expired = this->is_publish_expired(newtime, publish_spec.interval); + bool validity_expired = this->is_validity_expired(newtime, publish_spec.timeout); + bool isduplicate = this->compare(newitem, comparecolumns) == 0; + time_t reset_timer = this->first_timestamp + publish_spec.interval - newtime; + auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); + int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; + //ESP_LOGI(this->tag("store UPDATE Bef").c_str(), "%s Td:%2d To:%s Ex:%s Rt:%2d Nts: %s %s", isduplicate ? "DUP" : "NEW", timediff, validity_expired ? "Y" : "N", publish_expired ? "Y" : "N", static_cast(reset_timer), ntstime.c_str(), this->to_string().c_str()); + if(validity_expired) { + *this = newitem; + if(!isduplicate) { + this->count = 1; + publish_expired = false; + } + } + if(isduplicate || publish_expired) { + this->count++; + publish = (this->count == publish_spec.on_count); + this->last_timestamp = newtime; + if(reset_timer <= 0) { + this->first_timestamp = newtime; + this->count = 1; + } + } + //ESP_LOGI(this->tag("store UPDATE Aft").c_str(), "%s Td:%2d To:%s Ex:%s Rt:%2d Pu:%s Nts: %s %s", isduplicate ? "DUP" : "NEW", timediff, validity_expired ? "Y" : "N", publish_expired ? "Y" : "N", static_cast(reset_timer), publish ? "Y" : "N", ntstime.c_str(), this->to_string().c_str()); + return isduplicate; + } + int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } + else { + for (int i = 0; i < sisortEND && !stopcompare && result == 0 && comparecolumns[i] != 0; i++) { + int cmpcol = comparecolumns[i]; + bool validnextcol = cmpcol != 0 && comparecolumns[i + 1] != 0; + result = compare(b, cmpcol, &stopcompare, validnextcol); + } + } + return result; + } + int cbf_store::compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->can_id != 0 && b.can_id != 0; + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->can_id || b.can_id); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortcount: + { + result = num_compare(this->count, b.count); + break; + } + case sisortfirst_timestamp: + { + auto a_ts = this->first_timestamp; + auto b_ts = b.first_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortlast_timestamp: + { + auto a_ts = this->last_timestamp; + auto b_ts = b.last_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + // ESP_LOGE("cbf_store::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + std::string cbf_store::tag(const std::string& prefix) const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "%04d id%d 0x%03X ", msg_id, id, can_id); + else + snprintf(tag, sizeof(tag), "%-18s %04d id%d 0x%03X ", prefix.c_str(), msg_id, id, can_id); + return std::string(tag); + } + std::string cbf_store::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char buffer[80]; + auto ftstime = is_valid() ? ESPTime::from_epoch_local(first_timestamp).strftime("%H:%M:%S") : "N/A"; + auto ltstime = is_valid() ? ESPTime::from_epoch_local(last_timestamp).strftime("%H:%M:%S") : "N/A"; + snprintf(buffer, sizeof(buffer), " Fts: %s Lts: %s Count: %2d %s", ftstime.c_str(), ltstime.c_str(), count, cb_frame::to_string().c_str()); + return std::string(buffer); + } +} // namespace solar diff --git a/cbf_store.h b/cbf_store.h new file mode 100644 index 0000000..f817cf0 --- /dev/null +++ b/cbf_store.h @@ -0,0 +1,58 @@ +#ifndef __SOLAR_CBF_STORE // include GUARD +#define __SOLAR_CBF_STORE +#include "esphome.h" +#include +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + + class cbf_store : virtual public cb_frame { + public: + int id; // used for debugging, can be omitted + int count; // how many times a duplicate of this frame was received; used to signal publish + time_t first_timestamp; // used for checking whether updateinterval has expired, i.e. to force a publish + time_t last_timestamp; // used for choosing which store to overwrite with new (different) frame data + enum cbf_store_sortcolumns : int + { + sisortNULL, + sisortcanid = cb_frame::sisortcanid, + sisortframe = cb_frame::sisortframe, + sisortrtr = cb_frame::sisortrtr, + sisortmsgid = cb_frame::sisortmsgid, + sisortcount, + sisortfirst_timestamp, + sisortlast_timestamp, + sisortEND, + sisortcase = SORTCASE, // where applicable, case sensitive + sisortreverse = SORTREVERSE, // reverse order + sisortwild = WILDNULL, // where applicable, an empty or null entry equates to wildcard + sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible + }; + cbf_store() = delete; // default constructor not allowed + cbf_store(int msg_id, uint32_t can_id, int id); + cbf_store(int msg_id, uint32_t can_id, int id, time_t _first_timestamp, time_t _last_timestamp); + cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& _frame, bool _rtr, time_t _first_timestamp, time_t _last_timestamp); + cbf_store(const cbf_store& b, int id); + cbf_store(const cbf_store&& b, int id); + virtual ~cbf_store() = default; // virtual destructor for base class + // using default (compiler auto generated) copy and move constructors and assignment operators + + std::shared_ptr clone() const; + int compare(const cbf_store& b, const int *comparecolumns) const; + std::string tag(const std::string& prefix = "") const override; + std::string to_string() const override; + bool is_publish_expired(time_t currenttime, int update_interval) const; + bool is_validity_expired(time_t currenttime, int timeout_interval) const; + bool update(const cbf_store& newitem, publish_spec_t publish_spec, bool& publish, const int *comparecolumns); + virtual bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns); // should be pure virtual function, but we need to instantiate this class + + private: + int compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; + virtual std::shared_ptr clone_impl() const; + }; +} // namespace solar +#endif // __SOLAR_CBF_STORE \ No newline at end of file diff --git a/cbf_store_pylon.cpp b/cbf_store_pylon.cpp new file mode 100644 index 0000000..001bdfc --- /dev/null +++ b/cbf_store_pylon.cpp @@ -0,0 +1,71 @@ +#include "cbf_store_pylon.h" + +namespace solar +{ + cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr, time_t timestamp) + : cb_frame(msg_id, can_id, _frame, _rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_pylon CTOR1").c_str(), "%-20s %s", "Created store_pylon", this->to_string().c_str()); + } + cbf_store_pylon::cbf_store_pylon(const cbf_store_pylon& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_pylon CCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(const cbf_store_pylon& b) + { + if (this != &b) { + cbf_store_pylon tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_pylon AOPP").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + cbf_store_pylon::cbf_store_pylon(cbf_store_pylon&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_pylon MCCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(cbf_store_pylon&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_pylon MOASS").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_pylon::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon + } + std::shared_ptr cbf_store_pylon::clone_impl() const + { + //ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon"); + return std::make_shared(*this); + } + void cbf_store_pylon::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_pylon::swap(cbf_store_pylon &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + bool cbf_store_pylon::update(const cbf_store& newitem, bool& publish, const int *comparecolumns) + { + //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, publish, comparecolumns); + } + std::string cbf_store_pylon::to_string() const + { + return cbf_store::to_string() + " " + cbf_pylon::to_string(); + } + +} // namespace solar \ No newline at end of file diff --git a/cbf_store_pylon.h b/cbf_store_pylon.h new file mode 100644 index 0000000..16aa319 --- /dev/null +++ b/cbf_store_pylon.h @@ -0,0 +1,34 @@ +#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD +#define __SOLAR_CBF_STORE_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_pylon : public cbf_store, public cbf_pylon + { + public: + cbf_store_pylon() = delete; // default constructor not allowed + cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_pylon() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_pylon &s); + virtual bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns) override; + std::string to_string() const override; + + cbf_store_pylon(const cbf_store_pylon& b); + cbf_store_pylon& operator=(const cbf_store_pylon& b); + cbf_store_pylon(cbf_store_pylon&& src); + cbf_store_pylon& operator=(cbf_store_pylon&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar +#endif // __SOLAR_CBF_STORE_PYLON \ No newline at end of file diff --git a/common.h b/common.h new file mode 100644 index 0000000..8cf1b05 --- /dev/null +++ b/common.h @@ -0,0 +1,46 @@ +#ifndef __SOLAR_COMMON // include GUARD +#define __SOLAR_COMMON + +namespace solar +{ + + #define FLAGBITS ~0x0FFF // bitwise one's complement (sort column info fits into lower three nibbles) + #define SORTCASE 0x0001000 // case sensitive + #define SORTREVERSE 0x0002000 // reverse order + #define WILDNULL 0x0004000 // zero denotes wildcard, i.e. anything is equal to zero + #define STOPCOMPARE 0x0080000 // stop compare after this column if both items to be compared are valid and a compare was possible + #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) + #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 + #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 + + typedef unsigned int uint; + typedef std::vector byte_vector; + + struct publish_spec_t + { + int on_count; // how many times a duplicate of this frame was received; used to signal publish + int interval; // used for checking whether updateinterval has expired, i.e. to force a publish + int timeout; // used for checking whether timeout has expired, i.e. frame has become stale + publish_spec_t(int on_count, int interval, int timeout) + : on_count(on_count), interval(interval), timeout(timeout) {} + }; + + inline std::string ltrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t start = str.find_first_not_of(chars); + return (start == std::string::npos) ? "" : str.substr(start); + } + + inline std::string rtrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t end = str.find_last_not_of(chars); + return (end == std::string::npos) ? "" : str.substr(0, end + 1); + } + + inline std::string trim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + return rtrim(ltrim(str, chars), chars); + } +} // namespace solar + +#endif // __SOLAR_COMMON \ No newline at end of file From ea865dfa94dd614119722136a9b5aedfe1e54831 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 3 Sep 2025 22:38:18 +0200 Subject: [PATCH 2/6] Added sthome canbus --- cb_frame.cpp | 156 +++++++++++++++++++++++++++++++++++++++----- cb_frame.h | 45 +++++++++---- cbf_cache.cpp | 41 ++++++++++-- cbf_cache.h | 9 ++- cbf_cache_item.cpp | 73 +++++++++++---------- cbf_cache_item.h | 7 +- cbf_pylon.cpp | 51 +++++++++++---- cbf_pylon.h | 60 +++-------------- cbf_store.cpp | 46 ++++++++----- cbf_store.h | 20 ++++-- cbf_store_pylon.cpp | 10 +-- cbf_store_pylon.h | 4 +- common.h | 2 + 13 files changed, 361 insertions(+), 163 deletions(-) diff --git a/cb_frame.cpp b/cb_frame.cpp index bd3009a..59c629b 100644 --- a/cb_frame.cpp +++ b/cb_frame.cpp @@ -2,9 +2,21 @@ // limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. namespace solar { + const cb_frame emptyframe = cb_frame(); // to return a reference to when no valid frame is found + + cb_frame::cb_frame() + { + this->msg_id = 0; + this->publish = false; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR0").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } cb_frame::cb_frame(int msg_id, uint32_t can_id) { this->msg_id = msg_id; + this->publish = false; this->rtr = false; this->can_id = can_id; this->frame.clear(); @@ -13,6 +25,7 @@ namespace solar cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) { this->msg_id = msg_id; + this->publish = false; this->rtr = rtr; this->can_id = can_id; this->frame = frame; @@ -21,6 +34,7 @@ namespace solar void cb_frame::clear() { this->msg_id = 0; + this->publish = false; this->rtr = false; this->can_id = 0; this->frame.clear(); @@ -28,6 +42,7 @@ namespace solar void cb_frame::swap(cb_frame &s) { std::swap(this->msg_id, s.msg_id); + std::swap(this->publish, s.publish); std::swap(this->rtr, s.rtr); std::swap(this->can_id, s.can_id); this->frame.swap(s.frame); @@ -35,6 +50,34 @@ namespace solar bool cb_frame::is_valid() const { return this->can_id != 0; + } + void cb_frame::setpublish(bool do_publish) const + { + this->publish = do_publish; + } + bool cb_frame::getpublish() const + { + return this->publish; + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, bool extended_id, bool remote_transmission_request) const + { + if(canbus == nullptr) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "CAN bus interface is null, cannot send data"); + return false; + } + if(!this->is_valid()) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "Frame is not valid, cannot send data: %s", this->to_string().c_str()); + return false; + } + return cb_frame::send_frame(canbus, this->can_id, this->frame, extended_id, remote_transmission_request); + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + if(canbus == nullptr) { + return false; + } + canbus->send_data(can_id, extended_id, remote_transmission_request, frame); + return true; } int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const { @@ -132,13 +175,13 @@ namespace solar } return result; } - std::string cb_frame::tag(const std::string& prefix) const + std::string cb_frame::tag(const std::string& prefix, int prefixlen) const { char tag[48]; if(prefix.length() == 0) snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); else - snprintf(tag, sizeof(tag), "%-18s %04d 0x%03X ", prefix.c_str(), msg_id, can_id); + snprintf(tag, sizeof(tag), "%-*s %04d 0x%03X ", prefixlen, prefix.c_str(), msg_id, can_id); return std::string(tag); } std::string cb_frame::to_string() const @@ -147,22 +190,105 @@ namespace solar // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. std::string text = ""; char hex[10]; - snprintf(hex, sizeof(hex), " %s", rtr ? "RTR" : " "); - std::string line(hex); - if (this->frame.empty()) { - line += " "; - return line; + std::string line; + int n = 0; + if(rtr) { + line = " "; + text = " "; + n = 6; // to align text output } - for (const auto& byte : frame) { - snprintf(hex, sizeof(hex), "%02X ", byte); - line += hex; - if(byte > 31 && byte < 127) { - text += (char) byte; + else { + if (this->frame.empty()) { + return ""; } - else { - text += "."; - } + for (const auto& byte : frame) { + n++; + snprintf(hex, sizeof(hex), "%02X ", byte); + line += hex; + if(byte > 31 && byte < 127) { + text += (char) byte; + } + else { + text += "."; + } + } + } + for (int i = n; i < 8; i++) { + line += " "; + text += " "; } return line + " " + text; } + // helper function to extract a scaled double value from two bytes + double cb_frame::get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed) + { + int value = 0; + if ((lsb == 0xFF && msb == 0xFF) || inv_scale == 0) { + return std::numeric_limits::quiet_NaN(); + } + if (is_signed) { + value = static_cast((msb << 8) + lsb); + } else { + value = static_cast((msb << 8) + lsb); + } + //ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale); + return static_cast(value) / inv_scale; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) + { + //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + if(scale4 != 0) { + value = (scale4 < 0) ? static_cast(value4 * -scale4) : static_cast(value4 * scale4); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + } + return byte_stream; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + // Overloaded function to handle double scale for the fourth value + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4) + { + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + value = (scale4 < 0) ? static_cast(round(value4 * -scale4)) : static_cast(round(value4 * scale4)); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + return byte_stream; + } + } // namespace solar \ No newline at end of file diff --git a/cb_frame.h b/cb_frame.h index 5bd6bca..d97889b 100644 --- a/cb_frame.h +++ b/cb_frame.h @@ -9,25 +9,27 @@ namespace solar { class cbf_store; class cb_frame { + private: + mutable bool publish; // whether this frame should be published public: - enum cbf_store_sortcolumns : int + enum cbf_sortcolumns : int { - sisortNULL, - sisortcanid, - sisortframe, - sisortrtr, - sisortmsgid, - sisortEND, - sisortcase = SORTCASE, // case sensitive - sisortreverse = SORTREVERSE, // reverse order - sisortwild = WILDNULL, // an empty, or null entry equates to wildcard - sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible + sisortNULL, + sisortcanid, + sisortrtr, + sisortframe, + sisortmsgid, + sisortEND, + sisortcase = SORTCASE, // case sensitive + sisortreverse = SORTREVERSE, // reverse order + sisortwild = WILDNULL, // an empty, or null entry equates to wildcard + sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible }; int msg_id; - bool rtr; + bool rtr; uint32_t can_id; byte_vector frame; - cb_frame() = default; + cb_frame(); cb_frame(int msg_id, uint32_t can_id); cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); void clear(); @@ -36,11 +38,26 @@ namespace solar virtual int compare(const cb_frame& b, const int *comparecolumns) const; virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; int compare_frame(const byte_vector& _frame) const; - virtual std::string tag(const std::string& prefix = "") const; + void setpublish(bool do_publish = true) const; // we promise only to modify the publish flag + bool getpublish() const; + virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const; virtual std::string to_string() const; virtual ~cb_frame() = default; // using default (compiler auto generated) copy and move constructors and assignment operators + + /// Helper functions to convert four scaled values into a byte stream + /// set scale to negative for signed values + static std::vector get_byte_stream(double value1, int scale1, double value2 = 0, int scale2 = 0, double value3 = 0, int scale3 = 0, double value4 = 0, int scale4 = 0); + /// Overloaded function to handle double scale for the fourth value + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); + + /// helper function to extract a scaled double value from two bytes + static double get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed = false); //inv_scale is the reciprocal of scale factor + bool send_frame(esphome::canbus::Canbus *canbus, bool extended_id = false, bool remote_transmission_request = false) const; + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame }; + extern const cb_frame emptyframe; // to return a reference to when no valid frame is found + } // namespace solar #endif // __SOLAR_CB_FRAME diff --git a/cbf_cache.cpp b/cbf_cache.cpp index 442fbc1..eb974de 100644 --- a/cbf_cache.cpp +++ b/cbf_cache.cpp @@ -32,15 +32,16 @@ namespace solar } auto& kvp = *ret.first; auto& item = kvp.second; - bool publish = false; - if(item.update(storeitem, publish, comparecolumns, 1)) { + auto updateresult = item.update(storeitem, comparecolumns, 1); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); - return publish; + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; } // try next store to see if it has a duplicate of new item - if(item.update(storeitem, publish, comparecolumns, 2)) { + updateresult = item.update(storeitem, comparecolumns, 2); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); - return publish; + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; } item.update(storeitem); //cache_map.erase(kvp.first); @@ -49,6 +50,34 @@ namespace solar // ESP_LOGE(item.store0.tag("== ST1 ERR == ").c_str(), "Error re-inserting item into cache_map"); // ESP_LOGE(item.store1.tag("== ST2 ERR == ").c_str(), "Error re-inserting item into cache_map"); //} - return publish; + return false; } + const cb_frame& cbf_cache::get_frame(uint32_t can_id) const + { + auto it = cache_map.find(can_id); + if(it == cache_map.end()) + return emptyframe; // return reference to static empty frame if not found + const auto& item = it->second; + return item.get_frame(); + } + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) + { + if(!this->hasitem(can_id)) + return false; + const auto& cbf = get_frame(can_id); + if(cbf.getpublish()) { + return cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + return false; + } + // static version to send arbitrary frame + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + return cb_frame::send_frame(canbus, can_id, frame, extended_id, remote_transmission_request); + } + bool cbf_cache::send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id) + { + return cb_frame::send_frame(canbus, can_id, {}, extended_id, true); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_cache.h b/cbf_cache.h index e887c3e..26197b9 100644 --- a/cbf_cache.h +++ b/cbf_cache.h @@ -12,9 +12,8 @@ using namespace esphome; namespace solar { class cbf_cache { - private: - std::map cache_map; // map of CAN IDs to cache items public: + std::map cache_map; // map of CAN IDs to cache items cbf_cache() = default; void clear(); int size() const; @@ -23,7 +22,13 @@ namespace solar const cbf_cache_item& getitem(uint32_t can_id) const; // Add a new item to the cache or update an existing one bool additem(const cbf_store& item); + const cb_frame& get_frame(uint32_t can_id) const; + virtual ~cbf_cache() = default; // virtual destructor for base class + bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame + static bool send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false); // static version to send remote transmission request frame // using default (compiler auto generated) copy and move constructors and assignment operators + }; } // namespace solar diff --git a/cbf_cache_item.cpp b/cbf_cache_item.cpp index 4d503c9..8972d5f 100644 --- a/cbf_cache_item.cpp +++ b/cbf_cache_item.cpp @@ -15,10 +15,7 @@ namespace solar this->store0 = store0.clone(); this->store1 = store0.clone(); this->store0->id = 1; - //this->store1->clear(); this->store1->id = 2; - //ESP_LOGI( this->store0->tag("cache_item CTOR1A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); - //ESP_LOGI( this->store1->tag("cache_item CTOR1B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); } void cbf_cache_item::clear() { @@ -32,60 +29,69 @@ namespace solar } bool cbf_cache_item::update(const cbf_store& newitem) { - //std::string posttag0 = std::string("cache_item UPD ST1"); - //std::string posttag1 = std::string("cache_item UPD ST2"); - // ESP_LOGI(store0->tag(posttag0).c_str(), "VALID: %s %s", store0->is_valid() ? "Y" : "N", store0->to_string().c_str()); - // ESP_LOGI(store1->tag(posttag1).c_str(), "VALID: %s %s", store1->is_valid() ? "Y" : "N", store1->to_string().c_str()); if(!this->store0->is_valid()) { store0 = newitem.clone(); store0->id = 1; - // ESP_LOGI(store0->tag("== ST1 INV NEW ==").c_str(), store0->to_string().c_str()); return true; } if(!this->store1->is_valid()) { store1 = newitem.clone(); store1->id = 2; - // ESP_LOGI(store1->tag("== ST2 INV NEW ==").c_str(), store1->to_string().c_str()); return true; } bool result = store1->last_timestamp > store0->last_timestamp; if(result) { store0 = newitem.clone(); this->store0->id = 1; - // ESP_LOGI(store0->tag("== ST1 OLD NEW ==").c_str(), store0->to_string().c_str()); } else { store1 = newitem.clone(); this->store1->id = 2; - // ESP_LOGI(store1->tag("== OLD ST2 NEW ==").c_str(), store1->to_string().c_str()); } return result; } - bool cbf_cache_item::update(const cbf_store& newitem, bool& publish, const int *comparecolumns, int store_selector) + cbf_store::cbf_updateresult cbf_cache_item::update(const cbf_store& newitem, const int *comparecolumns, int store_selector) { - bool result = false; + cbf_store::cbf_updateresult result = cbf_store::cbf_updateresult::stu_NONE; switch(store_selector) { case 2: - result = this->store1->update(newitem, publish, comparecolumns); + result = this->store1->update(newitem, comparecolumns); break; default: - result = this->store0->update(newitem, publish, comparecolumns); + result = this->store0->update(newitem, comparecolumns); break; } - //std::string posttag = std::string("cache_item ") + (result ? "UPD" : "NUP") + " ST" + std::to_string(store_selector) + " "; - //if(store_selector == 1 ) { - // ESP_LOGI(store1->tag(posttag).c_str(), store1->to_string().c_str()); - //} else { - // ESP_LOGI(store0->tag(posttag).c_str(), store0->to_string().c_str()); - //} return result; } + const cb_frame& cbf_cache_item::get_frame() const + { + return get_store(); // return most recent valid store's frame + } + const cbf_store& cbf_cache_item::get_store() const + { + if(this->store0->is_valid() && !this->store1->is_valid()) { + return *store0; + } + if(!this->store0->is_valid() && this->store1->is_valid()) { + return *store1; + } + if(this->store0->is_valid() && this->store1->is_valid()) { + if(this->store0->last_timestamp >= this->store1->last_timestamp) { + return *this->store0; + } else { + return *this->store1; + } + } + return *this->store0; // no valid stores, but return store0 which is always present + } std::string cbf_cache_item::tag(const std::string& prefix) const { - char tag[24]; - snprintf(tag, sizeof(tag), "%-18s ", prefix.c_str()); - return std::string(tag); - } + return get_store().tag(prefix, prefix.length()); + } + std::string cbf_cache_item::to_string() const + { + return get_store().to_string(); + } std::string cbf_cache_item::tag0(const std::string& prefix) const { return store0->tag(prefix); @@ -94,15 +100,16 @@ namespace solar { return store1->tag(prefix); } - std::string cbf_cache_item::to_string() const + std::string cbf_cache_item::st0_tostring() const { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - char buffer[180]; - // trim - auto trimmedtag0 = trim(tag0()); - auto trimmedtag1 = trim(tag1()); - snprintf(buffer, sizeof(buffer), "ST1: [%s] %s | ST2: [%s] %s", trimmedtag0.c_str(), store0->to_string().c_str(), trimmedtag1.c_str(), store1->to_string().c_str()); + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str()); + return std::string(buffer); + } + std::string cbf_cache_item::st1_tostring() const + { + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str()); return std::string(buffer); } diff --git a/cbf_cache_item.h b/cbf_cache_item.h index 3057f09..0bf7e43 100644 --- a/cbf_cache_item.h +++ b/cbf_cache_item.h @@ -20,12 +20,15 @@ namespace solar void clear(); void swap(cbf_cache_item &s); bool update(const cbf_store& newitem); - bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns, int store_selector); + cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns, int store_selector); + const cb_frame& get_frame() const; // returns the most recent valid frame + const cbf_store& get_store() const; // returns the most recent valid store virtual std::string tag0(const std::string& prefix = "") const; virtual std::string tag1(const std::string& prefix = "") const; virtual std::string tag(const std::string& prefix = "") const; virtual std::string to_string() const; - + virtual std::string st0_tostring() const; // string of store 0 + virtual std::string st1_tostring() const; // string of store 1 // the default copy and move constructors and assignment operators are fine // because shared_ptr takes care of the underlying memory management cbf_cache_item(const cbf_cache_item& b) = default; diff --git a/cbf_pylon.cpp b/cbf_pylon.cpp index 4e456cf..7fb38e5 100644 --- a/cbf_pylon.cpp +++ b/cbf_pylon.cpp @@ -4,14 +4,15 @@ namespace solar { // see common.h for definition of publish_spec_t // publish_spec_t(int on_count, int interval, int timeout) - const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 10, 30); // default publish spec for Pylontech battery messages + const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages + const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) { if (this != &src) { cb_frame::operator=(std::move(src)); src.clear(); - ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); } return *this; } @@ -19,6 +20,13 @@ namespace solar { cb_frame::clear(); } + + // float cbf_pylon::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + // Function to build a message from the pylon canbus frame std::string cbf_pylon::to_string() const { @@ -27,6 +35,9 @@ namespace solar { case CB_BATTERY_LIMITS: { + if(rtr) { + return "Request for BATTERY LIMITS info"; + } if (this->frame.size() < 6) { return "Invalid frame size for CB_BATTERY_LIMITS"; } @@ -40,6 +51,9 @@ namespace solar break; case CB_BATTERY_STATE: { + if(rtr) { + return "Request for BATTERY STATE info"; + } if (this->frame.size() < 4) { return "Invalid frame size for CB_BATTERY_STATE"; } @@ -52,6 +66,9 @@ namespace solar break; case CB_BATTERY_STATUS: { + if(rtr) { + return "Request for BATTERY STATUS info"; + } if (this->frame.size() < 6) { return "Invalid frame size for CB_BATTERY_STATUS"; } @@ -65,6 +82,9 @@ namespace solar break; case CB_BATTERY_FAULT: { + if(rtr) { + return "Request for BATTERY FAULT info"; + } if (this->frame.size() < 8) { return "Invalid frame size for CB_BATTERY_FAULT"; } @@ -96,6 +116,9 @@ namespace solar break; case CB_BATTERY_REQUEST_FLAGS: { + if(rtr) { + return "Request for BATTERY REQUEST FLAGS info"; + } if (this->frame.size() < 1) { return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; } @@ -112,18 +135,24 @@ namespace solar break; case CB_BATTERY_MANUFACTURER: { - if (this->frame.size() < 8) { - return "Invalid frame size for CB_BATTERY_MANUFACTURER"; - } - const auto& x = this->frame; - // Manufacturer name is in the first 8 bytes, padded with spaces - // Convert to string and trim trailing spaces - std::string manufacturer(reinterpret_cast(x.data()), 8); - manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); - return "BATTERY OEM: " + manufacturer; + if(rtr) { + return "Request for BATTERY MANUFACTURER info"; + } + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_MANUFACTURER"; + } + const auto& x = this->frame; + // Manufacturer name is in the first 8 bytes, padded with spaces + // Convert to string and trim trailing spaces + std::string manufacturer(reinterpret_cast(x.data()), 8); + manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); + return "BATTERY OEM: " + manufacturer; } break; } + if(rtr) { + return "Request for unknown CAN ID info"; + } return "Unknown CAN ID"; } } // namespace solar \ No newline at end of file diff --git a/cbf_pylon.h b/cbf_pylon.h index 33c64b2..5b6f0ed 100644 --- a/cbf_pylon.h +++ b/cbf_pylon.h @@ -8,70 +8,30 @@ using namespace esphome; namespace solar { - class cbf_pylon : virtual public cb_frame { - public: + // Pylontech publish spec + // This is used to determine when to publish the battery data from the Pylontech battery + static const publish_spec_t publish_spec; // default publish spec for Pylontech battery messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + // Pylontech battery CAN IDs - // All unverified IDs are taken from the Pylontech CAN protocol documentation // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message - static const int CB_BATTERY_SETTING2 = 0x352; // used for Pylontech batteries - static const int CB_BATTERY_SETTING3 = 0x353; // used for Pylontech batteries - static const int CB_BATTERY_SETTING4 = 0x354; // used for Pylontech batteries static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message - static const int CB_BATTERY_ERROR = 0x357; - static const int CB_BATTERY_WARNING = 0x358; static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message - static const int CB_BATTERY_REQUEST = 0x35A; - static const int CB_BATTERY_RESPONSE = 0x35B; static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message - static const int CB_BATTERY_MANUFACTURER_ID = 0x35D; - static const int CB_BATTERY_MODEL_ID = 0x35E; static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message - static const int CB_BATTERY_MODEL = 0x35D; - static const int CB_BATTERY_SERIAL_NUMBER = 0x35F; - static const int CB_BATTERY_VERSION = 0x360; - static const int CB_BATTERY_CAPACITY = 0x361; - static const int CB_BATTERY_TEMPERATURE = 0x362; - static const int CB_BATTERY_VOLTAGE = 0x363; - static const int CB_BATTERY_CURRENT = 0x364; - static const int CB_BATTERY_SOC = 0x365; - static const int CB_BATTERY_SOH = 0x366; - static const int CB_BATTERY_CHARGE_POWER = 0x367; - static const int CB_BATTERY_DISCHARGE_POWER = 0x368; - static const int CB_BATTERY_CHARGE_ENERGY = 0x369; - static const int CB_BATTERY_DISCHARGE_ENERGY = 0x36A; - static const int CB_BATTERY_CHARGE_CYCLES = 0x36B; - static const int CB_BATTERY_CHARGE_LIMIT = 0x36C; - static const int CB_BATTERY_DISCHARGE_LIMIT = 0x36D; - static const int CB_BATTERY_CHARGE_MODE = 0x36E; - static const int CB_BATTERY_DISCHARGE_MODE = 0x36F; - static const int CB_BATTERY_CHARGE_STATUS = 0x370; - static const int CB_BATTERY_DISCHARGE_STATUS = 0x371; - static const int CB_BATTERY_CHARGE_CYCLE_COUNT = 0x372; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT = 0x373; - static const int CB_BATTERY_CHARGE_CYCLE_STATUS = 0x374; - static const int CB_BATTERY_CHARGE_CYCLE_MODE = 0x375; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_STATUS = 0x376; - static const int CB_BATTERY_CHARGE_CYCLE_MODE_STATUS = 0x377; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE = 0x378; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS = 0x379; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS2 = 0x37A; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS3 = 0x37B; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS4 = 0x37C; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS5 = 0x37D; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS6 = 0x37E; - static const int CB_BATTERY_CHARGE_CYCLE_LIMIT_MODE_STATUS7 = 0x37F; - // Pylontech battery publish spec - // This is used to determine when to publish the battery data - static const publish_spec_t publish_spec; + + //Property battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit}; cbf_pylon() = default; + cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); void clear(); - std::string to_string() const override; + virtual std::string to_string() const override; cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon // using default (compiler auto generated) copy and move constructors and assignment operators }; diff --git a/cbf_store.cpp b/cbf_store.cpp index df81a9d..5d98ac0 100644 --- a/cbf_store.cpp +++ b/cbf_store.cpp @@ -64,24 +64,29 @@ namespace solar { return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); } - bool cbf_store::update(const cbf_store& newitem, bool& publish, const int *comparecolumns) + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, const int *comparecolumns) { - ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing publish_spec as a parameter - return update(newitem, publish_spec_t{1, 5, 10}, publish, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation + ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing the publish_specs as parameters + return update(newitem, publish_spec_t{1, 5, 10}, publish_spec_t{1, 5, 10}, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation } - bool cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, bool& publish, const int *comparecolumns) + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns) { if(!is_valid()) { - return false; + return cbf_updateresult::stu_NONE; // cannot update an invalid store } + auto pbspec = rtr ? rtr_publish_spec : publish_spec; time_t newtime = newitem.last_timestamp; - bool publish_expired = this->is_publish_expired(newtime, publish_spec.interval); - bool validity_expired = this->is_validity_expired(newtime, publish_spec.timeout); + bool publish_expired = this->is_publish_expired(newtime, pbspec.interval); + bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout); bool isduplicate = this->compare(newitem, comparecolumns) == 0; - time_t reset_timer = this->first_timestamp + publish_spec.interval - newtime; - auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); + time_t reset_timer = this->first_timestamp + pbspec.interval - newtime; int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; - //ESP_LOGI(this->tag("store UPDATE Bef").c_str(), "%s Td:%2d To:%s Ex:%s Rt:%2d Nts: %s %s", isduplicate ? "DUP" : "NEW", timediff, validity_expired ? "Y" : "N", publish_expired ? "Y" : "N", static_cast(reset_timer), ntstime.c_str(), this->to_string().c_str()); + auto result = isduplicate ? cbf_updateresult::stu_DUPLICATE : cbf_updateresult::stu_NONE; + // auto ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // auto ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "A: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec// .interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(),// result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); if(validity_expired) { *this = newitem; if(!isduplicate) { @@ -91,15 +96,22 @@ namespace solar } if(isduplicate || publish_expired) { this->count++; - publish = (this->count == publish_spec.on_count); + if(this->count == pbspec.on_count) + this->setpublish(); // must be reset by caller after publish + if(this->getpublish()) + result = static_cast(result | cbf_updateresult::stu_PUBLISH); this->last_timestamp = newtime; if(reset_timer <= 0) { this->first_timestamp = newtime; this->count = 1; } } - //ESP_LOGI(this->tag("store UPDATE Aft").c_str(), "%s Td:%2d To:%s Ex:%s Rt:%2d Pu:%s Nts: %s %s", isduplicate ? "DUP" : "NEW", timediff, validity_expired ? "Y" : "N", publish_expired ? "Y" : "N", static_cast(reset_timer), publish ? "Y" : "N", ntstime.c_str(), this->to_string().c_str()); - return isduplicate; + // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "B: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec.interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(), result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); + + return result; } int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const { @@ -199,15 +211,15 @@ namespace solar result = -result; return result; } - std::string cbf_store::tag(const std::string& prefix) const + std::string cbf_store::tag(const std::string& prefix, int prefixlen) const { // Be extra careful with the placement of printf format specifiers and their corresponding arguments. // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. char tag[48]; if(prefix.length() == 0) - snprintf(tag, sizeof(tag), "%04d id%d 0x%03X ", msg_id, id, can_id); - else - snprintf(tag, sizeof(tag), "%-18s %04d id%d 0x%03X ", prefix.c_str(), msg_id, id, can_id); + snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " "); + else + snprintf(tag, sizeof(tag), "%-*s #%04d s%d 0x%03X %s", prefixlen, prefix.c_str(), msg_id, id, can_id, getpublish() ? "P" : " "); return std::string(tag); } std::string cbf_store::to_string() const diff --git a/cbf_store.h b/cbf_store.h index f817cf0..f4d2392 100644 --- a/cbf_store.h +++ b/cbf_store.h @@ -16,12 +16,12 @@ namespace solar int count; // how many times a duplicate of this frame was received; used to signal publish time_t first_timestamp; // used for checking whether updateinterval has expired, i.e. to force a publish time_t last_timestamp; // used for choosing which store to overwrite with new (different) frame data - enum cbf_store_sortcolumns : int + enum cbf_sortcolumns : int { sisortNULL, sisortcanid = cb_frame::sisortcanid, - sisortframe = cb_frame::sisortframe, sisortrtr = cb_frame::sisortrtr, + sisortframe = cb_frame::sisortframe, sisortmsgid = cb_frame::sisortmsgid, sisortcount, sisortfirst_timestamp, @@ -32,6 +32,14 @@ namespace solar sisortwild = WILDNULL, // where applicable, an empty or null entry equates to wildcard sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible }; + + enum cbf_updateresult : int + { + stu_NONE = 0, // no update + stu_DUPLICATE = 0b01, // updated with new timestamps no publish + stu_PUBLISH = 0b10, // can publish data + stu_DUPLICATE_PUBLISH = 0b11, // updated with new timestamps; can publish data + }; cbf_store() = delete; // default constructor not allowed cbf_store(int msg_id, uint32_t can_id, int id); cbf_store(int msg_id, uint32_t can_id, int id, time_t _first_timestamp, time_t _last_timestamp); @@ -43,12 +51,12 @@ namespace solar std::shared_ptr clone() const; int compare(const cbf_store& b, const int *comparecolumns) const; - std::string tag(const std::string& prefix = "") const override; - std::string to_string() const override; + virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const override; + virtual std::string to_string() const override; bool is_publish_expired(time_t currenttime, int update_interval) const; bool is_validity_expired(time_t currenttime, int timeout_interval) const; - bool update(const cbf_store& newitem, publish_spec_t publish_spec, bool& publish, const int *comparecolumns); - virtual bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns); // should be pure virtual function, but we need to instantiate this class + cbf_updateresult update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns); + virtual cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns); // should be pure virtual function, but we need to instantiate this class private: int compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; diff --git a/cbf_store_pylon.cpp b/cbf_store_pylon.cpp index 001bdfc..a32a9db 100644 --- a/cbf_store_pylon.cpp +++ b/cbf_store_pylon.cpp @@ -2,8 +2,8 @@ namespace solar { - cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr, time_t timestamp) - : cb_frame(msg_id, can_id, _frame, _rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) + cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_pylon(msg_id, can_id, frame, rtr) { this->id = 0; this->count = 0; @@ -58,14 +58,14 @@ namespace solar cb_frame::swap(s); cbf_store::swap(s); } - bool cbf_store_pylon::update(const cbf_store& newitem, bool& publish, const int *comparecolumns) + cbf_store::cbf_updateresult cbf_store_pylon::update(const cbf_store& newitem, const int *comparecolumns) { //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); - return cbf_store::update(newitem, publish_spec, publish, comparecolumns); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); } std::string cbf_store_pylon::to_string() const { - return cbf_store::to_string() + " " + cbf_pylon::to_string(); + return cb_frame::to_string() + " " + cbf_pylon::to_string(); } } // namespace solar \ No newline at end of file diff --git a/cbf_store_pylon.h b/cbf_store_pylon.h index 16aa319..3a854f0 100644 --- a/cbf_store_pylon.h +++ b/cbf_store_pylon.h @@ -18,8 +18,8 @@ namespace solar std::shared_ptr clone() const; void clear(); void swap(cbf_store_pylon &s); - virtual bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns) override; - std::string to_string() const override; + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; cbf_store_pylon(const cbf_store_pylon& b); cbf_store_pylon& operator=(const cbf_store_pylon& b); diff --git a/common.h b/common.h index 8cf1b05..ae88eaf 100644 --- a/common.h +++ b/common.h @@ -12,6 +12,8 @@ namespace solar #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 + #define max(a, b) ((a) > (b)) ? (a) : (b) + #define min(a, b) ((a) < (b)) ? (a) : (b) typedef unsigned int uint; typedef std::vector byte_vector; From d44b60f637daeb43088e64eecc33932d1f52d030 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 4 Sep 2025 21:36:22 +0200 Subject: [PATCH 3/6] "Hopefully" improved comms on sthome canbus. --- cb_frame.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ cb_frame.h | 5 ++++- cbf_cache.cpp | 44 +++++++++++++++++++++++++++----------------- cbf_cache.h | 13 ++++++++----- cbf_pylon.cpp | 2 +- 5 files changed, 88 insertions(+), 24 deletions(-) diff --git a/cb_frame.cpp b/cb_frame.cpp index 59c629b..389fdaa 100644 --- a/cb_frame.cpp +++ b/cb_frame.cpp @@ -236,6 +236,54 @@ namespace solar } // Helper function to convert four scaled values into a byte stream // set scale to negative for signed values + std::vector cb_frame::get_byte_stream(double value1, int scale1) + { + std::vector byte_stream(2, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2) + { + std::vector byte_stream(4, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3) + { + std::vector byte_stream(6, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + return byte_stream; + } std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) { //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); diff --git a/cb_frame.h b/cb_frame.h index d97889b..0646610 100644 --- a/cb_frame.h +++ b/cb_frame.h @@ -47,7 +47,10 @@ namespace solar /// Helper functions to convert four scaled values into a byte stream /// set scale to negative for signed values - static std::vector get_byte_stream(double value1, int scale1, double value2 = 0, int scale2 = 0, double value3 = 0, int scale3 = 0, double value4 = 0, int scale4 = 0); + static std::vector get_byte_stream(double value1, int scale1); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4); /// Overloaded function to handle double scale for the fourth value static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); diff --git a/cbf_cache.cpp b/cbf_cache.cpp index eb974de..9b17b17 100644 --- a/cbf_cache.cpp +++ b/cbf_cache.cpp @@ -12,21 +12,25 @@ namespace solar { return cache_map.size(); } - bool cbf_cache::hasitem(uint32_t can_id) const + bool cbf_cache::hasitem(uint32_t can_id, bool remote_transmission_request) const { - return cache_map.find(can_id) != cache_map.end(); + return hasitem(canid_rtr_t(can_id, remote_transmission_request)); + } + bool cbf_cache::hasitem(canid_rtr_t key) const + { + return cache_map.find(key) != cache_map.end(); } - cbf_cache_item& cbf_cache::getitem(uint32_t can_id) + cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) { - return cache_map.at(can_id); + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); } - const cbf_cache_item& cbf_cache::getitem(uint32_t can_id) const + const cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) const { - return cache_map.at(can_id); + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); } bool cbf_cache::additem(const cbf_store& storeitem) { - const auto& ret = cache_map.emplace(storeitem.can_id, cbf_cache_item(storeitem)); + const auto& ret = cache_map.emplace(canid_rtr_t(storeitem.can_id, storeitem.rtr), cbf_cache_item(storeitem)); if(ret.second) { return false; // new item inserted, no publish } @@ -44,17 +48,15 @@ namespace solar return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; } item.update(storeitem); - //cache_map.erase(kvp.first); - //ret = cache_map.emplace(storeitem.can_id, item); - //if(!ret.second) { - // ESP_LOGE(item.store0.tag("== ST1 ERR == ").c_str(), "Error re-inserting item into cache_map"); - // ESP_LOGE(item.store1.tag("== ST2 ERR == ").c_str(), "Error re-inserting item into cache_map"); - //} return false; } - const cb_frame& cbf_cache::get_frame(uint32_t can_id) const + const cb_frame& cbf_cache::get_frame(uint32_t can_id, bool remote_transmission_request) const { - auto it = cache_map.find(can_id); + return get_frame(canid_rtr_t(can_id, remote_transmission_request)); + } + const cb_frame& cbf_cache::get_frame(canid_rtr_t key) const + { + auto it = cache_map.find(key); if(it == cache_map.end()) return emptyframe; // return reference to static empty frame if not found const auto& item = it->second; @@ -62,13 +64,21 @@ namespace solar } bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) { - if(!this->hasitem(can_id)) + auto key = canid_rtr_t(can_id, remote_transmission_request); + if(!this->hasitem(key)) return false; - const auto& cbf = get_frame(can_id); + const auto& cbf = get_frame(key); if(cbf.getpublish()) { return cbf.send_frame(canbus, extended_id, remote_transmission_request); } return false; + for(const auto& kvp : cache_map) { + const auto& item = kvp.second; + const auto& cbf = item.get_frame(); + if(cbf.getpublish()) { + cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + } } // static version to send arbitrary frame bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) diff --git a/cbf_cache.h b/cbf_cache.h index 26197b9..a88e414 100644 --- a/cbf_cache.h +++ b/cbf_cache.h @@ -13,16 +13,19 @@ namespace solar { class cbf_cache { public: - std::map cache_map; // map of CAN IDs to cache items + typedef std::pair canid_rtr_t; // pair of CAN ID and RTR flag + std::map cache_map; // map of rtr/CAN IDs to cache items cbf_cache() = default; void clear(); int size() const; - bool hasitem(uint32_t can_id) const; - cbf_cache_item& getitem(uint32_t can_id); - const cbf_cache_item& getitem(uint32_t can_id) const; + bool hasitem(canid_rtr_t key) const; + bool hasitem(uint32_t can_id, bool remote_transmission_request) const; + cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request); + const cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request) const; // Add a new item to the cache or update an existing one bool additem(const cbf_store& item); - const cb_frame& get_frame(uint32_t can_id) const; + const cb_frame& get_frame(canid_rtr_t key) const; + const cb_frame& get_frame(uint32_t can_id, bool remote_transmission_request) const; virtual ~cbf_cache() = default; // virtual destructor for base class bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame diff --git a/cbf_pylon.cpp b/cbf_pylon.cpp index 7fb38e5..abb9b35 100644 --- a/cbf_pylon.cpp +++ b/cbf_pylon.cpp @@ -151,7 +151,7 @@ namespace solar break; } if(rtr) { - return "Request for unknown CAN ID info"; + return "Request for unknown pylon CAN ID"; } return "Unknown CAN ID"; } From 9b71eca610d6d655e5eea46cbd4029d664535bc4 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 10 Oct 2025 17:25:17 +0200 Subject: [PATCH 4/6] Old uncommited updates --- README.md | 6 + cbf_sthome.cpp | 342 +++++++++++++++++++++++++++++++++++++++++++ cbf_sthome.h | 78 ++++++++++ cbf_store_sthome.cpp | 71 +++++++++ cbf_store_sthome.h | 34 +++++ 5 files changed, 531 insertions(+) create mode 100644 cbf_sthome.cpp create mode 100644 cbf_sthome.h create mode 100644 cbf_store_sthome.cpp create mode 100644 cbf_store_sthome.h diff --git a/README.md b/README.md index e69de29..c77a458 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,6 @@ +# ESPHome config/source/solar Folder + +This folder contains Contains source code files and related resources for solar projects. + + +For more information, visit the [ESPHome documentation](https://esphome.io/). \ No newline at end of file diff --git a/cbf_sthome.cpp b/cbf_sthome.cpp new file mode 100644 index 0000000..2d0e5dd --- /dev/null +++ b/cbf_sthome.cpp @@ -0,0 +1,342 @@ +#include "cbf_sthome.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec + const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests + + const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators + + //cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + // : cb_frame(msg_id, can_id, frame, rtr) + // { + // } + cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_sthome::clear() + { + cb_frame::clear(); + } + + bool cbf_sthome::verify_heartbeat() const + { + const auto& x = this->frame; + std::string hb(reinterpret_cast(x.data()), x.size()); + hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end()); + return hb == heartbeat; + } + // float cbf_sthome::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + + // Function to build a message from the pylon canbus frame + std::string cbf_sthome::to_string() const + { + char buffer[128]; + const auto& x = this->frame; + switch (can_id) + { + case CB_CANBUS_ID01: + return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : ""; + case CB_CANBUS_ID02: + return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : ""; + case CB_CANBUS_ID03: + return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : ""; + case CB_CANBUS_ID04: + return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : ""; + case CB_CANBUS_ID05: + return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : ""; + case CB_CANBUS_ID06: + return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : ""; + case CB_CANBUS_ID07: + return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : ""; + case CB_CANBUS_ID08: + return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : ""; + case CB_CANBUS_ID09: + return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : ""; + case CB_CANBUS_ID10: + return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : ""; + case CB_GEYSER_TEMPERATURE_TOP: + { + if(rtr) { + return "Request for geyser top temp"; + } + double temp_top = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top); + return buffer; + } + case CB_GEYSER_TEMPERATURE_BOTTOM: + { + if(rtr) { + return "Request for geyser bottom temp"; + } + double temp_bot = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot); + return buffer; + } + case CB_GEYSER_TEMPERATURE_AMBIENT: + { + if(rtr) { + return "Request for geyser ambient temp"; + } + double temp_amb = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb); + return buffer; + } + case CB_GEYSER_HEATING: + { + if(rtr) { + return "Request for geyser heating"; + } + double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W + double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W + double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W + unsigned int est_heating_time = (unsigned int) static_cast(256 * x[7] + x[6]); // 0 to 65536 seconds + snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time); + return buffer; + } + case CB_GEYSER_ACTIVE_SCHEDULE: + { + if(rtr) { + return "Request for geyser schedule"; + } + double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C + int active_heating_time = static_cast(256 * x[3] + x[2]); // -32768 to 32768s + double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s + int active_schedule_day = static_cast(x[6]); + snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day); + return buffer; + } + case CB_POWER_MAINS: + { + if(rtr) { + return "Request for power mains"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_INVERTER: + { + if(rtr) { + return "Request for power inverter"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_PLUGS: + { + if(rtr) { + return "Request for power plugs"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_LIGHTS: + { + if(rtr) { + return "Request for power lights"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GEYSER: + { + if(rtr) { + return "Request for power geyser"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_POOL: + { + if(rtr) { + return "Request for power pool"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GENERATED: + { + if(rtr) { + return "Request for power generated"; + } + double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW + double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW + snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss); + return buffer; + } + case CB_ENERGY_MAINS: + { + if(rtr) { + return "Request for energy mains"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GEYSER: + { + if(rtr) { + return "Request for energy geyser"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_POOL: + { + if(rtr) { + return "Request for energy pool"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_PLUGS: + { + if(rtr) { + return "Request for energy plugs"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LIGHTS: + { + if(rtr) { + return "Request for energy lights"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_HOUSE: + { + if(rtr) { + return "Request for energy house"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GENERATED: + { + if(rtr) { + return "Request for energy generated"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LOSS: + { + if(rtr) { + return "Request for energy loss"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_CONTROLLER_STATES: + { + if(rtr) { + return "Request for controller states"; + } + uint8_t alarms = x[0]; + uint8_t states = x[1]; + uint8_t modes = x[2]; + bool inverter_1_battery_low = alarms & 0x80; + bool inverter_2_battery_low = alarms & 0x10; + bool inverter_overload = alarms & 0x08; + bool geyser_heating = states & 0x80; + bool geyser_energised = states & 0x40; + bool mains_supply_present = states & 0x20; + bool battery_charging = states & 0x08; + bool vacation_mode = modes & 0x80; // nobody at home + std::string mode = ""; + int geysermode = (modes & 0x70) >> 4; + switch (geysermode) { + case GM_SUNDAY: + mode = "SUN"; + break; + case GM_WORKDAY: + mode = "WRK"; + break; + case GM_SATURDAY: + mode = "SAT"; + break; + case GM_PUBLIC_HOLIDAY: + mode = "PUB"; + break; + case GM_SCHOOL_HOLIDAY: + mode = "SCH"; + break; + default: + mode = "UNK"; + break; + } + snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : ""); + return buffer; + } + + } + if(rtr) { + return "Request for unknown sthome CAN ID"; + } + return "Unknown CAN ID"; + } + +} // namespace solar \ No newline at end of file diff --git a/cbf_sthome.h b/cbf_sthome.h new file mode 100644 index 0000000..2016f87 --- /dev/null +++ b/cbf_sthome.h @@ -0,0 +1,78 @@ +#ifndef __SOLAR_CBF_STHOME_H // include GUARD +#define __SOLAR_CBF_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + + class cbf_sthome : virtual public cb_frame { + private: + // float _get_battery_charge_voltage_limit() const; + + public: + // STHOME publish spec + // This is used to determine when to publish the sthome data + static const publish_spec_t publish_spec; // default publish spec for sthome messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + + enum geyser_modes : int { + GM_SUNDAY, + GM_WORKDAY, + GM_SATURDAY, + GM_PUBLIC_HOLIDAY, + GM_SCHOOL_HOLIDAY, + }; + enum canbus_ids : int { + CB_CANBUS_ID01 = 0x501, + CB_CANBUS_ID02, + CB_CANBUS_ID03, + CB_CANBUS_ID04, + CB_CANBUS_ID05, + CB_CANBUS_ID06, + CB_CANBUS_ID07, + CB_CANBUS_ID08, + CB_CANBUS_ID09, + CB_CANBUS_ID10, + CB_POWER_MAINS = 0x401, + CB_POWER_INVERTER, + CB_POWER_PLUGS, + CB_POWER_LIGHTS, + CB_POWER_GEYSER, + CB_POWER_POOL, + CB_POWER_GENERATED, + CB_ENERGY_MAINS, + CB_ENERGY_GEYSER, + CB_ENERGY_POOL, + CB_ENERGY_PLUGS, + CB_ENERGY_LIGHTS, + CB_ENERGY_HOUSE, + CB_ENERGY_GENERATED, + CB_ENERGY_LOSS, + CB_CONTROLLER_STATES, + CB_GEYSER_TEMPERATURE_TOP, + CB_GEYSER_TEMPERATURE_BOTTOM, + CB_GEYSER_TEMPERATURE_AMBIENT, + CB_GEYSER_HEATING, + CB_GEYSER_ACTIVE_SCHEDULE, + }; + + //Property battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit}; + static const std::string heartbeat; + + cbf_sthome() = default; + cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); + void clear(); + virtual std::string to_string() const override; + cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + virtual ~cbf_sthome() = default; + bool verify_heartbeat() const; + }; + +} // namespace solar +#endif // __SOLAR_CBF_STHOME_H \ No newline at end of file diff --git a/cbf_store_sthome.cpp b/cbf_store_sthome.cpp new file mode 100644 index 0000000..b872d15 --- /dev/null +++ b/cbf_store_sthome.cpp @@ -0,0 +1,71 @@ +#include "cbf_store_sthome.h" + +namespace solar +{ + cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str()); + } + cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b) + { + if (this != &b) { + cbf_store_sthome tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_sthome::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome + } + std::shared_ptr cbf_store_sthome::clone_impl() const + { + //ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome"); + return std::make_shared(*this); + } + void cbf_store_sthome::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_sthome::swap(cbf_store_sthome &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns) + { + //ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); + } + std::string cbf_store_sthome::to_string() const + { + return cb_frame::to_string() + " " + cbf_sthome::to_string(); + } + +} // namespace solar \ No newline at end of file diff --git a/cbf_store_sthome.h b/cbf_store_sthome.h new file mode 100644 index 0000000..f8075f0 --- /dev/null +++ b/cbf_store_sthome.h @@ -0,0 +1,34 @@ +#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD +#define __SOLAR_CBF_STORE_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_sthome.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_sthome : public cbf_store, public cbf_sthome + { + public: + cbf_store_sthome() = delete; // default constructor not allowed + cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_sthome() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_sthome &s); + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; + + cbf_store_sthome(const cbf_store_sthome& b); + cbf_store_sthome& operator=(const cbf_store_sthome& b); + cbf_store_sthome(cbf_store_sthome&& src); + cbf_store_sthome& operator=(cbf_store_sthome&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar +#endif // __SOLAR_CBF_STORE_STHOME_H \ No newline at end of file From 4fb865c0facebf8f0555df97d4a586fadf949fd2 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 20 Mar 2026 12:43:09 +0200 Subject: [PATCH 5/6] updates after making Solar submodule of esphome config folder --- README.md | 10 +- cb_frame.cpp | 682 +++++++++++++++++++++---------------------- cb_frame.h | 132 ++++----- cbf_cache.cpp | 184 ++++++------ cbf_cache.h | 76 ++--- cbf_cache_item.cpp | 234 +++++++-------- cbf_cache_item.h | 82 +++--- cbf_pylon.cpp | 314 ++++++++++---------- cbf_pylon.h | 78 ++--- cbf_sthome.cpp | 682 +++++++++++++++++++++---------------------- cbf_sthome.h | 154 +++++----- cbf_store.cpp | 470 ++++++++++++++--------------- cbf_store_pylon.cpp | 140 ++++----- cbf_store_pylon.h | 66 ++--- cbf_store_sthome.cpp | 140 ++++----- cbf_store_sthome.h | 66 ++--- common.h | 94 +++--- 17 files changed, 1802 insertions(+), 1802 deletions(-) diff --git a/README.md b/README.md index c77a458..744cfa8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ESPHome config/source/solar Folder - -This folder contains Contains source code files and related resources for solar projects. - - +# ESPHome config/source/solar Folder + +This folder contains Contains source code files and related resources for solar projects. + + For more information, visit the [ESPHome documentation](https://esphome.io/). \ No newline at end of file diff --git a/cb_frame.cpp b/cb_frame.cpp index 389fdaa..2a40669 100644 --- a/cb_frame.cpp +++ b/cb_frame.cpp @@ -1,342 +1,342 @@ -#include "source/solar/cb_frame.h" -// limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. -namespace solar -{ - const cb_frame emptyframe = cb_frame(); // to return a reference to when no valid frame is found - - cb_frame::cb_frame() - { - this->msg_id = 0; - this->publish = false; - this->rtr = false; - this->can_id = 0; - this->frame.clear(); - // ESP_LOGI(tag("frame CTOR0").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - cb_frame::cb_frame(int msg_id, uint32_t can_id) - { - this->msg_id = msg_id; - this->publish = false; - this->rtr = false; - this->can_id = can_id; - this->frame.clear(); - // ESP_LOGI(tag("frame CTOR1").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) - { - this->msg_id = msg_id; - this->publish = false; - this->rtr = rtr; - this->can_id = can_id; - this->frame = frame; - // ESP_LOGI(tag("frame CTOR2").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - void cb_frame::clear() - { - this->msg_id = 0; - this->publish = false; - this->rtr = false; - this->can_id = 0; - this->frame.clear(); - } - void cb_frame::swap(cb_frame &s) - { - std::swap(this->msg_id, s.msg_id); - std::swap(this->publish, s.publish); - std::swap(this->rtr, s.rtr); - std::swap(this->can_id, s.can_id); - this->frame.swap(s.frame); - } - bool cb_frame::is_valid() const - { - return this->can_id != 0; - } - void cb_frame::setpublish(bool do_publish) const - { - this->publish = do_publish; - } - bool cb_frame::getpublish() const - { - return this->publish; - } - bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, bool extended_id, bool remote_transmission_request) const - { - if(canbus == nullptr) { - ESP_LOGE(tag("cb_frame::send_data").c_str(), "CAN bus interface is null, cannot send data"); - return false; - } - if(!this->is_valid()) { - ESP_LOGE(tag("cb_frame::send_data").c_str(), "Frame is not valid, cannot send data: %s", this->to_string().c_str()); - return false; - } - return cb_frame::send_frame(canbus, this->can_id, this->frame, extended_id, remote_transmission_request); - } - bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) - { - if(canbus == nullptr) { - return false; - } - canbus->send_data(can_id, extended_id, remote_transmission_request, frame); - return true; - } - int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const - { - int result = 0; - bool stopcompare = false; - bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; - int ncmpcols = 0; - while (comparecolumns[ncmpcols] != 0) ncmpcols++; - if (isdefaultcompare) { - for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { - bool validnextcol = i < sisortEND - 1; - result = compare(b, i, &stopcompare, validnextcol); - } - } else { - for (int i = 0; i < ncmpcols && !stopcompare && result == 0; i++) { - result = compare(b, comparecolumns[i], &stopcompare, true); - } - } - return result; - } - int cb_frame::compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const - { - int result = 0; - int reverseorderflag = cmpflag & sisortreverse; - int casesensitiveflag = cmpflag & sisortcase; - int nulliswildcardflag = cmpflag & sisortwild; - int stopcompareflag = cmpflag & sistopcomparecol; - cmpflag = cmpflag & ~FLAGBITS; - if (cmpflag == 0) - return result; - bool casesensitive = casesensitiveflag != 0; - bool sortwild = nulliswildcardflag != 0; - bool stopcompare = stopcompareflag != 0; - *stopcomparesignal = false; - - switch (cmpflag) { - case sisortNULL: - return 0; - case sisortcanid: - { - bool bothvalid = this->is_valid() && b.is_valid(); - if (bothvalid) { - result = num_compare(this->can_id, b.can_id); - *stopcomparesignal = stopcompare; // set flag only if both items are valid - } else { - if (sortwild) - result = 0; - else { - bool bothinvalid = !(this->is_valid() || b.is_valid()); - if (validnextcol && bothinvalid) - result = 0; //if validnextcol then return 0 if both IDs are invalid - result = this->can_id ? 1 : -1; - } - } - break; - } - case sisortmsgid: - result = num_compare(this->msg_id, b.msg_id); - break; - case sisortrtr: - result = bool_compare(this->rtr, b.rtr); - break; - case sisortframe: - { - result = this->compare_frame(b.frame); - break; - } - default: - result = 0; - ESP_LOGE("cb_frame::compare", "Unknown compare column %d", cmpflag); - *stopcomparesignal = true; // stop compare as we don't know how to handle - // should never reach here as all cases must be dealt with - break; - } - if (reverseorderflag != 0) - result = -result; - return result; - } - int cb_frame::compare_frame(const byte_vector& _frame) const - { - int result = 0; - auto j = _frame.begin(); - for(auto i = this->frame.begin(); i != this->frame.end() && result == 0; ++i) { - if(j == _frame.end()) { - // ESP_LOGW("cb_frame::compare", "Frame size mismatch: this.frame.size()=%zu, _frame.size()=%zu", this->frame.size(), _frame.size()); - result = 1; // this frame is longer than _frame - break; - } - // ESP_LOGI("cb_frame::compare", "Comparing frame byte %zu: %02X vs %02X", i - this->frame.begin(), *i, *j); - result = num_compare(*i, *j); - // if (result != 0) { - // ESP_LOGI("cb_frame::compare", "Frame byte %zu mismatch: %02X vs %02X", i - this->frame.begin(), *i, *j); - // } - j++; - } - return result; - } - std::string cb_frame::tag(const std::string& prefix, int prefixlen) const - { - char tag[48]; - if(prefix.length() == 0) - snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); - else - snprintf(tag, sizeof(tag), "%-*s %04d 0x%03X ", prefixlen, prefix.c_str(), msg_id, can_id); - return std::string(tag); - } - std::string cb_frame::to_string() const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments to avoid buffer overflows and ensure correct formatting. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - std::string text = ""; - char hex[10]; - std::string line; - int n = 0; - if(rtr) { - line = " "; - text = " "; - n = 6; // to align text output - } - else { - if (this->frame.empty()) { - return ""; - } - for (const auto& byte : frame) { - n++; - snprintf(hex, sizeof(hex), "%02X ", byte); - line += hex; - if(byte > 31 && byte < 127) { - text += (char) byte; - } - else { - text += "."; - } - } - } - for (int i = n; i < 8; i++) { - line += " "; - text += " "; - } - return line + " " + text; - } - // helper function to extract a scaled double value from two bytes - double cb_frame::get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed) - { - int value = 0; - if ((lsb == 0xFF && msb == 0xFF) || inv_scale == 0) { - return std::numeric_limits::quiet_NaN(); - } - if (is_signed) { - value = static_cast((msb << 8) + lsb); - } else { - value = static_cast((msb << 8) + lsb); - } - //ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale); - return static_cast(value) / inv_scale; - } - // Helper function to convert four scaled values into a byte stream - // set scale to negative for signed values - std::vector cb_frame::get_byte_stream(double value1, int scale1) - { - std::vector byte_stream(2, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2) - { - std::vector byte_stream(4, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3) - { - std::vector byte_stream(6, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) - { - //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); - std::vector byte_stream(8, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - if(scale4 != 0) { - value = (scale4 < 0) ? static_cast(value4 * -scale4) : static_cast(value4 * scale4); - byte_stream[6] = value % 256; - byte_stream[7] = (value >> 8) % 256; - } - return byte_stream; - } - // Helper function to convert four scaled values into a byte stream - // set scale to negative for signed values - // Overloaded function to handle double scale for the fourth value - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4) - { - std::vector byte_stream(8, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - value = (scale4 < 0) ? static_cast(round(value4 * -scale4)) : static_cast(round(value4 * scale4)); - byte_stream[6] = value % 256; - byte_stream[7] = (value >> 8) % 256; - return byte_stream; - } - +#include "source/solar/cb_frame.h" +// limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. +namespace solar +{ + const cb_frame emptyframe = cb_frame(); // to return a reference to when no valid frame is found + + cb_frame::cb_frame() + { + this->msg_id = 0; + this->publish = false; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR0").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + cb_frame::cb_frame(int msg_id, uint32_t can_id) + { + this->msg_id = msg_id; + this->publish = false; + this->rtr = false; + this->can_id = can_id; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR1").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + { + this->msg_id = msg_id; + this->publish = false; + this->rtr = rtr; + this->can_id = can_id; + this->frame = frame; + // ESP_LOGI(tag("frame CTOR2").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + void cb_frame::clear() + { + this->msg_id = 0; + this->publish = false; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + } + void cb_frame::swap(cb_frame &s) + { + std::swap(this->msg_id, s.msg_id); + std::swap(this->publish, s.publish); + std::swap(this->rtr, s.rtr); + std::swap(this->can_id, s.can_id); + this->frame.swap(s.frame); + } + bool cb_frame::is_valid() const + { + return this->can_id != 0; + } + void cb_frame::setpublish(bool do_publish) const + { + this->publish = do_publish; + } + bool cb_frame::getpublish() const + { + return this->publish; + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, bool extended_id, bool remote_transmission_request) const + { + if(canbus == nullptr) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "CAN bus interface is null, cannot send data"); + return false; + } + if(!this->is_valid()) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "Frame is not valid, cannot send data: %s", this->to_string().c_str()); + return false; + } + return cb_frame::send_frame(canbus, this->can_id, this->frame, extended_id, remote_transmission_request); + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + if(canbus == nullptr) { + return false; + } + canbus->send_data(can_id, extended_id, remote_transmission_request, frame); + return true; + } + int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } else { + for (int i = 0; i < ncmpcols && !stopcompare && result == 0; i++) { + result = compare(b, comparecolumns[i], &stopcompare, true); + } + } + return result; + } + int cb_frame::compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->is_valid() && b.is_valid(); + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->is_valid() || b.is_valid()); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + ESP_LOGE("cb_frame::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + int cb_frame::compare_frame(const byte_vector& _frame) const + { + int result = 0; + auto j = _frame.begin(); + for(auto i = this->frame.begin(); i != this->frame.end() && result == 0; ++i) { + if(j == _frame.end()) { + // ESP_LOGW("cb_frame::compare", "Frame size mismatch: this.frame.size()=%zu, _frame.size()=%zu", this->frame.size(), _frame.size()); + result = 1; // this frame is longer than _frame + break; + } + // ESP_LOGI("cb_frame::compare", "Comparing frame byte %zu: %02X vs %02X", i - this->frame.begin(), *i, *j); + result = num_compare(*i, *j); + // if (result != 0) { + // ESP_LOGI("cb_frame::compare", "Frame byte %zu mismatch: %02X vs %02X", i - this->frame.begin(), *i, *j); + // } + j++; + } + return result; + } + std::string cb_frame::tag(const std::string& prefix, int prefixlen) const + { + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); + else + snprintf(tag, sizeof(tag), "%-*s %04d 0x%03X ", prefixlen, prefix.c_str(), msg_id, can_id); + return std::string(tag); + } + std::string cb_frame::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments to avoid buffer overflows and ensure correct formatting. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + std::string text = ""; + char hex[10]; + std::string line; + int n = 0; + if(rtr) { + line = " "; + text = " "; + n = 6; // to align text output + } + else { + if (this->frame.empty()) { + return ""; + } + for (const auto& byte : frame) { + n++; + snprintf(hex, sizeof(hex), "%02X ", byte); + line += hex; + if(byte > 31 && byte < 127) { + text += (char) byte; + } + else { + text += "."; + } + } + } + for (int i = n; i < 8; i++) { + line += " "; + text += " "; + } + return line + " " + text; + } + // helper function to extract a scaled double value from two bytes + double cb_frame::get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed) + { + int value = 0; + if ((lsb == 0xFF && msb == 0xFF) || inv_scale == 0) { + return std::numeric_limits::quiet_NaN(); + } + if (is_signed) { + value = static_cast((msb << 8) + lsb); + } else { + value = static_cast((msb << 8) + lsb); + } + //ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale); + return static_cast(value) / inv_scale; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + std::vector cb_frame::get_byte_stream(double value1, int scale1) + { + std::vector byte_stream(2, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2) + { + std::vector byte_stream(4, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3) + { + std::vector byte_stream(6, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) + { + //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + if(scale4 != 0) { + value = (scale4 < 0) ? static_cast(value4 * -scale4) : static_cast(value4 * scale4); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + } + return byte_stream; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + // Overloaded function to handle double scale for the fourth value + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4) + { + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + value = (scale4 < 0) ? static_cast(round(value4 * -scale4)) : static_cast(round(value4 * scale4)); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + return byte_stream; + } + } // namespace solar \ No newline at end of file diff --git a/cb_frame.h b/cb_frame.h index 0646610..45138f8 100644 --- a/cb_frame.h +++ b/cb_frame.h @@ -1,66 +1,66 @@ -#ifndef __SOLAR_CB_FRAME // include GUARD -#define __SOLAR_CB_FRAME -#include "esphome.h" -#include -#include "common.h" -using namespace esphome; - -namespace solar -{ - class cbf_store; - class cb_frame { - private: - mutable bool publish; // whether this frame should be published - public: - enum cbf_sortcolumns : int - { - sisortNULL, - sisortcanid, - sisortrtr, - sisortframe, - sisortmsgid, - sisortEND, - sisortcase = SORTCASE, // case sensitive - sisortreverse = SORTREVERSE, // reverse order - sisortwild = WILDNULL, // an empty, or null entry equates to wildcard - sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible - }; - int msg_id; - bool rtr; - uint32_t can_id; - byte_vector frame; - cb_frame(); - cb_frame(int msg_id, uint32_t can_id); - cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); - void clear(); - void swap(cb_frame &s); - virtual bool is_valid() const; - virtual int compare(const cb_frame& b, const int *comparecolumns) const; - virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; - int compare_frame(const byte_vector& _frame) const; - void setpublish(bool do_publish = true) const; // we promise only to modify the publish flag - bool getpublish() const; - virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const; - virtual std::string to_string() const; - virtual ~cb_frame() = default; - // using default (compiler auto generated) copy and move constructors and assignment operators - - /// Helper functions to convert four scaled values into a byte stream - /// set scale to negative for signed values - static std::vector get_byte_stream(double value1, int scale1); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4); - /// Overloaded function to handle double scale for the fourth value - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); - - /// helper function to extract a scaled double value from two bytes - static double get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed = false); //inv_scale is the reciprocal of scale factor - bool send_frame(esphome::canbus::Canbus *canbus, bool extended_id = false, bool remote_transmission_request = false) const; - static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame - }; - - extern const cb_frame emptyframe; // to return a reference to when no valid frame is found - -} // namespace solar -#endif // __SOLAR_CB_FRAME +#ifndef __SOLAR_CB_FRAME // include GUARD +#define __SOLAR_CB_FRAME +#include "esphome.h" +#include +#include "common.h" +using namespace esphome; + +namespace solar +{ + class cbf_store; + class cb_frame { + private: + mutable bool publish; // whether this frame should be published + public: + enum cbf_sortcolumns : int + { + sisortNULL, + sisortcanid, + sisortrtr, + sisortframe, + sisortmsgid, + sisortEND, + sisortcase = SORTCASE, // case sensitive + sisortreverse = SORTREVERSE, // reverse order + sisortwild = WILDNULL, // an empty, or null entry equates to wildcard + sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible + }; + int msg_id; + bool rtr; + uint32_t can_id; + byte_vector frame; + cb_frame(); + cb_frame(int msg_id, uint32_t can_id); + cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); + void clear(); + void swap(cb_frame &s); + virtual bool is_valid() const; + virtual int compare(const cb_frame& b, const int *comparecolumns) const; + virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; + int compare_frame(const byte_vector& _frame) const; + void setpublish(bool do_publish = true) const; // we promise only to modify the publish flag + bool getpublish() const; + virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const; + virtual std::string to_string() const; + virtual ~cb_frame() = default; + // using default (compiler auto generated) copy and move constructors and assignment operators + + /// Helper functions to convert four scaled values into a byte stream + /// set scale to negative for signed values + static std::vector get_byte_stream(double value1, int scale1); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4); + /// Overloaded function to handle double scale for the fourth value + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); + + /// helper function to extract a scaled double value from two bytes + static double get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed = false); //inv_scale is the reciprocal of scale factor + bool send_frame(esphome::canbus::Canbus *canbus, bool extended_id = false, bool remote_transmission_request = false) const; + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame + }; + + extern const cb_frame emptyframe; // to return a reference to when no valid frame is found + +} // namespace solar +#endif // __SOLAR_CB_FRAME diff --git a/cbf_cache.cpp b/cbf_cache.cpp index 9b17b17..dc359b4 100644 --- a/cbf_cache.cpp +++ b/cbf_cache.cpp @@ -1,93 +1,93 @@ -#include "source/solar/cbf_cache.h" - -namespace solar -{ - static const int comparecolumns[] = {cbf_store::sisortframe, cbf_store::sisortrtr, 0}; - - void cbf_cache::clear() - { - cache_map.clear(); - } - int cbf_cache::size() const - { - return cache_map.size(); - } - bool cbf_cache::hasitem(uint32_t can_id, bool remote_transmission_request) const - { - return hasitem(canid_rtr_t(can_id, remote_transmission_request)); - } - bool cbf_cache::hasitem(canid_rtr_t key) const - { - return cache_map.find(key) != cache_map.end(); - } - cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) - { - return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); - } - const cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) const - { - return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); - } - bool cbf_cache::additem(const cbf_store& storeitem) - { - const auto& ret = cache_map.emplace(canid_rtr_t(storeitem.can_id, storeitem.rtr), cbf_cache_item(storeitem)); - if(ret.second) { - return false; // new item inserted, no publish - } - auto& kvp = *ret.first; - auto& item = kvp.second; - auto updateresult = item.update(storeitem, comparecolumns, 1); - if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { - // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); - return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; - } - // try next store to see if it has a duplicate of new item - updateresult = item.update(storeitem, comparecolumns, 2); - if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { - // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); - return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; - } - item.update(storeitem); - return false; - } - const cb_frame& cbf_cache::get_frame(uint32_t can_id, bool remote_transmission_request) const - { - return get_frame(canid_rtr_t(can_id, remote_transmission_request)); - } - const cb_frame& cbf_cache::get_frame(canid_rtr_t key) const - { - auto it = cache_map.find(key); - if(it == cache_map.end()) - return emptyframe; // return reference to static empty frame if not found - const auto& item = it->second; - return item.get_frame(); - } - bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) - { - auto key = canid_rtr_t(can_id, remote_transmission_request); - if(!this->hasitem(key)) - return false; - const auto& cbf = get_frame(key); - if(cbf.getpublish()) { - return cbf.send_frame(canbus, extended_id, remote_transmission_request); - } - return false; - for(const auto& kvp : cache_map) { - const auto& item = kvp.second; - const auto& cbf = item.get_frame(); - if(cbf.getpublish()) { - cbf.send_frame(canbus, extended_id, remote_transmission_request); - } - } - } - // static version to send arbitrary frame - bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) - { - return cb_frame::send_frame(canbus, can_id, frame, extended_id, remote_transmission_request); - } - bool cbf_cache::send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id) - { - return cb_frame::send_frame(canbus, can_id, {}, extended_id, true); - } - +#include "source/solar/cbf_cache.h" + +namespace solar +{ + static const int comparecolumns[] = {cbf_store::sisortframe, cbf_store::sisortrtr, 0}; + + void cbf_cache::clear() + { + cache_map.clear(); + } + int cbf_cache::size() const + { + return cache_map.size(); + } + bool cbf_cache::hasitem(uint32_t can_id, bool remote_transmission_request) const + { + return hasitem(canid_rtr_t(can_id, remote_transmission_request)); + } + bool cbf_cache::hasitem(canid_rtr_t key) const + { + return cache_map.find(key) != cache_map.end(); + } + cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) + { + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); + } + const cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) const + { + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); + } + bool cbf_cache::additem(const cbf_store& storeitem) + { + const auto& ret = cache_map.emplace(canid_rtr_t(storeitem.can_id, storeitem.rtr), cbf_cache_item(storeitem)); + if(ret.second) { + return false; // new item inserted, no publish + } + auto& kvp = *ret.first; + auto& item = kvp.second; + auto updateresult = item.update(storeitem, comparecolumns, 1); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { + // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; + } + // try next store to see if it has a duplicate of new item + updateresult = item.update(storeitem, comparecolumns, 2); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { + // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; + } + item.update(storeitem); + return false; + } + const cb_frame& cbf_cache::get_frame(uint32_t can_id, bool remote_transmission_request) const + { + return get_frame(canid_rtr_t(can_id, remote_transmission_request)); + } + const cb_frame& cbf_cache::get_frame(canid_rtr_t key) const + { + auto it = cache_map.find(key); + if(it == cache_map.end()) + return emptyframe; // return reference to static empty frame if not found + const auto& item = it->second; + return item.get_frame(); + } + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) + { + auto key = canid_rtr_t(can_id, remote_transmission_request); + if(!this->hasitem(key)) + return false; + const auto& cbf = get_frame(key); + if(cbf.getpublish()) { + return cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + return false; + for(const auto& kvp : cache_map) { + const auto& item = kvp.second; + const auto& cbf = item.get_frame(); + if(cbf.getpublish()) { + cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + } + } + // static version to send arbitrary frame + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + return cb_frame::send_frame(canbus, can_id, frame, extended_id, remote_transmission_request); + } + bool cbf_cache::send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id) + { + return cb_frame::send_frame(canbus, can_id, {}, extended_id, true); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_cache.h b/cbf_cache.h index a88e414..00e705a 100644 --- a/cbf_cache.h +++ b/cbf_cache.h @@ -1,39 +1,39 @@ -// NB! A lot of comments in .h and .cpp files were auto generated by CoPilot. Applicable comments have been retained, others removed. - -#ifndef __SOLAR_CBF_CACHE -#define __SOLAR_CBF_CACHE -#include -#include -#include "esphome.h" -#include "cbf_store_pylon.h" -#include "cbf_cache_item.h" -using namespace esphome; - -namespace solar -{ - class cbf_cache { - public: - typedef std::pair canid_rtr_t; // pair of CAN ID and RTR flag - std::map cache_map; // map of rtr/CAN IDs to cache items - cbf_cache() = default; - void clear(); - int size() const; - bool hasitem(canid_rtr_t key) const; - bool hasitem(uint32_t can_id, bool remote_transmission_request) const; - cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request); - const cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request) const; - // Add a new item to the cache or update an existing one - bool additem(const cbf_store& item); - const cb_frame& get_frame(canid_rtr_t key) const; - const cb_frame& get_frame(uint32_t can_id, bool remote_transmission_request) const; - virtual ~cbf_cache() = default; // virtual destructor for base class - bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); - static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame - static bool send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false); // static version to send remote transmission request frame - // using default (compiler auto generated) copy and move constructors and assignment operators - - }; - -} // namespace solar - +// NB! A lot of comments in .h and .cpp files were auto generated by CoPilot. Applicable comments have been retained, others removed. + +#ifndef __SOLAR_CBF_CACHE +#define __SOLAR_CBF_CACHE +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +#include "cbf_cache_item.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache { + public: + typedef std::pair canid_rtr_t; // pair of CAN ID and RTR flag + std::map cache_map; // map of rtr/CAN IDs to cache items + cbf_cache() = default; + void clear(); + int size() const; + bool hasitem(canid_rtr_t key) const; + bool hasitem(uint32_t can_id, bool remote_transmission_request) const; + cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request); + const cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request) const; + // Add a new item to the cache or update an existing one + bool additem(const cbf_store& item); + const cb_frame& get_frame(canid_rtr_t key) const; + const cb_frame& get_frame(uint32_t can_id, bool remote_transmission_request) const; + virtual ~cbf_cache() = default; // virtual destructor for base class + bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame + static bool send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false); // static version to send remote transmission request frame + // using default (compiler auto generated) copy and move constructors and assignment operators + + }; + +} // namespace solar + #endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_cache_item.cpp b/cbf_cache_item.cpp index 8972d5f..3b20ed1 100644 --- a/cbf_cache_item.cpp +++ b/cbf_cache_item.cpp @@ -1,117 +1,117 @@ -// if filename is changes, remove old .o file from \\TRUENAS\esphome\config\.esphome\build\sthome-ut8\.pioenvs\sthome-ut8\src\source\solar -#include "source/solar/cbf_cache_item.h" - -namespace solar -{ - cbf_cache_item::cbf_cache_item() - { - this->store0 = std::make_shared(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store - this->store1 = std::make_shared(0, 0, 2, byte_vector(), false, 0, 0); // make generic empty store - ESP_LOGI( this->store0->tag("cache_item CTOR0A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); - ESP_LOGI( this->store1->tag("cache_item CTOR0B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); - } - cbf_cache_item::cbf_cache_item(const cbf_store& store0) - { - this->store0 = store0.clone(); - this->store1 = store0.clone(); - this->store0->id = 1; - this->store1->id = 2; - } - void cbf_cache_item::clear() - { - store0 = nullptr; - store1 = nullptr; - } - void cbf_cache_item::swap(cbf_cache_item &s) - { - std::swap(this->store0, s.store0); - std::swap(this->store1, s.store1); - } - bool cbf_cache_item::update(const cbf_store& newitem) - { - if(!this->store0->is_valid()) { - store0 = newitem.clone(); - store0->id = 1; - return true; - } - if(!this->store1->is_valid()) { - store1 = newitem.clone(); - store1->id = 2; - return true; - } - bool result = store1->last_timestamp > store0->last_timestamp; - if(result) { - store0 = newitem.clone(); - this->store0->id = 1; - } - else { - store1 = newitem.clone(); - this->store1->id = 2; - } - return result; - } - cbf_store::cbf_updateresult cbf_cache_item::update(const cbf_store& newitem, const int *comparecolumns, int store_selector) - { - cbf_store::cbf_updateresult result = cbf_store::cbf_updateresult::stu_NONE; - switch(store_selector) { - case 2: - result = this->store1->update(newitem, comparecolumns); - break; - default: - result = this->store0->update(newitem, comparecolumns); - break; - } - return result; - } - const cb_frame& cbf_cache_item::get_frame() const - { - return get_store(); // return most recent valid store's frame - } - const cbf_store& cbf_cache_item::get_store() const - { - if(this->store0->is_valid() && !this->store1->is_valid()) { - return *store0; - } - if(!this->store0->is_valid() && this->store1->is_valid()) { - return *store1; - } - if(this->store0->is_valid() && this->store1->is_valid()) { - if(this->store0->last_timestamp >= this->store1->last_timestamp) { - return *this->store0; - } else { - return *this->store1; - } - } - return *this->store0; // no valid stores, but return store0 which is always present - } - std::string cbf_cache_item::tag(const std::string& prefix) const - { - return get_store().tag(prefix, prefix.length()); - } - std::string cbf_cache_item::to_string() const - { - return get_store().to_string(); - } - std::string cbf_cache_item::tag0(const std::string& prefix) const - { - return store0->tag(prefix); - } - std::string cbf_cache_item::tag1(const std::string& prefix) const - { - return store1->tag(prefix); - } - std::string cbf_cache_item::st0_tostring() const - { - char buffer[150]; - snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str()); - return std::string(buffer); - } - std::string cbf_cache_item::st1_tostring() const - { - char buffer[150]; - snprintf(buffer, sizeof(buffer), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str()); - return std::string(buffer); - } - -} // namespace solar - +// if filename is changes, remove old .o file from \\TRUENAS\esphome\config\.esphome\build\sthome-ut8\.pioenvs\sthome-ut8\src\source\solar +#include "source/solar/cbf_cache_item.h" + +namespace solar +{ + cbf_cache_item::cbf_cache_item() + { + this->store0 = std::make_shared(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store + this->store1 = std::make_shared(0, 0, 2, byte_vector(), false, 0, 0); // make generic empty store + ESP_LOGI( this->store0->tag("cache_item CTOR0A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); + ESP_LOGI( this->store1->tag("cache_item CTOR0B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); + } + cbf_cache_item::cbf_cache_item(const cbf_store& store0) + { + this->store0 = store0.clone(); + this->store1 = store0.clone(); + this->store0->id = 1; + this->store1->id = 2; + } + void cbf_cache_item::clear() + { + store0 = nullptr; + store1 = nullptr; + } + void cbf_cache_item::swap(cbf_cache_item &s) + { + std::swap(this->store0, s.store0); + std::swap(this->store1, s.store1); + } + bool cbf_cache_item::update(const cbf_store& newitem) + { + if(!this->store0->is_valid()) { + store0 = newitem.clone(); + store0->id = 1; + return true; + } + if(!this->store1->is_valid()) { + store1 = newitem.clone(); + store1->id = 2; + return true; + } + bool result = store1->last_timestamp > store0->last_timestamp; + if(result) { + store0 = newitem.clone(); + this->store0->id = 1; + } + else { + store1 = newitem.clone(); + this->store1->id = 2; + } + return result; + } + cbf_store::cbf_updateresult cbf_cache_item::update(const cbf_store& newitem, const int *comparecolumns, int store_selector) + { + cbf_store::cbf_updateresult result = cbf_store::cbf_updateresult::stu_NONE; + switch(store_selector) { + case 2: + result = this->store1->update(newitem, comparecolumns); + break; + default: + result = this->store0->update(newitem, comparecolumns); + break; + } + return result; + } + const cb_frame& cbf_cache_item::get_frame() const + { + return get_store(); // return most recent valid store's frame + } + const cbf_store& cbf_cache_item::get_store() const + { + if(this->store0->is_valid() && !this->store1->is_valid()) { + return *store0; + } + if(!this->store0->is_valid() && this->store1->is_valid()) { + return *store1; + } + if(this->store0->is_valid() && this->store1->is_valid()) { + if(this->store0->last_timestamp >= this->store1->last_timestamp) { + return *this->store0; + } else { + return *this->store1; + } + } + return *this->store0; // no valid stores, but return store0 which is always present + } + std::string cbf_cache_item::tag(const std::string& prefix) const + { + return get_store().tag(prefix, prefix.length()); + } + std::string cbf_cache_item::to_string() const + { + return get_store().to_string(); + } + std::string cbf_cache_item::tag0(const std::string& prefix) const + { + return store0->tag(prefix); + } + std::string cbf_cache_item::tag1(const std::string& prefix) const + { + return store1->tag(prefix); + } + std::string cbf_cache_item::st0_tostring() const + { + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str()); + return std::string(buffer); + } + std::string cbf_cache_item::st1_tostring() const + { + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str()); + return std::string(buffer); + } + +} // namespace solar + diff --git a/cbf_cache_item.h b/cbf_cache_item.h index 0bf7e43..6aedb9c 100644 --- a/cbf_cache_item.h +++ b/cbf_cache_item.h @@ -1,42 +1,42 @@ -#ifndef __SOLAR_CBF_CACHE_ITEM -#define __SOLAR_CBF_CACHE_ITEM -#include -#include -#include "esphome.h" -#include "cbf_store_pylon.h" -using namespace esphome; - -namespace solar -{ - class cbf_cache_item { - private: - const int storecount = 2; - public: - std::shared_ptr store0; - std::shared_ptr store1; - - cbf_cache_item(); // to allow object to be used as a value element in map operator[] method - cbf_cache_item(const cbf_store& store0); - void clear(); - void swap(cbf_cache_item &s); - bool update(const cbf_store& newitem); - cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns, int store_selector); - const cb_frame& get_frame() const; // returns the most recent valid frame - const cbf_store& get_store() const; // returns the most recent valid store - virtual std::string tag0(const std::string& prefix = "") const; - virtual std::string tag1(const std::string& prefix = "") const; - virtual std::string tag(const std::string& prefix = "") const; - virtual std::string to_string() const; - virtual std::string st0_tostring() const; // string of store 0 - virtual std::string st1_tostring() const; // string of store 1 - // the default copy and move constructors and assignment operators are fine - // because shared_ptr takes care of the underlying memory management - cbf_cache_item(const cbf_cache_item& b) = default; - cbf_cache_item& operator=(const cbf_cache_item& b) = default; - cbf_cache_item(cbf_cache_item&& src) = default; - cbf_cache_item& operator=(cbf_cache_item&& src) = default; - virtual ~cbf_cache_item() = default; // virtual destructor for base class - }; - -} // namespace solar +#ifndef __SOLAR_CBF_CACHE_ITEM +#define __SOLAR_CBF_CACHE_ITEM +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache_item { + private: + const int storecount = 2; + public: + std::shared_ptr store0; + std::shared_ptr store1; + + cbf_cache_item(); // to allow object to be used as a value element in map operator[] method + cbf_cache_item(const cbf_store& store0); + void clear(); + void swap(cbf_cache_item &s); + bool update(const cbf_store& newitem); + cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns, int store_selector); + const cb_frame& get_frame() const; // returns the most recent valid frame + const cbf_store& get_store() const; // returns the most recent valid store + virtual std::string tag0(const std::string& prefix = "") const; + virtual std::string tag1(const std::string& prefix = "") const; + virtual std::string tag(const std::string& prefix = "") const; + virtual std::string to_string() const; + virtual std::string st0_tostring() const; // string of store 0 + virtual std::string st1_tostring() const; // string of store 1 + // the default copy and move constructors and assignment operators are fine + // because shared_ptr takes care of the underlying memory management + cbf_cache_item(const cbf_cache_item& b) = default; + cbf_cache_item& operator=(const cbf_cache_item& b) = default; + cbf_cache_item(cbf_cache_item&& src) = default; + cbf_cache_item& operator=(cbf_cache_item&& src) = default; + virtual ~cbf_cache_item() = default; // virtual destructor for base class + }; + +} // namespace solar #endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_pylon.cpp b/cbf_pylon.cpp index abb9b35..7cfc2fa 100644 --- a/cbf_pylon.cpp +++ b/cbf_pylon.cpp @@ -1,158 +1,158 @@ -#include "cbf_pylon.h" - -namespace solar -{ - // see common.h for definition of publish_spec_t - // publish_spec_t(int on_count, int interval, int timeout) - const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages - const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests - - cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) - { - if (this != &src) { - cb_frame::operator=(std::move(src)); - src.clear(); - //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); - } - return *this; - } - void cbf_pylon::clear() - { - cb_frame::clear(); - } - - // float cbf_pylon::_get_battery_charge_voltage_limit() const - // { - // const auto& x = this->frame; - // return 0.1 * (float)((x[1] << 8) + x[0]); - // } - - // Function to build a message from the pylon canbus frame - std::string cbf_pylon::to_string() const - { - char buffer[80]; - switch (can_id) - { - case CB_BATTERY_LIMITS: - { - if(rtr) { - return "Request for BATTERY LIMITS info"; - } - if (this->frame.size() < 6) { - return "Invalid frame size for CB_BATTERY_LIMITS"; - } - const auto& x = this->frame; - float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V - float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A - float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A - snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); - return buffer; - } - break; - case CB_BATTERY_STATE: - { - if(rtr) { - return "Request for BATTERY STATE info"; - } - if (this->frame.size() < 4) { - return "Invalid frame size for CB_BATTERY_STATE"; - } - const auto& x = this->frame; - uint soc = static_cast((x[1] << 8) + x[0]); - uint soh = static_cast((x[3] << 8) + x[2]); - snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); - return buffer; - } - break; - case CB_BATTERY_STATUS: - { - if(rtr) { - return "Request for BATTERY STATUS info"; - } - if (this->frame.size() < 6) { - return "Invalid frame size for CB_BATTERY_STATUS"; - } - const auto& x = this->frame; - float system_voltage = 0.01 * static_cast((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system - float system_current = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current - float average_cell_temperature = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1°C - snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature); - return buffer; - } - break; - case CB_BATTERY_FAULT: - { - if(rtr) { - return "Request for BATTERY FAULT info"; - } - if (this->frame.size() < 8) { - return "Invalid frame size for CB_BATTERY_FAULT"; - } - const auto& x = this->frame; - uint8_t protection1 = x[0]; - uint8_t protection2 = x[1]; - uint8_t alarm1 = x[2]; - uint8_t alarm2 = x[3]; - uint8_t module_numbers = x[4]; - char ch5 = x[5]; - char ch6 = x[6]; - bool discharge_over_current = protection1 & 0x80; - bool cell_under_temperature = protection1 & 0x10; - bool cell_over_temperature = protection1 & 0x08; - bool cell_or_module_under_voltage = protection1 & 0x04; - bool cell_or_module_over_voltage = protection1 & 0x02; - bool system_error = protection2 & 0x8; - bool charge_over_current = protection2 & 0x01; - bool discharge_high_current = alarm1 & 0x80; - bool cell_low_temperature = alarm1 & 0x10; - bool cell_high_temperature = alarm1 & 0x08; - bool cell_or_module_low_voltage = alarm1 & 0x04; - bool cell_or_module_high_voltage = alarm1 & 0x02; - bool internal_communication_fail = alarm2 & 0x8; - bool charge_high_current = alarm2 & 0x01; - snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6); - return buffer; - } - break; - case CB_BATTERY_REQUEST_FLAGS: - { - if(rtr) { - return "Request for BATTERY REQUEST FLAGS info"; - } - if (this->frame.size() < 1) { - return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; - } - const auto& x = this->frame; - uint8_t request_flag = x[0]; - bool charge_enable = request_flag & 0x80; - bool discharge_enable = request_flag & 0x40; - bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it. - bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently. - bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1. - snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : ""); - return buffer; - } - break; - case CB_BATTERY_MANUFACTURER: - { - if(rtr) { - return "Request for BATTERY MANUFACTURER info"; - } - if (this->frame.size() < 8) { - return "Invalid frame size for CB_BATTERY_MANUFACTURER"; - } - const auto& x = this->frame; - // Manufacturer name is in the first 8 bytes, padded with spaces - // Convert to string and trim trailing spaces - std::string manufacturer(reinterpret_cast(x.data()), 8); - manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); - return "BATTERY OEM: " + manufacturer; - } - break; - } - if(rtr) { - return "Request for unknown pylon CAN ID"; - } - return "Unknown CAN ID"; - } +#include "cbf_pylon.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages + const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests + + cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_pylon::clear() + { + cb_frame::clear(); + } + + // float cbf_pylon::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + + // Function to build a message from the pylon canbus frame + std::string cbf_pylon::to_string() const + { + char buffer[80]; + switch (can_id) + { + case CB_BATTERY_LIMITS: + { + if(rtr) { + return "Request for BATTERY LIMITS info"; + } + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_LIMITS"; + } + const auto& x = this->frame; + float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V + float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A + float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A + snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); + return buffer; + } + break; + case CB_BATTERY_STATE: + { + if(rtr) { + return "Request for BATTERY STATE info"; + } + if (this->frame.size() < 4) { + return "Invalid frame size for CB_BATTERY_STATE"; + } + const auto& x = this->frame; + uint soc = static_cast((x[1] << 8) + x[0]); + uint soh = static_cast((x[3] << 8) + x[2]); + snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); + return buffer; + } + break; + case CB_BATTERY_STATUS: + { + if(rtr) { + return "Request for BATTERY STATUS info"; + } + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_STATUS"; + } + const auto& x = this->frame; + float system_voltage = 0.01 * static_cast((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system + float system_current = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current + float average_cell_temperature = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1°C + snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature); + return buffer; + } + break; + case CB_BATTERY_FAULT: + { + if(rtr) { + return "Request for BATTERY FAULT info"; + } + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_FAULT"; + } + const auto& x = this->frame; + uint8_t protection1 = x[0]; + uint8_t protection2 = x[1]; + uint8_t alarm1 = x[2]; + uint8_t alarm2 = x[3]; + uint8_t module_numbers = x[4]; + char ch5 = x[5]; + char ch6 = x[6]; + bool discharge_over_current = protection1 & 0x80; + bool cell_under_temperature = protection1 & 0x10; + bool cell_over_temperature = protection1 & 0x08; + bool cell_or_module_under_voltage = protection1 & 0x04; + bool cell_or_module_over_voltage = protection1 & 0x02; + bool system_error = protection2 & 0x8; + bool charge_over_current = protection2 & 0x01; + bool discharge_high_current = alarm1 & 0x80; + bool cell_low_temperature = alarm1 & 0x10; + bool cell_high_temperature = alarm1 & 0x08; + bool cell_or_module_low_voltage = alarm1 & 0x04; + bool cell_or_module_high_voltage = alarm1 & 0x02; + bool internal_communication_fail = alarm2 & 0x8; + bool charge_high_current = alarm2 & 0x01; + snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6); + return buffer; + } + break; + case CB_BATTERY_REQUEST_FLAGS: + { + if(rtr) { + return "Request for BATTERY REQUEST FLAGS info"; + } + if (this->frame.size() < 1) { + return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; + } + const auto& x = this->frame; + uint8_t request_flag = x[0]; + bool charge_enable = request_flag & 0x80; + bool discharge_enable = request_flag & 0x40; + bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it. + bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently. + bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1. + snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : ""); + return buffer; + } + break; + case CB_BATTERY_MANUFACTURER: + { + if(rtr) { + return "Request for BATTERY MANUFACTURER info"; + } + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_MANUFACTURER"; + } + const auto& x = this->frame; + // Manufacturer name is in the first 8 bytes, padded with spaces + // Convert to string and trim trailing spaces + std::string manufacturer(reinterpret_cast(x.data()), 8); + manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); + return "BATTERY OEM: " + manufacturer; + } + break; + } + if(rtr) { + return "Request for unknown pylon CAN ID"; + } + return "Unknown CAN ID"; + } } // namespace solar \ No newline at end of file diff --git a/cbf_pylon.h b/cbf_pylon.h index 5b6f0ed..f2f76e4 100644 --- a/cbf_pylon.h +++ b/cbf_pylon.h @@ -1,40 +1,40 @@ -#ifndef __SOLAR_CBF_PYLON // include GUARD -#define __SOLAR_CBF_PYLON -#include "esphome.h" -#include -#include "common.h" -#include "cb_frame.h" -using namespace esphome; - -namespace solar -{ - class cbf_pylon : virtual public cb_frame { - public: - // Pylontech publish spec - // This is used to determine when to publish the battery data from the Pylontech battery - static const publish_spec_t publish_spec; // default publish spec for Pylontech battery messages - static const publish_spec_t rtr_publish_spec; // for remote transmission requests - - // Pylontech battery CAN IDs - // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole - // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery - static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message - static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message - static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message - static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message - static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message - static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message - - //Property battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit }; - //Property battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit}; - - cbf_pylon() = default; - cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); - void clear(); - virtual std::string to_string() const override; - cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon - // using default (compiler auto generated) copy and move constructors and assignment operators - }; - -} // namespace solar +#ifndef __SOLAR_CBF_PYLON // include GUARD +#define __SOLAR_CBF_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + class cbf_pylon : virtual public cb_frame { + public: + // Pylontech publish spec + // This is used to determine when to publish the battery data from the Pylontech battery + static const publish_spec_t publish_spec; // default publish spec for Pylontech battery messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + + // Pylontech battery CAN IDs + // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole + // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery + static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message + static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message + static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message + static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message + static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message + static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message + + //Property battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit}; + + cbf_pylon() = default; + cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); + void clear(); + virtual std::string to_string() const override; + cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + }; + +} // namespace solar #endif // __SOLAR_CBF_PYLON \ No newline at end of file diff --git a/cbf_sthome.cpp b/cbf_sthome.cpp index 2d0e5dd..878f9f6 100644 --- a/cbf_sthome.cpp +++ b/cbf_sthome.cpp @@ -1,342 +1,342 @@ -#include "cbf_sthome.h" - -namespace solar -{ - // see common.h for definition of publish_spec_t - // publish_spec_t(int on_count, int interval, int timeout) - const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec - const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests - - const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators - - //cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) - // : cb_frame(msg_id, can_id, frame, rtr) - // { - // } - cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src) - { - if (this != &src) { - cb_frame::operator=(std::move(src)); - src.clear(); - //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); - } - return *this; - } - void cbf_sthome::clear() - { - cb_frame::clear(); - } - - bool cbf_sthome::verify_heartbeat() const - { - const auto& x = this->frame; - std::string hb(reinterpret_cast(x.data()), x.size()); - hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end()); - return hb == heartbeat; - } - // float cbf_sthome::_get_battery_charge_voltage_limit() const - // { - // const auto& x = this->frame; - // return 0.1 * (float)((x[1] << 8) + x[0]); - // } - - // Function to build a message from the pylon canbus frame - std::string cbf_sthome::to_string() const - { - char buffer[128]; - const auto& x = this->frame; - switch (can_id) - { - case CB_CANBUS_ID01: - return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : ""; - case CB_CANBUS_ID02: - return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : ""; - case CB_CANBUS_ID03: - return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : ""; - case CB_CANBUS_ID04: - return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : ""; - case CB_CANBUS_ID05: - return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : ""; - case CB_CANBUS_ID06: - return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : ""; - case CB_CANBUS_ID07: - return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : ""; - case CB_CANBUS_ID08: - return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : ""; - case CB_CANBUS_ID09: - return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : ""; - case CB_CANBUS_ID10: - return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : ""; - case CB_GEYSER_TEMPERATURE_TOP: - { - if(rtr) { - return "Request for geyser top temp"; - } - double temp_top = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top); - return buffer; - } - case CB_GEYSER_TEMPERATURE_BOTTOM: - { - if(rtr) { - return "Request for geyser bottom temp"; - } - double temp_bot = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot); - return buffer; - } - case CB_GEYSER_TEMPERATURE_AMBIENT: - { - if(rtr) { - return "Request for geyser ambient temp"; - } - double temp_amb = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb); - return buffer; - } - case CB_GEYSER_HEATING: - { - if(rtr) { - return "Request for geyser heating"; - } - double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W - double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W - double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W - unsigned int est_heating_time = (unsigned int) static_cast(256 * x[7] + x[6]); // 0 to 65536 seconds - snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time); - return buffer; - } - case CB_GEYSER_ACTIVE_SCHEDULE: - { - if(rtr) { - return "Request for geyser schedule"; - } - double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C - int active_heating_time = static_cast(256 * x[3] + x[2]); // -32768 to 32768s - double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s - int active_schedule_day = static_cast(x[6]); - snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day); - return buffer; - } - case CB_POWER_MAINS: - { - if(rtr) { - return "Request for power mains"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_INVERTER: - { - if(rtr) { - return "Request for power inverter"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_PLUGS: - { - if(rtr) { - return "Request for power plugs"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_LIGHTS: - { - if(rtr) { - return "Request for power lights"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_GEYSER: - { - if(rtr) { - return "Request for power geyser"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_POOL: - { - if(rtr) { - return "Request for power pool"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_GENERATED: - { - if(rtr) { - return "Request for power generated"; - } - double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW - double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW - snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss); - return buffer; - } - case CB_ENERGY_MAINS: - { - if(rtr) { - return "Request for energy mains"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_GEYSER: - { - if(rtr) { - return "Request for energy geyser"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_POOL: - { - if(rtr) { - return "Request for energy pool"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_PLUGS: - { - if(rtr) { - return "Request for energy plugs"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_LIGHTS: - { - if(rtr) { - return "Request for energy lights"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_HOUSE: - { - if(rtr) { - return "Request for energy house"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_GENERATED: - { - if(rtr) { - return "Request for energy generated"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_LOSS: - { - if(rtr) { - return "Request for energy loss"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_CONTROLLER_STATES: - { - if(rtr) { - return "Request for controller states"; - } - uint8_t alarms = x[0]; - uint8_t states = x[1]; - uint8_t modes = x[2]; - bool inverter_1_battery_low = alarms & 0x80; - bool inverter_2_battery_low = alarms & 0x10; - bool inverter_overload = alarms & 0x08; - bool geyser_heating = states & 0x80; - bool geyser_energised = states & 0x40; - bool mains_supply_present = states & 0x20; - bool battery_charging = states & 0x08; - bool vacation_mode = modes & 0x80; // nobody at home - std::string mode = ""; - int geysermode = (modes & 0x70) >> 4; - switch (geysermode) { - case GM_SUNDAY: - mode = "SUN"; - break; - case GM_WORKDAY: - mode = "WRK"; - break; - case GM_SATURDAY: - mode = "SAT"; - break; - case GM_PUBLIC_HOLIDAY: - mode = "PUB"; - break; - case GM_SCHOOL_HOLIDAY: - mode = "SCH"; - break; - default: - mode = "UNK"; - break; - } - snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : ""); - return buffer; - } - - } - if(rtr) { - return "Request for unknown sthome CAN ID"; - } - return "Unknown CAN ID"; - } - +#include "cbf_sthome.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec + const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests + + const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators + + //cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + // : cb_frame(msg_id, can_id, frame, rtr) + // { + // } + cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_sthome::clear() + { + cb_frame::clear(); + } + + bool cbf_sthome::verify_heartbeat() const + { + const auto& x = this->frame; + std::string hb(reinterpret_cast(x.data()), x.size()); + hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end()); + return hb == heartbeat; + } + // float cbf_sthome::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + + // Function to build a message from the pylon canbus frame + std::string cbf_sthome::to_string() const + { + char buffer[128]; + const auto& x = this->frame; + switch (can_id) + { + case CB_CANBUS_ID01: + return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : ""; + case CB_CANBUS_ID02: + return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : ""; + case CB_CANBUS_ID03: + return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : ""; + case CB_CANBUS_ID04: + return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : ""; + case CB_CANBUS_ID05: + return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : ""; + case CB_CANBUS_ID06: + return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : ""; + case CB_CANBUS_ID07: + return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : ""; + case CB_CANBUS_ID08: + return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : ""; + case CB_CANBUS_ID09: + return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : ""; + case CB_CANBUS_ID10: + return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : ""; + case CB_GEYSER_TEMPERATURE_TOP: + { + if(rtr) { + return "Request for geyser top temp"; + } + double temp_top = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top); + return buffer; + } + case CB_GEYSER_TEMPERATURE_BOTTOM: + { + if(rtr) { + return "Request for geyser bottom temp"; + } + double temp_bot = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot); + return buffer; + } + case CB_GEYSER_TEMPERATURE_AMBIENT: + { + if(rtr) { + return "Request for geyser ambient temp"; + } + double temp_amb = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb); + return buffer; + } + case CB_GEYSER_HEATING: + { + if(rtr) { + return "Request for geyser heating"; + } + double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W + double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W + double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W + unsigned int est_heating_time = (unsigned int) static_cast(256 * x[7] + x[6]); // 0 to 65536 seconds + snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time); + return buffer; + } + case CB_GEYSER_ACTIVE_SCHEDULE: + { + if(rtr) { + return "Request for geyser schedule"; + } + double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C + int active_heating_time = static_cast(256 * x[3] + x[2]); // -32768 to 32768s + double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s + int active_schedule_day = static_cast(x[6]); + snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day); + return buffer; + } + case CB_POWER_MAINS: + { + if(rtr) { + return "Request for power mains"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_INVERTER: + { + if(rtr) { + return "Request for power inverter"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_PLUGS: + { + if(rtr) { + return "Request for power plugs"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_LIGHTS: + { + if(rtr) { + return "Request for power lights"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GEYSER: + { + if(rtr) { + return "Request for power geyser"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_POOL: + { + if(rtr) { + return "Request for power pool"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GENERATED: + { + if(rtr) { + return "Request for power generated"; + } + double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW + double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW + snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss); + return buffer; + } + case CB_ENERGY_MAINS: + { + if(rtr) { + return "Request for energy mains"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GEYSER: + { + if(rtr) { + return "Request for energy geyser"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_POOL: + { + if(rtr) { + return "Request for energy pool"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_PLUGS: + { + if(rtr) { + return "Request for energy plugs"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LIGHTS: + { + if(rtr) { + return "Request for energy lights"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_HOUSE: + { + if(rtr) { + return "Request for energy house"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GENERATED: + { + if(rtr) { + return "Request for energy generated"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LOSS: + { + if(rtr) { + return "Request for energy loss"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_CONTROLLER_STATES: + { + if(rtr) { + return "Request for controller states"; + } + uint8_t alarms = x[0]; + uint8_t states = x[1]; + uint8_t modes = x[2]; + bool inverter_1_battery_low = alarms & 0x80; + bool inverter_2_battery_low = alarms & 0x10; + bool inverter_overload = alarms & 0x08; + bool geyser_heating = states & 0x80; + bool geyser_energised = states & 0x40; + bool mains_supply_present = states & 0x20; + bool battery_charging = states & 0x08; + bool vacation_mode = modes & 0x80; // nobody at home + std::string mode = ""; + int geysermode = (modes & 0x70) >> 4; + switch (geysermode) { + case GM_SUNDAY: + mode = "SUN"; + break; + case GM_WORKDAY: + mode = "WRK"; + break; + case GM_SATURDAY: + mode = "SAT"; + break; + case GM_PUBLIC_HOLIDAY: + mode = "PUB"; + break; + case GM_SCHOOL_HOLIDAY: + mode = "SCH"; + break; + default: + mode = "UNK"; + break; + } + snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : ""); + return buffer; + } + + } + if(rtr) { + return "Request for unknown sthome CAN ID"; + } + return "Unknown CAN ID"; + } + } // namespace solar \ No newline at end of file diff --git a/cbf_sthome.h b/cbf_sthome.h index 2016f87..8f5076a 100644 --- a/cbf_sthome.h +++ b/cbf_sthome.h @@ -1,78 +1,78 @@ -#ifndef __SOLAR_CBF_STHOME_H // include GUARD -#define __SOLAR_CBF_STHOME_H -#include "esphome.h" -#include -#include "common.h" -#include "cb_frame.h" -using namespace esphome; - -namespace solar -{ - - class cbf_sthome : virtual public cb_frame { - private: - // float _get_battery_charge_voltage_limit() const; - - public: - // STHOME publish spec - // This is used to determine when to publish the sthome data - static const publish_spec_t publish_spec; // default publish spec for sthome messages - static const publish_spec_t rtr_publish_spec; // for remote transmission requests - - enum geyser_modes : int { - GM_SUNDAY, - GM_WORKDAY, - GM_SATURDAY, - GM_PUBLIC_HOLIDAY, - GM_SCHOOL_HOLIDAY, - }; - enum canbus_ids : int { - CB_CANBUS_ID01 = 0x501, - CB_CANBUS_ID02, - CB_CANBUS_ID03, - CB_CANBUS_ID04, - CB_CANBUS_ID05, - CB_CANBUS_ID06, - CB_CANBUS_ID07, - CB_CANBUS_ID08, - CB_CANBUS_ID09, - CB_CANBUS_ID10, - CB_POWER_MAINS = 0x401, - CB_POWER_INVERTER, - CB_POWER_PLUGS, - CB_POWER_LIGHTS, - CB_POWER_GEYSER, - CB_POWER_POOL, - CB_POWER_GENERATED, - CB_ENERGY_MAINS, - CB_ENERGY_GEYSER, - CB_ENERGY_POOL, - CB_ENERGY_PLUGS, - CB_ENERGY_LIGHTS, - CB_ENERGY_HOUSE, - CB_ENERGY_GENERATED, - CB_ENERGY_LOSS, - CB_CONTROLLER_STATES, - CB_GEYSER_TEMPERATURE_TOP, - CB_GEYSER_TEMPERATURE_BOTTOM, - CB_GEYSER_TEMPERATURE_AMBIENT, - CB_GEYSER_HEATING, - CB_GEYSER_ACTIVE_SCHEDULE, - }; - - //Property battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit }; - //Property battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit}; - static const std::string heartbeat; - - cbf_sthome() = default; - cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); - void clear(); - virtual std::string to_string() const override; - cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon - // using default (compiler auto generated) copy and move constructors and assignment operators - virtual ~cbf_sthome() = default; - bool verify_heartbeat() const; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STHOME_H // include GUARD +#define __SOLAR_CBF_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + + class cbf_sthome : virtual public cb_frame { + private: + // float _get_battery_charge_voltage_limit() const; + + public: + // STHOME publish spec + // This is used to determine when to publish the sthome data + static const publish_spec_t publish_spec; // default publish spec for sthome messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + + enum geyser_modes : int { + GM_SUNDAY, + GM_WORKDAY, + GM_SATURDAY, + GM_PUBLIC_HOLIDAY, + GM_SCHOOL_HOLIDAY, + }; + enum canbus_ids : int { + CB_CANBUS_ID01 = 0x501, + CB_CANBUS_ID02, + CB_CANBUS_ID03, + CB_CANBUS_ID04, + CB_CANBUS_ID05, + CB_CANBUS_ID06, + CB_CANBUS_ID07, + CB_CANBUS_ID08, + CB_CANBUS_ID09, + CB_CANBUS_ID10, + CB_POWER_MAINS = 0x401, + CB_POWER_INVERTER, + CB_POWER_PLUGS, + CB_POWER_LIGHTS, + CB_POWER_GEYSER, + CB_POWER_POOL, + CB_POWER_GENERATED, + CB_ENERGY_MAINS, + CB_ENERGY_GEYSER, + CB_ENERGY_POOL, + CB_ENERGY_PLUGS, + CB_ENERGY_LIGHTS, + CB_ENERGY_HOUSE, + CB_ENERGY_GENERATED, + CB_ENERGY_LOSS, + CB_CONTROLLER_STATES, + CB_GEYSER_TEMPERATURE_TOP, + CB_GEYSER_TEMPERATURE_BOTTOM, + CB_GEYSER_TEMPERATURE_AMBIENT, + CB_GEYSER_HEATING, + CB_GEYSER_ACTIVE_SCHEDULE, + }; + + //Property battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit}; + static const std::string heartbeat; + + cbf_sthome() = default; + cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); + void clear(); + virtual std::string to_string() const override; + cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + virtual ~cbf_sthome() = default; + bool verify_heartbeat() const; + }; + +} // namespace solar #endif // __SOLAR_CBF_STHOME_H \ No newline at end of file diff --git a/cbf_store.cpp b/cbf_store.cpp index 5d98ac0..9f6b327 100644 --- a/cbf_store.cpp +++ b/cbf_store.cpp @@ -1,235 +1,235 @@ -#include "source/solar/cbf_store.h" - -namespace solar -{ - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id) - : cb_frame(msg_id, can_id) - { - this->id = id; // used for debugging, can be omitted - this->count = 0; - this->first_timestamp = 0; - this->last_timestamp = 0; - // ESP_LOGI(tag("store CTOR1").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, time_t first_timestamp, time_t last_timestamp) - : cb_frame(msg_id, can_id) - { - this->id = id; - this->count = 0; - this->first_timestamp = first_timestamp; - this->last_timestamp = last_timestamp; - // ESP_LOGI(tag("store CTOR2").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& frame, bool rtr, time_t first_timestamp, time_t last_timestamp) - : cb_frame(msg_id, can_id, frame, rtr) - { - this->id = id; - this->count = 0; - this->first_timestamp = first_timestamp; - this->last_timestamp = last_timestamp; - // ESP_LOGI(tag("store CTOR3").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(const cbf_store& b, int id) - : cb_frame(b) - { - this->id = id; - this->count = b.count; - this->first_timestamp = b.first_timestamp; - this->last_timestamp = b.last_timestamp; - // ESP_LOGI(tag("store CCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); - } - cbf_store::cbf_store(const cbf_store&& b, int id) - : cb_frame(b) - { - this->id = id; - this->count = b.count; - this->first_timestamp = b.first_timestamp; - this->last_timestamp = b.last_timestamp; - // ESP_LOGI(tag("store MCCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); - } - std::shared_ptr cbf_store::clone() const - { - return clone_impl(); - } - std::shared_ptr cbf_store::clone_impl() const - { - ESP_LOGW(tag("store CLONE").c_str(), "%-20s", "Cloning store"); // this should happen as all cloning should be done by derived classes - return std::make_shared(*this); - } - bool cbf_store::is_publish_expired(time_t currenttime, int update_interval) const - { - return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= update_interval); - } - bool cbf_store::is_validity_expired(time_t currenttime, int timeout_interval) const - { - return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); - } - cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, const int *comparecolumns) - { - ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing the publish_specs as parameters - return update(newitem, publish_spec_t{1, 5, 10}, publish_spec_t{1, 5, 10}, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation - } - cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns) - { - if(!is_valid()) { - return cbf_updateresult::stu_NONE; // cannot update an invalid store - } - auto pbspec = rtr ? rtr_publish_spec : publish_spec; - time_t newtime = newitem.last_timestamp; - bool publish_expired = this->is_publish_expired(newtime, pbspec.interval); - bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout); - bool isduplicate = this->compare(newitem, comparecolumns) == 0; - time_t reset_timer = this->first_timestamp + pbspec.interval - newtime; - int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; - auto result = isduplicate ? cbf_updateresult::stu_DUPLICATE : cbf_updateresult::stu_NONE; - // auto ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); - // auto ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); - // auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); - // if(rtr) - // ESP_LOGI(this->tag("store UPDATE").c_str(), "A: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec// .interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(),// result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); - if(validity_expired) { - *this = newitem; - if(!isduplicate) { - this->count = 1; - publish_expired = false; - } - } - if(isduplicate || publish_expired) { - this->count++; - if(this->count == pbspec.on_count) - this->setpublish(); // must be reset by caller after publish - if(this->getpublish()) - result = static_cast(result | cbf_updateresult::stu_PUBLISH); - this->last_timestamp = newtime; - if(reset_timer <= 0) { - this->first_timestamp = newtime; - this->count = 1; - } - } - // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); - // ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); - // if(rtr) - // ESP_LOGI(this->tag("store UPDATE").c_str(), "B: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec.interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(), result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); - - return result; - } - int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const - { - int result = 0; - bool stopcompare = false; - bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; - int ncmpcols = 0; - while (comparecolumns[ncmpcols] != 0) ncmpcols++; - if (isdefaultcompare) { - for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { - bool validnextcol = i < sisortEND - 1; - result = compare(b, i, &stopcompare, validnextcol); - } - } - else { - for (int i = 0; i < sisortEND && !stopcompare && result == 0 && comparecolumns[i] != 0; i++) { - int cmpcol = comparecolumns[i]; - bool validnextcol = cmpcol != 0 && comparecolumns[i + 1] != 0; - result = compare(b, cmpcol, &stopcompare, validnextcol); - } - } - return result; - } - int cbf_store::compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const - { - int result = 0; - int reverseorderflag = cmpflag & sisortreverse; - int casesensitiveflag = cmpflag & sisortcase; - int nulliswildcardflag = cmpflag & sisortwild; - int stopcompareflag = cmpflag & sistopcomparecol; - cmpflag = cmpflag & ~FLAGBITS; - if (cmpflag == 0) - return result; - bool casesensitive = casesensitiveflag != 0; - bool sortwild = nulliswildcardflag != 0; - bool stopcompare = stopcompareflag != 0; - *stopcomparesignal = false; - switch (cmpflag) { - case sisortNULL: - return 0; - case sisortcanid: - { - bool bothvalid = this->can_id != 0 && b.can_id != 0; - if (bothvalid) { - result = num_compare(this->can_id, b.can_id); - *stopcomparesignal = stopcompare; // set flag only if both items are valid - } else { - if (sortwild) - result = 0; - else { - bool bothinvalid = !(this->can_id || b.can_id); - if (validnextcol && bothinvalid) - result = 0; //if validnextcol then return 0 if both IDs are invalid - result = this->can_id ? 1 : -1; - } - } - break; - } - case sisortmsgid: - result = num_compare(this->msg_id, b.msg_id); - break; - case sisortcount: - { - result = num_compare(this->count, b.count); - break; - } - case sisortfirst_timestamp: - { - auto a_ts = this->first_timestamp; - auto b_ts = b.first_timestamp; - result = num_compare(a_ts, b_ts); - break; - } - case sisortlast_timestamp: - { - auto a_ts = this->last_timestamp; - auto b_ts = b.last_timestamp; - result = num_compare(a_ts, b_ts); - break; - } - case sisortrtr: - result = bool_compare(this->rtr, b.rtr); - break; - case sisortframe: - { - result = this->compare_frame(b.frame); - break; - } - default: - result = 0; - // ESP_LOGE("cbf_store::compare", "Unknown compare column %d", cmpflag); - *stopcomparesignal = true; // stop compare as we don't know how to handle - // should never reach here as all cases must be dealt with - break; - } - if (reverseorderflag != 0) - result = -result; - return result; - } - std::string cbf_store::tag(const std::string& prefix, int prefixlen) const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - char tag[48]; - if(prefix.length() == 0) - snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " "); - else - snprintf(tag, sizeof(tag), "%-*s #%04d s%d 0x%03X %s", prefixlen, prefix.c_str(), msg_id, id, can_id, getpublish() ? "P" : " "); - return std::string(tag); - } - std::string cbf_store::to_string() const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - char buffer[80]; - auto ftstime = is_valid() ? ESPTime::from_epoch_local(first_timestamp).strftime("%H:%M:%S") : "N/A"; - auto ltstime = is_valid() ? ESPTime::from_epoch_local(last_timestamp).strftime("%H:%M:%S") : "N/A"; - snprintf(buffer, sizeof(buffer), " Fts: %s Lts: %s Count: %2d %s", ftstime.c_str(), ltstime.c_str(), count, cb_frame::to_string().c_str()); - return std::string(buffer); - } -} // namespace solar +#include "source/solar/cbf_store.h" + +namespace solar +{ + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id) + : cb_frame(msg_id, can_id) + { + this->id = id; // used for debugging, can be omitted + this->count = 0; + this->first_timestamp = 0; + this->last_timestamp = 0; + // ESP_LOGI(tag("store CTOR1").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR2").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& frame, bool rtr, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id, frame, rtr) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR3").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store CCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store&& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store MCCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + std::shared_ptr cbf_store::clone() const + { + return clone_impl(); + } + std::shared_ptr cbf_store::clone_impl() const + { + ESP_LOGW(tag("store CLONE").c_str(), "%-20s", "Cloning store"); // this should happen as all cloning should be done by derived classes + return std::make_shared(*this); + } + bool cbf_store::is_publish_expired(time_t currenttime, int update_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= update_interval); + } + bool cbf_store::is_validity_expired(time_t currenttime, int timeout_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); + } + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, const int *comparecolumns) + { + ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing the publish_specs as parameters + return update(newitem, publish_spec_t{1, 5, 10}, publish_spec_t{1, 5, 10}, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation + } + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns) + { + if(!is_valid()) { + return cbf_updateresult::stu_NONE; // cannot update an invalid store + } + auto pbspec = rtr ? rtr_publish_spec : publish_spec; + time_t newtime = newitem.last_timestamp; + bool publish_expired = this->is_publish_expired(newtime, pbspec.interval); + bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout); + bool isduplicate = this->compare(newitem, comparecolumns) == 0; + time_t reset_timer = this->first_timestamp + pbspec.interval - newtime; + int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; + auto result = isduplicate ? cbf_updateresult::stu_DUPLICATE : cbf_updateresult::stu_NONE; + // auto ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // auto ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "A: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec// .interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(),// result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); + if(validity_expired) { + *this = newitem; + if(!isduplicate) { + this->count = 1; + publish_expired = false; + } + } + if(isduplicate || publish_expired) { + this->count++; + if(this->count == pbspec.on_count) + this->setpublish(); // must be reset by caller after publish + if(this->getpublish()) + result = static_cast(result | cbf_updateresult::stu_PUBLISH); + this->last_timestamp = newtime; + if(reset_timer <= 0) { + this->first_timestamp = newtime; + this->count = 1; + } + } + // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "B: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec.interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(), result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); + + return result; + } + int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } + else { + for (int i = 0; i < sisortEND && !stopcompare && result == 0 && comparecolumns[i] != 0; i++) { + int cmpcol = comparecolumns[i]; + bool validnextcol = cmpcol != 0 && comparecolumns[i + 1] != 0; + result = compare(b, cmpcol, &stopcompare, validnextcol); + } + } + return result; + } + int cbf_store::compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->can_id != 0 && b.can_id != 0; + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->can_id || b.can_id); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortcount: + { + result = num_compare(this->count, b.count); + break; + } + case sisortfirst_timestamp: + { + auto a_ts = this->first_timestamp; + auto b_ts = b.first_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortlast_timestamp: + { + auto a_ts = this->last_timestamp; + auto b_ts = b.last_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + // ESP_LOGE("cbf_store::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + std::string cbf_store::tag(const std::string& prefix, int prefixlen) const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " "); + else + snprintf(tag, sizeof(tag), "%-*s #%04d s%d 0x%03X %s", prefixlen, prefix.c_str(), msg_id, id, can_id, getpublish() ? "P" : " "); + return std::string(tag); + } + std::string cbf_store::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char buffer[80]; + auto ftstime = is_valid() ? ESPTime::from_epoch_local(first_timestamp).strftime("%H:%M:%S") : "N/A"; + auto ltstime = is_valid() ? ESPTime::from_epoch_local(last_timestamp).strftime("%H:%M:%S") : "N/A"; + snprintf(buffer, sizeof(buffer), " Fts: %s Lts: %s Count: %2d %s", ftstime.c_str(), ltstime.c_str(), count, cb_frame::to_string().c_str()); + return std::string(buffer); + } +} // namespace solar diff --git a/cbf_store_pylon.cpp b/cbf_store_pylon.cpp index a32a9db..fd7636d 100644 --- a/cbf_store_pylon.cpp +++ b/cbf_store_pylon.cpp @@ -1,71 +1,71 @@ -#include "cbf_store_pylon.h" - -namespace solar -{ - cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) - : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_pylon(msg_id, can_id, frame, rtr) - { - this->id = 0; - this->count = 0; - // ESP_LOGI(tag("store_pylon CTOR1").c_str(), "%-20s %s", "Created store_pylon", this->to_string().c_str()); - } - cbf_store_pylon::cbf_store_pylon(const cbf_store_pylon& b) - : cb_frame(b), cbf_store(b) // call base class copy constructor - { - // ESP_LOGI(tag("store_pylon CCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); - } - cbf_store_pylon& cbf_store_pylon::operator=(const cbf_store_pylon& b) - { - if (this != &b) { - cbf_store_pylon tmp(b); - swap(tmp); - // ESP_LOGI(tag("store_pylon AOPP").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); - } - return *this; - } - cbf_store_pylon::cbf_store_pylon(cbf_store_pylon&& src) - : cb_frame(src), cbf_store(src) - { - src.clear(); - // ESP_LOGI(tag("store_pylon MCCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); - } - cbf_store_pylon& cbf_store_pylon::operator=(cbf_store_pylon&& src) - { - if (this != &src) { - cbf_store::operator=(std::move(src)); - cb_frame::operator=(std::move(src)); - src.clear(); - // ESP_LOGI(tag("store_pylon MOASS").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); - } - return *this; - } - std::shared_ptr cbf_store_pylon::clone() const - { - return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon - } - std::shared_ptr cbf_store_pylon::clone_impl() const - { - //ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon"); - return std::make_shared(*this); - } - void cbf_store_pylon::clear() - { - cb_frame::clear(); - cbf_store::clear(); - } - void cbf_store_pylon::swap(cbf_store_pylon &s) - { - cb_frame::swap(s); - cbf_store::swap(s); - } - cbf_store::cbf_updateresult cbf_store_pylon::update(const cbf_store& newitem, const int *comparecolumns) - { - //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); - return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); - } - std::string cbf_store_pylon::to_string() const - { - return cb_frame::to_string() + " " + cbf_pylon::to_string(); - } - +#include "cbf_store_pylon.h" + +namespace solar +{ + cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_pylon(msg_id, can_id, frame, rtr) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_pylon CTOR1").c_str(), "%-20s %s", "Created store_pylon", this->to_string().c_str()); + } + cbf_store_pylon::cbf_store_pylon(const cbf_store_pylon& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_pylon CCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(const cbf_store_pylon& b) + { + if (this != &b) { + cbf_store_pylon tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_pylon AOPP").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + cbf_store_pylon::cbf_store_pylon(cbf_store_pylon&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_pylon MCCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(cbf_store_pylon&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_pylon MOASS").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_pylon::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon + } + std::shared_ptr cbf_store_pylon::clone_impl() const + { + //ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon"); + return std::make_shared(*this); + } + void cbf_store_pylon::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_pylon::swap(cbf_store_pylon &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + cbf_store::cbf_updateresult cbf_store_pylon::update(const cbf_store& newitem, const int *comparecolumns) + { + //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); + } + std::string cbf_store_pylon::to_string() const + { + return cb_frame::to_string() + " " + cbf_pylon::to_string(); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_store_pylon.h b/cbf_store_pylon.h index 3a854f0..85739da 100644 --- a/cbf_store_pylon.h +++ b/cbf_store_pylon.h @@ -1,34 +1,34 @@ -#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD -#define __SOLAR_CBF_STORE_PYLON -#include "esphome.h" -#include -#include "common.h" -#include "cbf_store.h" -#include "cbf_pylon.h" -using namespace esphome; - -namespace solar -{ - class cbf_store_pylon : public cbf_store, public cbf_pylon - { - public: - cbf_store_pylon() = delete; // default constructor not allowed - cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); - virtual ~cbf_store_pylon() = default; // virtual destructor for base class - std::shared_ptr clone() const; - void clear(); - void swap(cbf_store_pylon &s); - virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; - virtual std::string to_string() const override; - - cbf_store_pylon(const cbf_store_pylon& b); - cbf_store_pylon& operator=(const cbf_store_pylon& b); - cbf_store_pylon(cbf_store_pylon&& src); - cbf_store_pylon& operator=(cbf_store_pylon&& src); - - private: - virtual std::shared_ptr clone_impl() const override; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD +#define __SOLAR_CBF_STORE_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_pylon : public cbf_store, public cbf_pylon + { + public: + cbf_store_pylon() = delete; // default constructor not allowed + cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_pylon() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_pylon &s); + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; + + cbf_store_pylon(const cbf_store_pylon& b); + cbf_store_pylon& operator=(const cbf_store_pylon& b); + cbf_store_pylon(cbf_store_pylon&& src); + cbf_store_pylon& operator=(cbf_store_pylon&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar #endif // __SOLAR_CBF_STORE_PYLON \ No newline at end of file diff --git a/cbf_store_sthome.cpp b/cbf_store_sthome.cpp index b872d15..216b859 100644 --- a/cbf_store_sthome.cpp +++ b/cbf_store_sthome.cpp @@ -1,71 +1,71 @@ -#include "cbf_store_sthome.h" - -namespace solar -{ - cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) - : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr) - { - this->id = 0; - this->count = 0; - // ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str()); - } - cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b) - : cb_frame(b), cbf_store(b) // call base class copy constructor - { - // ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); - } - cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b) - { - if (this != &b) { - cbf_store_sthome tmp(b); - swap(tmp); - // ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); - } - return *this; - } - cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src) - : cb_frame(src), cbf_store(src) - { - src.clear(); - // ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); - } - cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src) - { - if (this != &src) { - cbf_store::operator=(std::move(src)); - cb_frame::operator=(std::move(src)); - src.clear(); - // ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); - } - return *this; - } - std::shared_ptr cbf_store_sthome::clone() const - { - return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome - } - std::shared_ptr cbf_store_sthome::clone_impl() const - { - //ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome"); - return std::make_shared(*this); - } - void cbf_store_sthome::clear() - { - cb_frame::clear(); - cbf_store::clear(); - } - void cbf_store_sthome::swap(cbf_store_sthome &s) - { - cb_frame::swap(s); - cbf_store::swap(s); - } - cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns) - { - //ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str()); - return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); - } - std::string cbf_store_sthome::to_string() const - { - return cb_frame::to_string() + " " + cbf_sthome::to_string(); - } - +#include "cbf_store_sthome.h" + +namespace solar +{ + cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str()); + } + cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b) + { + if (this != &b) { + cbf_store_sthome tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_sthome::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome + } + std::shared_ptr cbf_store_sthome::clone_impl() const + { + //ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome"); + return std::make_shared(*this); + } + void cbf_store_sthome::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_sthome::swap(cbf_store_sthome &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns) + { + //ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); + } + std::string cbf_store_sthome::to_string() const + { + return cb_frame::to_string() + " " + cbf_sthome::to_string(); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_store_sthome.h b/cbf_store_sthome.h index f8075f0..bd182bb 100644 --- a/cbf_store_sthome.h +++ b/cbf_store_sthome.h @@ -1,34 +1,34 @@ -#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD -#define __SOLAR_CBF_STORE_STHOME_H -#include "esphome.h" -#include -#include "common.h" -#include "cbf_store.h" -#include "cbf_sthome.h" -using namespace esphome; - -namespace solar -{ - class cbf_store_sthome : public cbf_store, public cbf_sthome - { - public: - cbf_store_sthome() = delete; // default constructor not allowed - cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); - virtual ~cbf_store_sthome() = default; // virtual destructor for base class - std::shared_ptr clone() const; - void clear(); - void swap(cbf_store_sthome &s); - virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; - virtual std::string to_string() const override; - - cbf_store_sthome(const cbf_store_sthome& b); - cbf_store_sthome& operator=(const cbf_store_sthome& b); - cbf_store_sthome(cbf_store_sthome&& src); - cbf_store_sthome& operator=(cbf_store_sthome&& src); - - private: - virtual std::shared_ptr clone_impl() const override; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD +#define __SOLAR_CBF_STORE_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_sthome.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_sthome : public cbf_store, public cbf_sthome + { + public: + cbf_store_sthome() = delete; // default constructor not allowed + cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_sthome() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_sthome &s); + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; + + cbf_store_sthome(const cbf_store_sthome& b); + cbf_store_sthome& operator=(const cbf_store_sthome& b); + cbf_store_sthome(cbf_store_sthome&& src); + cbf_store_sthome& operator=(cbf_store_sthome&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar #endif // __SOLAR_CBF_STORE_STHOME_H \ No newline at end of file diff --git a/common.h b/common.h index ae88eaf..9f866ad 100644 --- a/common.h +++ b/common.h @@ -1,48 +1,48 @@ -#ifndef __SOLAR_COMMON // include GUARD -#define __SOLAR_COMMON - -namespace solar -{ - - #define FLAGBITS ~0x0FFF // bitwise one's complement (sort column info fits into lower three nibbles) - #define SORTCASE 0x0001000 // case sensitive - #define SORTREVERSE 0x0002000 // reverse order - #define WILDNULL 0x0004000 // zero denotes wildcard, i.e. anything is equal to zero - #define STOPCOMPARE 0x0080000 // stop compare after this column if both items to be compared are valid and a compare was possible - #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) - #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 - #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 - #define max(a, b) ((a) > (b)) ? (a) : (b) - #define min(a, b) ((a) < (b)) ? (a) : (b) - - typedef unsigned int uint; - typedef std::vector byte_vector; - - struct publish_spec_t - { - int on_count; // how many times a duplicate of this frame was received; used to signal publish - int interval; // used for checking whether updateinterval has expired, i.e. to force a publish - int timeout; // used for checking whether timeout has expired, i.e. frame has become stale - publish_spec_t(int on_count, int interval, int timeout) - : on_count(on_count), interval(interval), timeout(timeout) {} - }; - - inline std::string ltrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - size_t start = str.find_first_not_of(chars); - return (start == std::string::npos) ? "" : str.substr(start); - } - - inline std::string rtrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - size_t end = str.find_last_not_of(chars); - return (end == std::string::npos) ? "" : str.substr(0, end + 1); - } - - inline std::string trim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - return rtrim(ltrim(str, chars), chars); - } -} // namespace solar - +#ifndef __SOLAR_COMMON // include GUARD +#define __SOLAR_COMMON + +namespace solar +{ + + #define FLAGBITS ~0x0FFF // bitwise one's complement (sort column info fits into lower three nibbles) + #define SORTCASE 0x0001000 // case sensitive + #define SORTREVERSE 0x0002000 // reverse order + #define WILDNULL 0x0004000 // zero denotes wildcard, i.e. anything is equal to zero + #define STOPCOMPARE 0x0080000 // stop compare after this column if both items to be compared are valid and a compare was possible + #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) + #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 + #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 + #define max(a, b) ((a) > (b)) ? (a) : (b) + #define min(a, b) ((a) < (b)) ? (a) : (b) + + typedef unsigned int uint; + typedef std::vector byte_vector; + + struct publish_spec_t + { + int on_count; // how many times a duplicate of this frame was received; used to signal publish + int interval; // used for checking whether updateinterval has expired, i.e. to force a publish + int timeout; // used for checking whether timeout has expired, i.e. frame has become stale + publish_spec_t(int on_count, int interval, int timeout) + : on_count(on_count), interval(interval), timeout(timeout) {} + }; + + inline std::string ltrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t start = str.find_first_not_of(chars); + return (start == std::string::npos) ? "" : str.substr(start); + } + + inline std::string rtrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t end = str.find_last_not_of(chars); + return (end == std::string::npos) ? "" : str.substr(0, end + 1); + } + + inline std::string trim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + return rtrim(ltrim(str, chars), chars); + } +} // namespace solar + #endif // __SOLAR_COMMON \ No newline at end of file From 605523ede5da8531f8adf97c248ee8dabd8924be Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 20 Mar 2026 17:19:14 +0200 Subject: [PATCH 6/6] merge prep --- README.md => source/solar/README.md | 10 +- cb_frame.cpp => source/solar/cb_frame.cpp | 682 +++++++++--------- cb_frame.h => source/solar/cb_frame.h | 132 ++-- cbf_cache.cpp => source/solar/cbf_cache.cpp | 184 ++--- cbf_cache.h => source/solar/cbf_cache.h | 76 +- .../solar/cbf_cache_item.cpp | 234 +++--- .../solar/cbf_cache_item.h | 82 +-- cbf_pylon.cpp => source/solar/cbf_pylon.cpp | 314 ++++---- cbf_pylon.h => source/solar/cbf_pylon.h | 78 +- cbf_sthome.cpp => source/solar/cbf_sthome.cpp | 682 +++++++++--------- cbf_sthome.h => source/solar/cbf_sthome.h | 154 ++-- cbf_store.cpp => source/solar/cbf_store.cpp | 470 ++++++------ cbf_store.h => source/solar/cbf_store.h | 0 .../solar/cbf_store_pylon.cpp | 140 ++-- .../solar/cbf_store_pylon.h | 66 +- .../solar/cbf_store_sthome.cpp | 140 ++-- .../solar/cbf_store_sthome.h | 66 +- common.h => source/solar/common.h | 94 +-- 18 files changed, 1802 insertions(+), 1802 deletions(-) rename README.md => source/solar/README.md (97%) rename cb_frame.cpp => source/solar/cb_frame.cpp (97%) rename cb_frame.h => source/solar/cb_frame.h (98%) rename cbf_cache.cpp => source/solar/cbf_cache.cpp (97%) rename cbf_cache.h => source/solar/cbf_cache.h (98%) rename cbf_cache_item.cpp => source/solar/cbf_cache_item.cpp (97%) rename cbf_cache_item.h => source/solar/cbf_cache_item.h (97%) rename cbf_pylon.cpp => source/solar/cbf_pylon.cpp (98%) rename cbf_pylon.h => source/solar/cbf_pylon.h (98%) rename cbf_sthome.cpp => source/solar/cbf_sthome.cpp (98%) rename cbf_sthome.h => source/solar/cbf_sthome.h (97%) rename cbf_store.cpp => source/solar/cbf_store.cpp (98%) rename cbf_store.h => source/solar/cbf_store.h (100%) rename cbf_store_pylon.cpp => source/solar/cbf_store_pylon.cpp (97%) rename cbf_store_pylon.h => source/solar/cbf_store_pylon.h (97%) rename cbf_store_sthome.cpp => source/solar/cbf_store_sthome.cpp (97%) rename cbf_store_sthome.h => source/solar/cbf_store_sthome.h (97%) rename common.h => source/solar/common.h (97%) diff --git a/README.md b/source/solar/README.md similarity index 97% rename from README.md rename to source/solar/README.md index 744cfa8..c77a458 100644 --- a/README.md +++ b/source/solar/README.md @@ -1,6 +1,6 @@ -# ESPHome config/source/solar Folder - -This folder contains Contains source code files and related resources for solar projects. - - +# ESPHome config/source/solar Folder + +This folder contains Contains source code files and related resources for solar projects. + + For more information, visit the [ESPHome documentation](https://esphome.io/). \ No newline at end of file diff --git a/cb_frame.cpp b/source/solar/cb_frame.cpp similarity index 97% rename from cb_frame.cpp rename to source/solar/cb_frame.cpp index 2a40669..389fdaa 100644 --- a/cb_frame.cpp +++ b/source/solar/cb_frame.cpp @@ -1,342 +1,342 @@ -#include "source/solar/cb_frame.h" -// limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. -namespace solar -{ - const cb_frame emptyframe = cb_frame(); // to return a reference to when no valid frame is found - - cb_frame::cb_frame() - { - this->msg_id = 0; - this->publish = false; - this->rtr = false; - this->can_id = 0; - this->frame.clear(); - // ESP_LOGI(tag("frame CTOR0").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - cb_frame::cb_frame(int msg_id, uint32_t can_id) - { - this->msg_id = msg_id; - this->publish = false; - this->rtr = false; - this->can_id = can_id; - this->frame.clear(); - // ESP_LOGI(tag("frame CTOR1").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) - { - this->msg_id = msg_id; - this->publish = false; - this->rtr = rtr; - this->can_id = can_id; - this->frame = frame; - // ESP_LOGI(tag("frame CTOR2").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); - } - void cb_frame::clear() - { - this->msg_id = 0; - this->publish = false; - this->rtr = false; - this->can_id = 0; - this->frame.clear(); - } - void cb_frame::swap(cb_frame &s) - { - std::swap(this->msg_id, s.msg_id); - std::swap(this->publish, s.publish); - std::swap(this->rtr, s.rtr); - std::swap(this->can_id, s.can_id); - this->frame.swap(s.frame); - } - bool cb_frame::is_valid() const - { - return this->can_id != 0; - } - void cb_frame::setpublish(bool do_publish) const - { - this->publish = do_publish; - } - bool cb_frame::getpublish() const - { - return this->publish; - } - bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, bool extended_id, bool remote_transmission_request) const - { - if(canbus == nullptr) { - ESP_LOGE(tag("cb_frame::send_data").c_str(), "CAN bus interface is null, cannot send data"); - return false; - } - if(!this->is_valid()) { - ESP_LOGE(tag("cb_frame::send_data").c_str(), "Frame is not valid, cannot send data: %s", this->to_string().c_str()); - return false; - } - return cb_frame::send_frame(canbus, this->can_id, this->frame, extended_id, remote_transmission_request); - } - bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) - { - if(canbus == nullptr) { - return false; - } - canbus->send_data(can_id, extended_id, remote_transmission_request, frame); - return true; - } - int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const - { - int result = 0; - bool stopcompare = false; - bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; - int ncmpcols = 0; - while (comparecolumns[ncmpcols] != 0) ncmpcols++; - if (isdefaultcompare) { - for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { - bool validnextcol = i < sisortEND - 1; - result = compare(b, i, &stopcompare, validnextcol); - } - } else { - for (int i = 0; i < ncmpcols && !stopcompare && result == 0; i++) { - result = compare(b, comparecolumns[i], &stopcompare, true); - } - } - return result; - } - int cb_frame::compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const - { - int result = 0; - int reverseorderflag = cmpflag & sisortreverse; - int casesensitiveflag = cmpflag & sisortcase; - int nulliswildcardflag = cmpflag & sisortwild; - int stopcompareflag = cmpflag & sistopcomparecol; - cmpflag = cmpflag & ~FLAGBITS; - if (cmpflag == 0) - return result; - bool casesensitive = casesensitiveflag != 0; - bool sortwild = nulliswildcardflag != 0; - bool stopcompare = stopcompareflag != 0; - *stopcomparesignal = false; - - switch (cmpflag) { - case sisortNULL: - return 0; - case sisortcanid: - { - bool bothvalid = this->is_valid() && b.is_valid(); - if (bothvalid) { - result = num_compare(this->can_id, b.can_id); - *stopcomparesignal = stopcompare; // set flag only if both items are valid - } else { - if (sortwild) - result = 0; - else { - bool bothinvalid = !(this->is_valid() || b.is_valid()); - if (validnextcol && bothinvalid) - result = 0; //if validnextcol then return 0 if both IDs are invalid - result = this->can_id ? 1 : -1; - } - } - break; - } - case sisortmsgid: - result = num_compare(this->msg_id, b.msg_id); - break; - case sisortrtr: - result = bool_compare(this->rtr, b.rtr); - break; - case sisortframe: - { - result = this->compare_frame(b.frame); - break; - } - default: - result = 0; - ESP_LOGE("cb_frame::compare", "Unknown compare column %d", cmpflag); - *stopcomparesignal = true; // stop compare as we don't know how to handle - // should never reach here as all cases must be dealt with - break; - } - if (reverseorderflag != 0) - result = -result; - return result; - } - int cb_frame::compare_frame(const byte_vector& _frame) const - { - int result = 0; - auto j = _frame.begin(); - for(auto i = this->frame.begin(); i != this->frame.end() && result == 0; ++i) { - if(j == _frame.end()) { - // ESP_LOGW("cb_frame::compare", "Frame size mismatch: this.frame.size()=%zu, _frame.size()=%zu", this->frame.size(), _frame.size()); - result = 1; // this frame is longer than _frame - break; - } - // ESP_LOGI("cb_frame::compare", "Comparing frame byte %zu: %02X vs %02X", i - this->frame.begin(), *i, *j); - result = num_compare(*i, *j); - // if (result != 0) { - // ESP_LOGI("cb_frame::compare", "Frame byte %zu mismatch: %02X vs %02X", i - this->frame.begin(), *i, *j); - // } - j++; - } - return result; - } - std::string cb_frame::tag(const std::string& prefix, int prefixlen) const - { - char tag[48]; - if(prefix.length() == 0) - snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); - else - snprintf(tag, sizeof(tag), "%-*s %04d 0x%03X ", prefixlen, prefix.c_str(), msg_id, can_id); - return std::string(tag); - } - std::string cb_frame::to_string() const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments to avoid buffer overflows and ensure correct formatting. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - std::string text = ""; - char hex[10]; - std::string line; - int n = 0; - if(rtr) { - line = " "; - text = " "; - n = 6; // to align text output - } - else { - if (this->frame.empty()) { - return ""; - } - for (const auto& byte : frame) { - n++; - snprintf(hex, sizeof(hex), "%02X ", byte); - line += hex; - if(byte > 31 && byte < 127) { - text += (char) byte; - } - else { - text += "."; - } - } - } - for (int i = n; i < 8; i++) { - line += " "; - text += " "; - } - return line + " " + text; - } - // helper function to extract a scaled double value from two bytes - double cb_frame::get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed) - { - int value = 0; - if ((lsb == 0xFF && msb == 0xFF) || inv_scale == 0) { - return std::numeric_limits::quiet_NaN(); - } - if (is_signed) { - value = static_cast((msb << 8) + lsb); - } else { - value = static_cast((msb << 8) + lsb); - } - //ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale); - return static_cast(value) / inv_scale; - } - // Helper function to convert four scaled values into a byte stream - // set scale to negative for signed values - std::vector cb_frame::get_byte_stream(double value1, int scale1) - { - std::vector byte_stream(2, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2) - { - std::vector byte_stream(4, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3) - { - std::vector byte_stream(6, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - return byte_stream; - } - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) - { - //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); - std::vector byte_stream(8, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - if(scale4 != 0) { - value = (scale4 < 0) ? static_cast(value4 * -scale4) : static_cast(value4 * scale4); - byte_stream[6] = value % 256; - byte_stream[7] = (value >> 8) % 256; - } - return byte_stream; - } - // Helper function to convert four scaled values into a byte stream - // set scale to negative for signed values - // Overloaded function to handle double scale for the fourth value - std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4) - { - std::vector byte_stream(8, 0); - int value; - if(scale1 != 0) { - value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); - byte_stream[0] = value % 256; - byte_stream[1] = (value >> 8) % 256; - } - if(scale2 != 0) { - value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); - byte_stream[2] = value % 256; - byte_stream[3] = (value >> 8) % 256; - } - if(scale3 != 0) { - value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); - byte_stream[4] = value % 256; - byte_stream[5] = (value >> 8) % 256; - } - value = (scale4 < 0) ? static_cast(round(value4 * -scale4)) : static_cast(round(value4 * scale4)); - byte_stream[6] = value % 256; - byte_stream[7] = (value >> 8) % 256; - return byte_stream; - } - +#include "source/solar/cb_frame.h" +// limit the amount of logging from this class as it can be called very frequently. Too much logging can lead to instability and no connectivity. +namespace solar +{ + const cb_frame emptyframe = cb_frame(); // to return a reference to when no valid frame is found + + cb_frame::cb_frame() + { + this->msg_id = 0; + this->publish = false; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR0").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + cb_frame::cb_frame(int msg_id, uint32_t can_id) + { + this->msg_id = msg_id; + this->publish = false; + this->rtr = false; + this->can_id = can_id; + this->frame.clear(); + // ESP_LOGI(tag("frame CTOR1").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + { + this->msg_id = msg_id; + this->publish = false; + this->rtr = rtr; + this->can_id = can_id; + this->frame = frame; + // ESP_LOGI(tag("frame CTOR2").c_str(), "%-20s %s", "Created frame", this->to_string().c_str()); + } + void cb_frame::clear() + { + this->msg_id = 0; + this->publish = false; + this->rtr = false; + this->can_id = 0; + this->frame.clear(); + } + void cb_frame::swap(cb_frame &s) + { + std::swap(this->msg_id, s.msg_id); + std::swap(this->publish, s.publish); + std::swap(this->rtr, s.rtr); + std::swap(this->can_id, s.can_id); + this->frame.swap(s.frame); + } + bool cb_frame::is_valid() const + { + return this->can_id != 0; + } + void cb_frame::setpublish(bool do_publish) const + { + this->publish = do_publish; + } + bool cb_frame::getpublish() const + { + return this->publish; + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, bool extended_id, bool remote_transmission_request) const + { + if(canbus == nullptr) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "CAN bus interface is null, cannot send data"); + return false; + } + if(!this->is_valid()) { + ESP_LOGE(tag("cb_frame::send_data").c_str(), "Frame is not valid, cannot send data: %s", this->to_string().c_str()); + return false; + } + return cb_frame::send_frame(canbus, this->can_id, this->frame, extended_id, remote_transmission_request); + } + bool cb_frame::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + if(canbus == nullptr) { + return false; + } + canbus->send_data(can_id, extended_id, remote_transmission_request, frame); + return true; + } + int cb_frame::compare(const cb_frame& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } else { + for (int i = 0; i < ncmpcols && !stopcompare && result == 0; i++) { + result = compare(b, comparecolumns[i], &stopcompare, true); + } + } + return result; + } + int cb_frame::compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->is_valid() && b.is_valid(); + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->is_valid() || b.is_valid()); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + ESP_LOGE("cb_frame::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + int cb_frame::compare_frame(const byte_vector& _frame) const + { + int result = 0; + auto j = _frame.begin(); + for(auto i = this->frame.begin(); i != this->frame.end() && result == 0; ++i) { + if(j == _frame.end()) { + // ESP_LOGW("cb_frame::compare", "Frame size mismatch: this.frame.size()=%zu, _frame.size()=%zu", this->frame.size(), _frame.size()); + result = 1; // this frame is longer than _frame + break; + } + // ESP_LOGI("cb_frame::compare", "Comparing frame byte %zu: %02X vs %02X", i - this->frame.begin(), *i, *j); + result = num_compare(*i, *j); + // if (result != 0) { + // ESP_LOGI("cb_frame::compare", "Frame byte %zu mismatch: %02X vs %02X", i - this->frame.begin(), *i, *j); + // } + j++; + } + return result; + } + std::string cb_frame::tag(const std::string& prefix, int prefixlen) const + { + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); + else + snprintf(tag, sizeof(tag), "%-*s %04d 0x%03X ", prefixlen, prefix.c_str(), msg_id, can_id); + return std::string(tag); + } + std::string cb_frame::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments to avoid buffer overflows and ensure correct formatting. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + std::string text = ""; + char hex[10]; + std::string line; + int n = 0; + if(rtr) { + line = " "; + text = " "; + n = 6; // to align text output + } + else { + if (this->frame.empty()) { + return ""; + } + for (const auto& byte : frame) { + n++; + snprintf(hex, sizeof(hex), "%02X ", byte); + line += hex; + if(byte > 31 && byte < 127) { + text += (char) byte; + } + else { + text += "."; + } + } + } + for (int i = n; i < 8; i++) { + line += " "; + text += " "; + } + return line + " " + text; + } + // helper function to extract a scaled double value from two bytes + double cb_frame::get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed) + { + int value = 0; + if ((lsb == 0xFF && msb == 0xFF) || inv_scale == 0) { + return std::numeric_limits::quiet_NaN(); + } + if (is_signed) { + value = static_cast((msb << 8) + lsb); + } else { + value = static_cast((msb << 8) + lsb); + } + //ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale); + return static_cast(value) / inv_scale; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + std::vector cb_frame::get_byte_stream(double value1, int scale1) + { + std::vector byte_stream(2, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2) + { + std::vector byte_stream(4, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3) + { + std::vector byte_stream(6, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + return byte_stream; + } + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4) + { + //ESP_LOGI("cb_frame::get_byte_stream", "1: %.3f 2: %.3f 3: %.3f 4: %.3f", value1, value2, value3, value4); + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + if(scale4 != 0) { + value = (scale4 < 0) ? static_cast(value4 * -scale4) : static_cast(value4 * scale4); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + } + return byte_stream; + } + // Helper function to convert four scaled values into a byte stream + // set scale to negative for signed values + // Overloaded function to handle double scale for the fourth value + std::vector cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4) + { + std::vector byte_stream(8, 0); + int value; + if(scale1 != 0) { + value = (scale1 < 0) ? static_cast(value1 * -scale1) : static_cast(value1 * scale1); + byte_stream[0] = value % 256; + byte_stream[1] = (value >> 8) % 256; + } + if(scale2 != 0) { + value = (scale2 < 0) ? static_cast(value2 * -scale2) : static_cast(value2 * scale2); + byte_stream[2] = value % 256; + byte_stream[3] = (value >> 8) % 256; + } + if(scale3 != 0) { + value = (scale3 < 0) ? static_cast(value3 * -scale3) : static_cast(value3 * scale3); + byte_stream[4] = value % 256; + byte_stream[5] = (value >> 8) % 256; + } + value = (scale4 < 0) ? static_cast(round(value4 * -scale4)) : static_cast(round(value4 * scale4)); + byte_stream[6] = value % 256; + byte_stream[7] = (value >> 8) % 256; + return byte_stream; + } + } // namespace solar \ No newline at end of file diff --git a/cb_frame.h b/source/solar/cb_frame.h similarity index 98% rename from cb_frame.h rename to source/solar/cb_frame.h index 45138f8..0646610 100644 --- a/cb_frame.h +++ b/source/solar/cb_frame.h @@ -1,66 +1,66 @@ -#ifndef __SOLAR_CB_FRAME // include GUARD -#define __SOLAR_CB_FRAME -#include "esphome.h" -#include -#include "common.h" -using namespace esphome; - -namespace solar -{ - class cbf_store; - class cb_frame { - private: - mutable bool publish; // whether this frame should be published - public: - enum cbf_sortcolumns : int - { - sisortNULL, - sisortcanid, - sisortrtr, - sisortframe, - sisortmsgid, - sisortEND, - sisortcase = SORTCASE, // case sensitive - sisortreverse = SORTREVERSE, // reverse order - sisortwild = WILDNULL, // an empty, or null entry equates to wildcard - sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible - }; - int msg_id; - bool rtr; - uint32_t can_id; - byte_vector frame; - cb_frame(); - cb_frame(int msg_id, uint32_t can_id); - cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); - void clear(); - void swap(cb_frame &s); - virtual bool is_valid() const; - virtual int compare(const cb_frame& b, const int *comparecolumns) const; - virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; - int compare_frame(const byte_vector& _frame) const; - void setpublish(bool do_publish = true) const; // we promise only to modify the publish flag - bool getpublish() const; - virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const; - virtual std::string to_string() const; - virtual ~cb_frame() = default; - // using default (compiler auto generated) copy and move constructors and assignment operators - - /// Helper functions to convert four scaled values into a byte stream - /// set scale to negative for signed values - static std::vector get_byte_stream(double value1, int scale1); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3); - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4); - /// Overloaded function to handle double scale for the fourth value - static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); - - /// helper function to extract a scaled double value from two bytes - static double get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed = false); //inv_scale is the reciprocal of scale factor - bool send_frame(esphome::canbus::Canbus *canbus, bool extended_id = false, bool remote_transmission_request = false) const; - static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame - }; - - extern const cb_frame emptyframe; // to return a reference to when no valid frame is found - -} // namespace solar -#endif // __SOLAR_CB_FRAME +#ifndef __SOLAR_CB_FRAME // include GUARD +#define __SOLAR_CB_FRAME +#include "esphome.h" +#include +#include "common.h" +using namespace esphome; + +namespace solar +{ + class cbf_store; + class cb_frame { + private: + mutable bool publish; // whether this frame should be published + public: + enum cbf_sortcolumns : int + { + sisortNULL, + sisortcanid, + sisortrtr, + sisortframe, + sisortmsgid, + sisortEND, + sisortcase = SORTCASE, // case sensitive + sisortreverse = SORTREVERSE, // reverse order + sisortwild = WILDNULL, // an empty, or null entry equates to wildcard + sistopcomparecol = STOPCOMPARE // stop compare after this column if both items to be compared are valid and a compare was possible + }; + int msg_id; + bool rtr; + uint32_t can_id; + byte_vector frame; + cb_frame(); + cb_frame(int msg_id, uint32_t can_id); + cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); + void clear(); + void swap(cb_frame &s); + virtual bool is_valid() const; + virtual int compare(const cb_frame& b, const int *comparecolumns) const; + virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; + int compare_frame(const byte_vector& _frame) const; + void setpublish(bool do_publish = true) const; // we promise only to modify the publish flag + bool getpublish() const; + virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const; + virtual std::string to_string() const; + virtual ~cb_frame() = default; + // using default (compiler auto generated) copy and move constructors and assignment operators + + /// Helper functions to convert four scaled values into a byte stream + /// set scale to negative for signed values + static std::vector get_byte_stream(double value1, int scale1); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3); + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, int scale4); + /// Overloaded function to handle double scale for the fourth value + static std::vector get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4); + + /// helper function to extract a scaled double value from two bytes + static double get_double(uint8_t lsb, uint8_t msb, int inv_scale, bool is_signed = false); //inv_scale is the reciprocal of scale factor + bool send_frame(esphome::canbus::Canbus *canbus, bool extended_id = false, bool remote_transmission_request = false) const; + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame + }; + + extern const cb_frame emptyframe; // to return a reference to when no valid frame is found + +} // namespace solar +#endif // __SOLAR_CB_FRAME diff --git a/cbf_cache.cpp b/source/solar/cbf_cache.cpp similarity index 97% rename from cbf_cache.cpp rename to source/solar/cbf_cache.cpp index dc359b4..9b17b17 100644 --- a/cbf_cache.cpp +++ b/source/solar/cbf_cache.cpp @@ -1,93 +1,93 @@ -#include "source/solar/cbf_cache.h" - -namespace solar -{ - static const int comparecolumns[] = {cbf_store::sisortframe, cbf_store::sisortrtr, 0}; - - void cbf_cache::clear() - { - cache_map.clear(); - } - int cbf_cache::size() const - { - return cache_map.size(); - } - bool cbf_cache::hasitem(uint32_t can_id, bool remote_transmission_request) const - { - return hasitem(canid_rtr_t(can_id, remote_transmission_request)); - } - bool cbf_cache::hasitem(canid_rtr_t key) const - { - return cache_map.find(key) != cache_map.end(); - } - cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) - { - return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); - } - const cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) const - { - return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); - } - bool cbf_cache::additem(const cbf_store& storeitem) - { - const auto& ret = cache_map.emplace(canid_rtr_t(storeitem.can_id, storeitem.rtr), cbf_cache_item(storeitem)); - if(ret.second) { - return false; // new item inserted, no publish - } - auto& kvp = *ret.first; - auto& item = kvp.second; - auto updateresult = item.update(storeitem, comparecolumns, 1); - if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { - // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); - return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; - } - // try next store to see if it has a duplicate of new item - updateresult = item.update(storeitem, comparecolumns, 2); - if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { - // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); - return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; - } - item.update(storeitem); - return false; - } - const cb_frame& cbf_cache::get_frame(uint32_t can_id, bool remote_transmission_request) const - { - return get_frame(canid_rtr_t(can_id, remote_transmission_request)); - } - const cb_frame& cbf_cache::get_frame(canid_rtr_t key) const - { - auto it = cache_map.find(key); - if(it == cache_map.end()) - return emptyframe; // return reference to static empty frame if not found - const auto& item = it->second; - return item.get_frame(); - } - bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) - { - auto key = canid_rtr_t(can_id, remote_transmission_request); - if(!this->hasitem(key)) - return false; - const auto& cbf = get_frame(key); - if(cbf.getpublish()) { - return cbf.send_frame(canbus, extended_id, remote_transmission_request); - } - return false; - for(const auto& kvp : cache_map) { - const auto& item = kvp.second; - const auto& cbf = item.get_frame(); - if(cbf.getpublish()) { - cbf.send_frame(canbus, extended_id, remote_transmission_request); - } - } - } - // static version to send arbitrary frame - bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) - { - return cb_frame::send_frame(canbus, can_id, frame, extended_id, remote_transmission_request); - } - bool cbf_cache::send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id) - { - return cb_frame::send_frame(canbus, can_id, {}, extended_id, true); - } - +#include "source/solar/cbf_cache.h" + +namespace solar +{ + static const int comparecolumns[] = {cbf_store::sisortframe, cbf_store::sisortrtr, 0}; + + void cbf_cache::clear() + { + cache_map.clear(); + } + int cbf_cache::size() const + { + return cache_map.size(); + } + bool cbf_cache::hasitem(uint32_t can_id, bool remote_transmission_request) const + { + return hasitem(canid_rtr_t(can_id, remote_transmission_request)); + } + bool cbf_cache::hasitem(canid_rtr_t key) const + { + return cache_map.find(key) != cache_map.end(); + } + cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) + { + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); + } + const cbf_cache_item& cbf_cache::getitem(uint32_t can_id, bool remote_transmission_request) const + { + return cache_map.at(canid_rtr_t(can_id, remote_transmission_request)); + } + bool cbf_cache::additem(const cbf_store& storeitem) + { + const auto& ret = cache_map.emplace(canid_rtr_t(storeitem.can_id, storeitem.rtr), cbf_cache_item(storeitem)); + if(ret.second) { + return false; // new item inserted, no publish + } + auto& kvp = *ret.first; + auto& item = kvp.second; + auto updateresult = item.update(storeitem, comparecolumns, 1); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { + // ESP_LOGI(item.store0.tag("== ST1 DUP == ").c_str(), item.store0.to_string().c_str()); + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; + } + // try next store to see if it has a duplicate of new item + updateresult = item.update(storeitem, comparecolumns, 2); + if(updateresult & cbf_store::cbf_updateresult::stu_DUPLICATE) { + // ESP_LOGI(item.store1.tag("== ST2 DUP == ").c_str(), item.store1.to_string().c_str()); + return updateresult & cbf_store::cbf_updateresult::stu_PUBLISH; + } + item.update(storeitem); + return false; + } + const cb_frame& cbf_cache::get_frame(uint32_t can_id, bool remote_transmission_request) const + { + return get_frame(canid_rtr_t(can_id, remote_transmission_request)); + } + const cb_frame& cbf_cache::get_frame(canid_rtr_t key) const + { + auto it = cache_map.find(key); + if(it == cache_map.end()) + return emptyframe; // return reference to static empty frame if not found + const auto& item = it->second; + return item.get_frame(); + } + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id, bool remote_transmission_request) + { + auto key = canid_rtr_t(can_id, remote_transmission_request); + if(!this->hasitem(key)) + return false; + const auto& cbf = get_frame(key); + if(cbf.getpublish()) { + return cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + return false; + for(const auto& kvp : cache_map) { + const auto& item = kvp.second; + const auto& cbf = item.get_frame(); + if(cbf.getpublish()) { + cbf.send_frame(canbus, extended_id, remote_transmission_request); + } + } + } + // static version to send arbitrary frame + bool cbf_cache::send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id, bool remote_transmission_request) + { + return cb_frame::send_frame(canbus, can_id, frame, extended_id, remote_transmission_request); + } + bool cbf_cache::send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id) + { + return cb_frame::send_frame(canbus, can_id, {}, extended_id, true); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_cache.h b/source/solar/cbf_cache.h similarity index 98% rename from cbf_cache.h rename to source/solar/cbf_cache.h index 00e705a..a88e414 100644 --- a/cbf_cache.h +++ b/source/solar/cbf_cache.h @@ -1,39 +1,39 @@ -// NB! A lot of comments in .h and .cpp files were auto generated by CoPilot. Applicable comments have been retained, others removed. - -#ifndef __SOLAR_CBF_CACHE -#define __SOLAR_CBF_CACHE -#include -#include -#include "esphome.h" -#include "cbf_store_pylon.h" -#include "cbf_cache_item.h" -using namespace esphome; - -namespace solar -{ - class cbf_cache { - public: - typedef std::pair canid_rtr_t; // pair of CAN ID and RTR flag - std::map cache_map; // map of rtr/CAN IDs to cache items - cbf_cache() = default; - void clear(); - int size() const; - bool hasitem(canid_rtr_t key) const; - bool hasitem(uint32_t can_id, bool remote_transmission_request) const; - cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request); - const cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request) const; - // Add a new item to the cache or update an existing one - bool additem(const cbf_store& item); - const cb_frame& get_frame(canid_rtr_t key) const; - const cb_frame& get_frame(uint32_t can_id, bool remote_transmission_request) const; - virtual ~cbf_cache() = default; // virtual destructor for base class - bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); - static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame - static bool send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false); // static version to send remote transmission request frame - // using default (compiler auto generated) copy and move constructors and assignment operators - - }; - -} // namespace solar - +// NB! A lot of comments in .h and .cpp files were auto generated by CoPilot. Applicable comments have been retained, others removed. + +#ifndef __SOLAR_CBF_CACHE +#define __SOLAR_CBF_CACHE +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +#include "cbf_cache_item.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache { + public: + typedef std::pair canid_rtr_t; // pair of CAN ID and RTR flag + std::map cache_map; // map of rtr/CAN IDs to cache items + cbf_cache() = default; + void clear(); + int size() const; + bool hasitem(canid_rtr_t key) const; + bool hasitem(uint32_t can_id, bool remote_transmission_request) const; + cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request); + const cbf_cache_item& getitem(uint32_t can_id, bool remote_transmission_request) const; + // Add a new item to the cache or update an existing one + bool additem(const cbf_store& item); + const cb_frame& get_frame(canid_rtr_t key) const; + const cb_frame& get_frame(uint32_t can_id, bool remote_transmission_request) const; + virtual ~cbf_cache() = default; // virtual destructor for base class + bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false, bool remote_transmission_request = false); + static bool send_frame(esphome::canbus::Canbus *canbus, uint32_t can_id, const byte_vector& frame, bool extended_id = false, bool remote_transmission_request = false); // static version to send arbitrary frame + static bool send_request(esphome::canbus::Canbus *canbus, uint32_t can_id, bool extended_id = false); // static version to send remote transmission request frame + // using default (compiler auto generated) copy and move constructors and assignment operators + + }; + +} // namespace solar + #endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_cache_item.cpp b/source/solar/cbf_cache_item.cpp similarity index 97% rename from cbf_cache_item.cpp rename to source/solar/cbf_cache_item.cpp index 3b20ed1..8972d5f 100644 --- a/cbf_cache_item.cpp +++ b/source/solar/cbf_cache_item.cpp @@ -1,117 +1,117 @@ -// if filename is changes, remove old .o file from \\TRUENAS\esphome\config\.esphome\build\sthome-ut8\.pioenvs\sthome-ut8\src\source\solar -#include "source/solar/cbf_cache_item.h" - -namespace solar -{ - cbf_cache_item::cbf_cache_item() - { - this->store0 = std::make_shared(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store - this->store1 = std::make_shared(0, 0, 2, byte_vector(), false, 0, 0); // make generic empty store - ESP_LOGI( this->store0->tag("cache_item CTOR0A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); - ESP_LOGI( this->store1->tag("cache_item CTOR0B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); - } - cbf_cache_item::cbf_cache_item(const cbf_store& store0) - { - this->store0 = store0.clone(); - this->store1 = store0.clone(); - this->store0->id = 1; - this->store1->id = 2; - } - void cbf_cache_item::clear() - { - store0 = nullptr; - store1 = nullptr; - } - void cbf_cache_item::swap(cbf_cache_item &s) - { - std::swap(this->store0, s.store0); - std::swap(this->store1, s.store1); - } - bool cbf_cache_item::update(const cbf_store& newitem) - { - if(!this->store0->is_valid()) { - store0 = newitem.clone(); - store0->id = 1; - return true; - } - if(!this->store1->is_valid()) { - store1 = newitem.clone(); - store1->id = 2; - return true; - } - bool result = store1->last_timestamp > store0->last_timestamp; - if(result) { - store0 = newitem.clone(); - this->store0->id = 1; - } - else { - store1 = newitem.clone(); - this->store1->id = 2; - } - return result; - } - cbf_store::cbf_updateresult cbf_cache_item::update(const cbf_store& newitem, const int *comparecolumns, int store_selector) - { - cbf_store::cbf_updateresult result = cbf_store::cbf_updateresult::stu_NONE; - switch(store_selector) { - case 2: - result = this->store1->update(newitem, comparecolumns); - break; - default: - result = this->store0->update(newitem, comparecolumns); - break; - } - return result; - } - const cb_frame& cbf_cache_item::get_frame() const - { - return get_store(); // return most recent valid store's frame - } - const cbf_store& cbf_cache_item::get_store() const - { - if(this->store0->is_valid() && !this->store1->is_valid()) { - return *store0; - } - if(!this->store0->is_valid() && this->store1->is_valid()) { - return *store1; - } - if(this->store0->is_valid() && this->store1->is_valid()) { - if(this->store0->last_timestamp >= this->store1->last_timestamp) { - return *this->store0; - } else { - return *this->store1; - } - } - return *this->store0; // no valid stores, but return store0 which is always present - } - std::string cbf_cache_item::tag(const std::string& prefix) const - { - return get_store().tag(prefix, prefix.length()); - } - std::string cbf_cache_item::to_string() const - { - return get_store().to_string(); - } - std::string cbf_cache_item::tag0(const std::string& prefix) const - { - return store0->tag(prefix); - } - std::string cbf_cache_item::tag1(const std::string& prefix) const - { - return store1->tag(prefix); - } - std::string cbf_cache_item::st0_tostring() const - { - char buffer[150]; - snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str()); - return std::string(buffer); - } - std::string cbf_cache_item::st1_tostring() const - { - char buffer[150]; - snprintf(buffer, sizeof(buffer), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str()); - return std::string(buffer); - } - -} // namespace solar - +// if filename is changes, remove old .o file from \\TRUENAS\esphome\config\.esphome\build\sthome-ut8\.pioenvs\sthome-ut8\src\source\solar +#include "source/solar/cbf_cache_item.h" + +namespace solar +{ + cbf_cache_item::cbf_cache_item() + { + this->store0 = std::make_shared(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store + this->store1 = std::make_shared(0, 0, 2, byte_vector(), false, 0, 0); // make generic empty store + ESP_LOGI( this->store0->tag("cache_item CTOR0A ").c_str(), "%-20s %s", "Created cache item", this->store0->to_string().c_str()); + ESP_LOGI( this->store1->tag("cache_item CTOR0B ").c_str(), "%-20s %s", "Created cache item", this->store1->to_string().c_str()); + } + cbf_cache_item::cbf_cache_item(const cbf_store& store0) + { + this->store0 = store0.clone(); + this->store1 = store0.clone(); + this->store0->id = 1; + this->store1->id = 2; + } + void cbf_cache_item::clear() + { + store0 = nullptr; + store1 = nullptr; + } + void cbf_cache_item::swap(cbf_cache_item &s) + { + std::swap(this->store0, s.store0); + std::swap(this->store1, s.store1); + } + bool cbf_cache_item::update(const cbf_store& newitem) + { + if(!this->store0->is_valid()) { + store0 = newitem.clone(); + store0->id = 1; + return true; + } + if(!this->store1->is_valid()) { + store1 = newitem.clone(); + store1->id = 2; + return true; + } + bool result = store1->last_timestamp > store0->last_timestamp; + if(result) { + store0 = newitem.clone(); + this->store0->id = 1; + } + else { + store1 = newitem.clone(); + this->store1->id = 2; + } + return result; + } + cbf_store::cbf_updateresult cbf_cache_item::update(const cbf_store& newitem, const int *comparecolumns, int store_selector) + { + cbf_store::cbf_updateresult result = cbf_store::cbf_updateresult::stu_NONE; + switch(store_selector) { + case 2: + result = this->store1->update(newitem, comparecolumns); + break; + default: + result = this->store0->update(newitem, comparecolumns); + break; + } + return result; + } + const cb_frame& cbf_cache_item::get_frame() const + { + return get_store(); // return most recent valid store's frame + } + const cbf_store& cbf_cache_item::get_store() const + { + if(this->store0->is_valid() && !this->store1->is_valid()) { + return *store0; + } + if(!this->store0->is_valid() && this->store1->is_valid()) { + return *store1; + } + if(this->store0->is_valid() && this->store1->is_valid()) { + if(this->store0->last_timestamp >= this->store1->last_timestamp) { + return *this->store0; + } else { + return *this->store1; + } + } + return *this->store0; // no valid stores, but return store0 which is always present + } + std::string cbf_cache_item::tag(const std::string& prefix) const + { + return get_store().tag(prefix, prefix.length()); + } + std::string cbf_cache_item::to_string() const + { + return get_store().to_string(); + } + std::string cbf_cache_item::tag0(const std::string& prefix) const + { + return store0->tag(prefix); + } + std::string cbf_cache_item::tag1(const std::string& prefix) const + { + return store1->tag(prefix); + } + std::string cbf_cache_item::st0_tostring() const + { + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str()); + return std::string(buffer); + } + std::string cbf_cache_item::st1_tostring() const + { + char buffer[150]; + snprintf(buffer, sizeof(buffer), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str()); + return std::string(buffer); + } + +} // namespace solar + diff --git a/cbf_cache_item.h b/source/solar/cbf_cache_item.h similarity index 97% rename from cbf_cache_item.h rename to source/solar/cbf_cache_item.h index 6aedb9c..0bf7e43 100644 --- a/cbf_cache_item.h +++ b/source/solar/cbf_cache_item.h @@ -1,42 +1,42 @@ -#ifndef __SOLAR_CBF_CACHE_ITEM -#define __SOLAR_CBF_CACHE_ITEM -#include -#include -#include "esphome.h" -#include "cbf_store_pylon.h" -using namespace esphome; - -namespace solar -{ - class cbf_cache_item { - private: - const int storecount = 2; - public: - std::shared_ptr store0; - std::shared_ptr store1; - - cbf_cache_item(); // to allow object to be used as a value element in map operator[] method - cbf_cache_item(const cbf_store& store0); - void clear(); - void swap(cbf_cache_item &s); - bool update(const cbf_store& newitem); - cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns, int store_selector); - const cb_frame& get_frame() const; // returns the most recent valid frame - const cbf_store& get_store() const; // returns the most recent valid store - virtual std::string tag0(const std::string& prefix = "") const; - virtual std::string tag1(const std::string& prefix = "") const; - virtual std::string tag(const std::string& prefix = "") const; - virtual std::string to_string() const; - virtual std::string st0_tostring() const; // string of store 0 - virtual std::string st1_tostring() const; // string of store 1 - // the default copy and move constructors and assignment operators are fine - // because shared_ptr takes care of the underlying memory management - cbf_cache_item(const cbf_cache_item& b) = default; - cbf_cache_item& operator=(const cbf_cache_item& b) = default; - cbf_cache_item(cbf_cache_item&& src) = default; - cbf_cache_item& operator=(cbf_cache_item&& src) = default; - virtual ~cbf_cache_item() = default; // virtual destructor for base class - }; - -} // namespace solar +#ifndef __SOLAR_CBF_CACHE_ITEM +#define __SOLAR_CBF_CACHE_ITEM +#include +#include +#include "esphome.h" +#include "cbf_store_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_cache_item { + private: + const int storecount = 2; + public: + std::shared_ptr store0; + std::shared_ptr store1; + + cbf_cache_item(); // to allow object to be used as a value element in map operator[] method + cbf_cache_item(const cbf_store& store0); + void clear(); + void swap(cbf_cache_item &s); + bool update(const cbf_store& newitem); + cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns, int store_selector); + const cb_frame& get_frame() const; // returns the most recent valid frame + const cbf_store& get_store() const; // returns the most recent valid store + virtual std::string tag0(const std::string& prefix = "") const; + virtual std::string tag1(const std::string& prefix = "") const; + virtual std::string tag(const std::string& prefix = "") const; + virtual std::string to_string() const; + virtual std::string st0_tostring() const; // string of store 0 + virtual std::string st1_tostring() const; // string of store 1 + // the default copy and move constructors and assignment operators are fine + // because shared_ptr takes care of the underlying memory management + cbf_cache_item(const cbf_cache_item& b) = default; + cbf_cache_item& operator=(const cbf_cache_item& b) = default; + cbf_cache_item(cbf_cache_item&& src) = default; + cbf_cache_item& operator=(cbf_cache_item&& src) = default; + virtual ~cbf_cache_item() = default; // virtual destructor for base class + }; + +} // namespace solar #endif // __SOLAR_CBF_CACHE \ No newline at end of file diff --git a/cbf_pylon.cpp b/source/solar/cbf_pylon.cpp similarity index 98% rename from cbf_pylon.cpp rename to source/solar/cbf_pylon.cpp index 7cfc2fa..abb9b35 100644 --- a/cbf_pylon.cpp +++ b/source/solar/cbf_pylon.cpp @@ -1,158 +1,158 @@ -#include "cbf_pylon.h" - -namespace solar -{ - // see common.h for definition of publish_spec_t - // publish_spec_t(int on_count, int interval, int timeout) - const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages - const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests - - cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) - { - if (this != &src) { - cb_frame::operator=(std::move(src)); - src.clear(); - //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); - } - return *this; - } - void cbf_pylon::clear() - { - cb_frame::clear(); - } - - // float cbf_pylon::_get_battery_charge_voltage_limit() const - // { - // const auto& x = this->frame; - // return 0.1 * (float)((x[1] << 8) + x[0]); - // } - - // Function to build a message from the pylon canbus frame - std::string cbf_pylon::to_string() const - { - char buffer[80]; - switch (can_id) - { - case CB_BATTERY_LIMITS: - { - if(rtr) { - return "Request for BATTERY LIMITS info"; - } - if (this->frame.size() < 6) { - return "Invalid frame size for CB_BATTERY_LIMITS"; - } - const auto& x = this->frame; - float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V - float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A - float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A - snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); - return buffer; - } - break; - case CB_BATTERY_STATE: - { - if(rtr) { - return "Request for BATTERY STATE info"; - } - if (this->frame.size() < 4) { - return "Invalid frame size for CB_BATTERY_STATE"; - } - const auto& x = this->frame; - uint soc = static_cast((x[1] << 8) + x[0]); - uint soh = static_cast((x[3] << 8) + x[2]); - snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); - return buffer; - } - break; - case CB_BATTERY_STATUS: - { - if(rtr) { - return "Request for BATTERY STATUS info"; - } - if (this->frame.size() < 6) { - return "Invalid frame size for CB_BATTERY_STATUS"; - } - const auto& x = this->frame; - float system_voltage = 0.01 * static_cast((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system - float system_current = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current - float average_cell_temperature = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1°C - snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature); - return buffer; - } - break; - case CB_BATTERY_FAULT: - { - if(rtr) { - return "Request for BATTERY FAULT info"; - } - if (this->frame.size() < 8) { - return "Invalid frame size for CB_BATTERY_FAULT"; - } - const auto& x = this->frame; - uint8_t protection1 = x[0]; - uint8_t protection2 = x[1]; - uint8_t alarm1 = x[2]; - uint8_t alarm2 = x[3]; - uint8_t module_numbers = x[4]; - char ch5 = x[5]; - char ch6 = x[6]; - bool discharge_over_current = protection1 & 0x80; - bool cell_under_temperature = protection1 & 0x10; - bool cell_over_temperature = protection1 & 0x08; - bool cell_or_module_under_voltage = protection1 & 0x04; - bool cell_or_module_over_voltage = protection1 & 0x02; - bool system_error = protection2 & 0x8; - bool charge_over_current = protection2 & 0x01; - bool discharge_high_current = alarm1 & 0x80; - bool cell_low_temperature = alarm1 & 0x10; - bool cell_high_temperature = alarm1 & 0x08; - bool cell_or_module_low_voltage = alarm1 & 0x04; - bool cell_or_module_high_voltage = alarm1 & 0x02; - bool internal_communication_fail = alarm2 & 0x8; - bool charge_high_current = alarm2 & 0x01; - snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6); - return buffer; - } - break; - case CB_BATTERY_REQUEST_FLAGS: - { - if(rtr) { - return "Request for BATTERY REQUEST FLAGS info"; - } - if (this->frame.size() < 1) { - return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; - } - const auto& x = this->frame; - uint8_t request_flag = x[0]; - bool charge_enable = request_flag & 0x80; - bool discharge_enable = request_flag & 0x40; - bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it. - bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently. - bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1. - snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : ""); - return buffer; - } - break; - case CB_BATTERY_MANUFACTURER: - { - if(rtr) { - return "Request for BATTERY MANUFACTURER info"; - } - if (this->frame.size() < 8) { - return "Invalid frame size for CB_BATTERY_MANUFACTURER"; - } - const auto& x = this->frame; - // Manufacturer name is in the first 8 bytes, padded with spaces - // Convert to string and trim trailing spaces - std::string manufacturer(reinterpret_cast(x.data()), 8); - manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); - return "BATTERY OEM: " + manufacturer; - } - break; - } - if(rtr) { - return "Request for unknown pylon CAN ID"; - } - return "Unknown CAN ID"; - } +#include "cbf_pylon.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_pylon::publish_spec = publish_spec_t(3, 15, 30); // default publish spec for Pylontech battery messages + const publish_spec_t cbf_pylon::rtr_publish_spec = publish_spec_t(2, 10, 15); // for remote transmission requests + + cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_pylon::clear() + { + cb_frame::clear(); + } + + // float cbf_pylon::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + + // Function to build a message from the pylon canbus frame + std::string cbf_pylon::to_string() const + { + char buffer[80]; + switch (can_id) + { + case CB_BATTERY_LIMITS: + { + if(rtr) { + return "Request for BATTERY LIMITS info"; + } + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_LIMITS"; + } + const auto& x = this->frame; + float battery_charge_voltage_limit = 0.1 * ((x[1] << 8) + x[0]); // unit = 0.1V + float charge_current_limit = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A + float discharge_current_limit = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1A + snprintf(buffer, sizeof(buffer), "BATTERY MAX CHARGE: VMax= %.1fV IMaxChg= %.1fA IMaxDis= %.1fA", battery_charge_voltage_limit, charge_current_limit, discharge_current_limit); + return buffer; + } + break; + case CB_BATTERY_STATE: + { + if(rtr) { + return "Request for BATTERY STATE info"; + } + if (this->frame.size() < 4) { + return "Invalid frame size for CB_BATTERY_STATE"; + } + const auto& x = this->frame; + uint soc = static_cast((x[1] << 8) + x[0]); + uint soh = static_cast((x[3] << 8) + x[2]); + snprintf(buffer, sizeof(buffer), "BATTERY STATE: SOC= %d%% SOH= %d%%", soc, soh); + return buffer; + } + break; + case CB_BATTERY_STATUS: + { + if(rtr) { + return "Request for BATTERY STATUS info"; + } + if (this->frame.size() < 6) { + return "Invalid frame size for CB_BATTERY_STATUS"; + } + const auto& x = this->frame; + float system_voltage = 0.01 * static_cast((x[1] << 8) + x[0]); // unit = 0.01V Voltage of single module or average module voltage of system + float system_current = 0.1 * static_cast((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current + float average_cell_temperature = 0.1 * static_cast((x[5] << 8) + x[4]); // unit = 0.1°C + snprintf(buffer, sizeof(buffer), "BATTERY STATUS: VSYS= %.2fV ISYS= %.1fA TSYS= %.1f°C", system_voltage, system_current, average_cell_temperature); + return buffer; + } + break; + case CB_BATTERY_FAULT: + { + if(rtr) { + return "Request for BATTERY FAULT info"; + } + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_FAULT"; + } + const auto& x = this->frame; + uint8_t protection1 = x[0]; + uint8_t protection2 = x[1]; + uint8_t alarm1 = x[2]; + uint8_t alarm2 = x[3]; + uint8_t module_numbers = x[4]; + char ch5 = x[5]; + char ch6 = x[6]; + bool discharge_over_current = protection1 & 0x80; + bool cell_under_temperature = protection1 & 0x10; + bool cell_over_temperature = protection1 & 0x08; + bool cell_or_module_under_voltage = protection1 & 0x04; + bool cell_or_module_over_voltage = protection1 & 0x02; + bool system_error = protection2 & 0x8; + bool charge_over_current = protection2 & 0x01; + bool discharge_high_current = alarm1 & 0x80; + bool cell_low_temperature = alarm1 & 0x10; + bool cell_high_temperature = alarm1 & 0x08; + bool cell_or_module_low_voltage = alarm1 & 0x04; + bool cell_or_module_high_voltage = alarm1 & 0x02; + bool internal_communication_fail = alarm2 & 0x8; + bool charge_high_current = alarm2 & 0x01; + snprintf(buffer, sizeof(buffer), "BATTERY PROTECT: %s%s%s%s%s%s%s ALARM= %s%s%s%s%s%s%s MN=%d %c%c", discharge_over_current ? "DOC " : "", cell_under_temperature ? "CUT " : "", cell_over_temperature ? "COT " : "", cell_or_module_under_voltage ? "CMUV " : "", cell_or_module_over_voltage ? "CMOV" : "", system_error ? "SERR " : "", charge_over_current ? "COC ": "", discharge_high_current ? "DHC " : "", cell_low_temperature ? "CLT " : "", cell_high_temperature ? "CHT " : "", cell_or_module_low_voltage ? "CMLV " : "", cell_or_module_high_voltage ? "CMHV" : "", internal_communication_fail ? "ICF " : "", charge_high_current ? "CHC ": "", module_numbers, ch5, ch6); + return buffer; + } + break; + case CB_BATTERY_REQUEST_FLAGS: + { + if(rtr) { + return "Request for BATTERY REQUEST FLAGS info"; + } + if (this->frame.size() < 1) { + return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; + } + const auto& x = this->frame; + uint8_t request_flag = x[0]; + bool charge_enable = request_flag & 0x80; + bool discharge_enable = request_flag & 0x40; + bool request_force_charge1 = request_flag & 0x20; // use bit 5, the SOC range is: 15~19%. Bit 4 is NULL. Bit 5 is designed for inverter allows battery to shut down, and able to wake battery up to charge it. + bool request_force_charge2 = request_flag & 0x10; // Bit 5 the SOC range is 5~10%, Bit 4 the SOC range is 9~13%. Bit 4 is designed for inverter doesn`t want battery to shut down, able to charge battery before shut down to avoid low energy. We suggest inverter to use this bit, In this case, inverter itself should set a threshold of SOC: after force charge, only when battery SOC is higher than this threshold then inverter will allow discharge, to avoid force charge and discharge status change frequently. + bool request_full_charge = request_flag & 0x08; // Reason: when battery is not full charged for long time, the accumulative error of SOC calculation will be too high and may not able to be charged or discharged as expected capacity. Logic: if SOC never higher than 97% in 30 days, will set this flag to 1. And when the SOC is 97%, the flag will be 0. How to: we suggest inverter to charge the battery by grid when this flag is 1. + snprintf(buffer, sizeof(buffer), "BATTERY REQUEST: %s%s%s%s%s", charge_enable ? "CE " : "", discharge_enable ? "DE " : "", request_force_charge1 ? "RFORCECH1 " : "", request_force_charge2 ? "RFORCECH2 " : "", request_full_charge ? "RFULLCH" : ""); + return buffer; + } + break; + case CB_BATTERY_MANUFACTURER: + { + if(rtr) { + return "Request for BATTERY MANUFACTURER info"; + } + if (this->frame.size() < 8) { + return "Invalid frame size for CB_BATTERY_MANUFACTURER"; + } + const auto& x = this->frame; + // Manufacturer name is in the first 8 bytes, padded with spaces + // Convert to string and trim trailing spaces + std::string manufacturer(reinterpret_cast(x.data()), 8); + manufacturer.erase(std::find_if(manufacturer.rbegin(), manufacturer.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), manufacturer.end()); + return "BATTERY OEM: " + manufacturer; + } + break; + } + if(rtr) { + return "Request for unknown pylon CAN ID"; + } + return "Unknown CAN ID"; + } } // namespace solar \ No newline at end of file diff --git a/cbf_pylon.h b/source/solar/cbf_pylon.h similarity index 98% rename from cbf_pylon.h rename to source/solar/cbf_pylon.h index f2f76e4..5b6f0ed 100644 --- a/cbf_pylon.h +++ b/source/solar/cbf_pylon.h @@ -1,40 +1,40 @@ -#ifndef __SOLAR_CBF_PYLON // include GUARD -#define __SOLAR_CBF_PYLON -#include "esphome.h" -#include -#include "common.h" -#include "cb_frame.h" -using namespace esphome; - -namespace solar -{ - class cbf_pylon : virtual public cb_frame { - public: - // Pylontech publish spec - // This is used to determine when to publish the battery data from the Pylontech battery - static const publish_spec_t publish_spec; // default publish spec for Pylontech battery messages - static const publish_spec_t rtr_publish_spec; // for remote transmission requests - - // Pylontech battery CAN IDs - // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole - // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery - static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message - static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message - static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message - static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message - static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message - static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message - - //Property battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit }; - //Property battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit}; - - cbf_pylon() = default; - cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); - void clear(); - virtual std::string to_string() const override; - cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon - // using default (compiler auto generated) copy and move constructors and assignment operators - }; - -} // namespace solar +#ifndef __SOLAR_CBF_PYLON // include GUARD +#define __SOLAR_CBF_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + class cbf_pylon : virtual public cb_frame { + public: + // Pylontech publish spec + // This is used to determine when to publish the battery data from the Pylontech battery + static const publish_spec_t publish_spec; // default publish spec for Pylontech battery messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + + // Pylontech battery CAN IDs + // https://domosimple.eu/en/documentation/manuels/pylontech-can-bus-protocole + // VERIFIED IDs have been confirmed by capturing actual CAN bus traffic from a Pylontech battery + static const int CB_BATTERY_LIMITS = 0x351; // VERIFIED: Pylontech battery max charging/discharging values message + static const int CB_BATTERY_STATE = 0x355; // VERIFIED: Pylontech battery state message + static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status message + static const int CB_BATTERY_FAULT = 0x359; // VERIFIED: Pylontech battery fault message + static const int CB_BATTERY_REQUEST_FLAGS = 0x35C; // VERIFIED: Pylontech battery request flag message + static const int CB_BATTERY_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message + + //Property battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit}; + + cbf_pylon() = default; + cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); + void clear(); + virtual std::string to_string() const override; + cbf_pylon& operator=(cbf_pylon&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + }; + +} // namespace solar #endif // __SOLAR_CBF_PYLON \ No newline at end of file diff --git a/cbf_sthome.cpp b/source/solar/cbf_sthome.cpp similarity index 98% rename from cbf_sthome.cpp rename to source/solar/cbf_sthome.cpp index 878f9f6..2d0e5dd 100644 --- a/cbf_sthome.cpp +++ b/source/solar/cbf_sthome.cpp @@ -1,342 +1,342 @@ -#include "cbf_sthome.h" - -namespace solar -{ - // see common.h for definition of publish_spec_t - // publish_spec_t(int on_count, int interval, int timeout) - const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec - const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests - - const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators - - //cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) - // : cb_frame(msg_id, can_id, frame, rtr) - // { - // } - cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src) - { - if (this != &src) { - cb_frame::operator=(std::move(src)); - src.clear(); - //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); - } - return *this; - } - void cbf_sthome::clear() - { - cb_frame::clear(); - } - - bool cbf_sthome::verify_heartbeat() const - { - const auto& x = this->frame; - std::string hb(reinterpret_cast(x.data()), x.size()); - hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end()); - return hb == heartbeat; - } - // float cbf_sthome::_get_battery_charge_voltage_limit() const - // { - // const auto& x = this->frame; - // return 0.1 * (float)((x[1] << 8) + x[0]); - // } - - // Function to build a message from the pylon canbus frame - std::string cbf_sthome::to_string() const - { - char buffer[128]; - const auto& x = this->frame; - switch (can_id) - { - case CB_CANBUS_ID01: - return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : ""; - case CB_CANBUS_ID02: - return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : ""; - case CB_CANBUS_ID03: - return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : ""; - case CB_CANBUS_ID04: - return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : ""; - case CB_CANBUS_ID05: - return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : ""; - case CB_CANBUS_ID06: - return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : ""; - case CB_CANBUS_ID07: - return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : ""; - case CB_CANBUS_ID08: - return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : ""; - case CB_CANBUS_ID09: - return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : ""; - case CB_CANBUS_ID10: - return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : ""; - case CB_GEYSER_TEMPERATURE_TOP: - { - if(rtr) { - return "Request for geyser top temp"; - } - double temp_top = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top); - return buffer; - } - case CB_GEYSER_TEMPERATURE_BOTTOM: - { - if(rtr) { - return "Request for geyser bottom temp"; - } - double temp_bot = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot); - return buffer; - } - case CB_GEYSER_TEMPERATURE_AMBIENT: - { - if(rtr) { - return "Request for geyser ambient temp"; - } - double temp_amb = get_double(x[0], x[1], 256, true); - snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb); - return buffer; - } - case CB_GEYSER_HEATING: - { - if(rtr) { - return "Request for geyser heating"; - } - double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W - double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W - double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W - unsigned int est_heating_time = (unsigned int) static_cast(256 * x[7] + x[6]); // 0 to 65536 seconds - snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time); - return buffer; - } - case CB_GEYSER_ACTIVE_SCHEDULE: - { - if(rtr) { - return "Request for geyser schedule"; - } - double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C - int active_heating_time = static_cast(256 * x[3] + x[2]); // -32768 to 32768s - double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s - int active_schedule_day = static_cast(x[6]); - snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day); - return buffer; - } - case CB_POWER_MAINS: - { - if(rtr) { - return "Request for power mains"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_INVERTER: - { - if(rtr) { - return "Request for power inverter"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_PLUGS: - { - if(rtr) { - return "Request for power plugs"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_LIGHTS: - { - if(rtr) { - return "Request for power lights"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_GEYSER: - { - if(rtr) { - return "Request for power geyser"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_POOL: - { - if(rtr) { - return "Request for power pool"; - } - double power = get_double(x[0], x[1], 2048); // 0 to 32kW - double voltage = get_double(x[2], x[3], 128); // 0 to 512V - double current = get_double(x[4], x[5], 512); // 0 to 128A - snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power); - return buffer; - } - case CB_POWER_GENERATED: - { - if(rtr) { - return "Request for power generated"; - } - double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW - double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW - snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss); - return buffer; - } - case CB_ENERGY_MAINS: - { - if(rtr) { - return "Request for energy mains"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_GEYSER: - { - if(rtr) { - return "Request for energy geyser"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_POOL: - { - if(rtr) { - return "Request for energy pool"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_PLUGS: - { - if(rtr) { - return "Request for energy plugs"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_LIGHTS: - { - if(rtr) { - return "Request for energy lights"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_HOUSE: - { - if(rtr) { - return "Request for energy house"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_GENERATED: - { - if(rtr) { - return "Request for energy generated"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_ENERGY_LOSS: - { - if(rtr) { - return "Request for energy loss"; - } - double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh - double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh - double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh - double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh - snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); - return buffer; - } - case CB_CONTROLLER_STATES: - { - if(rtr) { - return "Request for controller states"; - } - uint8_t alarms = x[0]; - uint8_t states = x[1]; - uint8_t modes = x[2]; - bool inverter_1_battery_low = alarms & 0x80; - bool inverter_2_battery_low = alarms & 0x10; - bool inverter_overload = alarms & 0x08; - bool geyser_heating = states & 0x80; - bool geyser_energised = states & 0x40; - bool mains_supply_present = states & 0x20; - bool battery_charging = states & 0x08; - bool vacation_mode = modes & 0x80; // nobody at home - std::string mode = ""; - int geysermode = (modes & 0x70) >> 4; - switch (geysermode) { - case GM_SUNDAY: - mode = "SUN"; - break; - case GM_WORKDAY: - mode = "WRK"; - break; - case GM_SATURDAY: - mode = "SAT"; - break; - case GM_PUBLIC_HOLIDAY: - mode = "PUB"; - break; - case GM_SCHOOL_HOLIDAY: - mode = "SCH"; - break; - default: - mode = "UNK"; - break; - } - snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : ""); - return buffer; - } - - } - if(rtr) { - return "Request for unknown sthome CAN ID"; - } - return "Unknown CAN ID"; - } - +#include "cbf_sthome.h" + +namespace solar +{ + // see common.h for definition of publish_spec_t + // publish_spec_t(int on_count, int interval, int timeout) + const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec + const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests + + const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators + + //cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr) + // : cb_frame(msg_id, can_id, frame, rtr) + // { + // } + cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src) + { + if (this != &src) { + cb_frame::operator=(std::move(src)); + src.clear(); + //ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str()); + } + return *this; + } + void cbf_sthome::clear() + { + cb_frame::clear(); + } + + bool cbf_sthome::verify_heartbeat() const + { + const auto& x = this->frame; + std::string hb(reinterpret_cast(x.data()), x.size()); + hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end()); + return hb == heartbeat; + } + // float cbf_sthome::_get_battery_charge_voltage_limit() const + // { + // const auto& x = this->frame; + // return 0.1 * (float)((x[1] << 8) + x[0]); + // } + + // Function to build a message from the pylon canbus frame + std::string cbf_sthome::to_string() const + { + char buffer[128]; + const auto& x = this->frame; + switch (can_id) + { + case CB_CANBUS_ID01: + return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : ""; + case CB_CANBUS_ID02: + return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : ""; + case CB_CANBUS_ID03: + return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : ""; + case CB_CANBUS_ID04: + return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : ""; + case CB_CANBUS_ID05: + return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : ""; + case CB_CANBUS_ID06: + return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : ""; + case CB_CANBUS_ID07: + return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : ""; + case CB_CANBUS_ID08: + return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : ""; + case CB_CANBUS_ID09: + return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : ""; + case CB_CANBUS_ID10: + return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : ""; + case CB_GEYSER_TEMPERATURE_TOP: + { + if(rtr) { + return "Request for geyser top temp"; + } + double temp_top = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top); + return buffer; + } + case CB_GEYSER_TEMPERATURE_BOTTOM: + { + if(rtr) { + return "Request for geyser bottom temp"; + } + double temp_bot = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot); + return buffer; + } + case CB_GEYSER_TEMPERATURE_AMBIENT: + { + if(rtr) { + return "Request for geyser ambient temp"; + } + double temp_amb = get_double(x[0], x[1], 256, true); + snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb); + return buffer; + } + case CB_GEYSER_HEATING: + { + if(rtr) { + return "Request for geyser heating"; + } + double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W + double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W + double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W + unsigned int est_heating_time = (unsigned int) static_cast(256 * x[7] + x[6]); // 0 to 65536 seconds + snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time); + return buffer; + } + case CB_GEYSER_ACTIVE_SCHEDULE: + { + if(rtr) { + return "Request for geyser schedule"; + } + double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C + int active_heating_time = static_cast(256 * x[3] + x[2]); // -32768 to 32768s + double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s + int active_schedule_day = static_cast(x[6]); + snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day); + return buffer; + } + case CB_POWER_MAINS: + { + if(rtr) { + return "Request for power mains"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_INVERTER: + { + if(rtr) { + return "Request for power inverter"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_PLUGS: + { + if(rtr) { + return "Request for power plugs"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_LIGHTS: + { + if(rtr) { + return "Request for power lights"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GEYSER: + { + if(rtr) { + return "Request for power geyser"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_POOL: + { + if(rtr) { + return "Request for power pool"; + } + double power = get_double(x[0], x[1], 2048); // 0 to 32kW + double voltage = get_double(x[2], x[3], 128); // 0 to 512V + double current = get_double(x[4], x[5], 512); // 0 to 128A + snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power); + return buffer; + } + case CB_POWER_GENERATED: + { + if(rtr) { + return "Request for power generated"; + } + double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW + double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW + snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss); + return buffer; + } + case CB_ENERGY_MAINS: + { + if(rtr) { + return "Request for energy mains"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GEYSER: + { + if(rtr) { + return "Request for energy geyser"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_POOL: + { + if(rtr) { + return "Request for energy pool"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_PLUGS: + { + if(rtr) { + return "Request for energy plugs"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LIGHTS: + { + if(rtr) { + return "Request for energy lights"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_HOUSE: + { + if(rtr) { + return "Request for energy house"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_GENERATED: + { + if(rtr) { + return "Request for energy generated"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_ENERGY_LOSS: + { + if(rtr) { + return "Request for energy loss"; + } + double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh + double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh + double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh + double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh + snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime); + return buffer; + } + case CB_CONTROLLER_STATES: + { + if(rtr) { + return "Request for controller states"; + } + uint8_t alarms = x[0]; + uint8_t states = x[1]; + uint8_t modes = x[2]; + bool inverter_1_battery_low = alarms & 0x80; + bool inverter_2_battery_low = alarms & 0x10; + bool inverter_overload = alarms & 0x08; + bool geyser_heating = states & 0x80; + bool geyser_energised = states & 0x40; + bool mains_supply_present = states & 0x20; + bool battery_charging = states & 0x08; + bool vacation_mode = modes & 0x80; // nobody at home + std::string mode = ""; + int geysermode = (modes & 0x70) >> 4; + switch (geysermode) { + case GM_SUNDAY: + mode = "SUN"; + break; + case GM_WORKDAY: + mode = "WRK"; + break; + case GM_SATURDAY: + mode = "SAT"; + break; + case GM_PUBLIC_HOLIDAY: + mode = "PUB"; + break; + case GM_SCHOOL_HOLIDAY: + mode = "SCH"; + break; + default: + mode = "UNK"; + break; + } + snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : ""); + return buffer; + } + + } + if(rtr) { + return "Request for unknown sthome CAN ID"; + } + return "Unknown CAN ID"; + } + } // namespace solar \ No newline at end of file diff --git a/cbf_sthome.h b/source/solar/cbf_sthome.h similarity index 97% rename from cbf_sthome.h rename to source/solar/cbf_sthome.h index 8f5076a..2016f87 100644 --- a/cbf_sthome.h +++ b/source/solar/cbf_sthome.h @@ -1,78 +1,78 @@ -#ifndef __SOLAR_CBF_STHOME_H // include GUARD -#define __SOLAR_CBF_STHOME_H -#include "esphome.h" -#include -#include "common.h" -#include "cb_frame.h" -using namespace esphome; - -namespace solar -{ - - class cbf_sthome : virtual public cb_frame { - private: - // float _get_battery_charge_voltage_limit() const; - - public: - // STHOME publish spec - // This is used to determine when to publish the sthome data - static const publish_spec_t publish_spec; // default publish spec for sthome messages - static const publish_spec_t rtr_publish_spec; // for remote transmission requests - - enum geyser_modes : int { - GM_SUNDAY, - GM_WORKDAY, - GM_SATURDAY, - GM_PUBLIC_HOLIDAY, - GM_SCHOOL_HOLIDAY, - }; - enum canbus_ids : int { - CB_CANBUS_ID01 = 0x501, - CB_CANBUS_ID02, - CB_CANBUS_ID03, - CB_CANBUS_ID04, - CB_CANBUS_ID05, - CB_CANBUS_ID06, - CB_CANBUS_ID07, - CB_CANBUS_ID08, - CB_CANBUS_ID09, - CB_CANBUS_ID10, - CB_POWER_MAINS = 0x401, - CB_POWER_INVERTER, - CB_POWER_PLUGS, - CB_POWER_LIGHTS, - CB_POWER_GEYSER, - CB_POWER_POOL, - CB_POWER_GENERATED, - CB_ENERGY_MAINS, - CB_ENERGY_GEYSER, - CB_ENERGY_POOL, - CB_ENERGY_PLUGS, - CB_ENERGY_LIGHTS, - CB_ENERGY_HOUSE, - CB_ENERGY_GENERATED, - CB_ENERGY_LOSS, - CB_CONTROLLER_STATES, - CB_GEYSER_TEMPERATURE_TOP, - CB_GEYSER_TEMPERATURE_BOTTOM, - CB_GEYSER_TEMPERATURE_AMBIENT, - CB_GEYSER_HEATING, - CB_GEYSER_ACTIVE_SCHEDULE, - }; - - //Property battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit }; - //Property battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit}; - static const std::string heartbeat; - - cbf_sthome() = default; - cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); - void clear(); - virtual std::string to_string() const override; - cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon - // using default (compiler auto generated) copy and move constructors and assignment operators - virtual ~cbf_sthome() = default; - bool verify_heartbeat() const; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STHOME_H // include GUARD +#define __SOLAR_CBF_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cb_frame.h" +using namespace esphome; + +namespace solar +{ + + class cbf_sthome : virtual public cb_frame { + private: + // float _get_battery_charge_voltage_limit() const; + + public: + // STHOME publish spec + // This is used to determine when to publish the sthome data + static const publish_spec_t publish_spec; // default publish spec for sthome messages + static const publish_spec_t rtr_publish_spec; // for remote transmission requests + + enum geyser_modes : int { + GM_SUNDAY, + GM_WORKDAY, + GM_SATURDAY, + GM_PUBLIC_HOLIDAY, + GM_SCHOOL_HOLIDAY, + }; + enum canbus_ids : int { + CB_CANBUS_ID01 = 0x501, + CB_CANBUS_ID02, + CB_CANBUS_ID03, + CB_CANBUS_ID04, + CB_CANBUS_ID05, + CB_CANBUS_ID06, + CB_CANBUS_ID07, + CB_CANBUS_ID08, + CB_CANBUS_ID09, + CB_CANBUS_ID10, + CB_POWER_MAINS = 0x401, + CB_POWER_INVERTER, + CB_POWER_PLUGS, + CB_POWER_LIGHTS, + CB_POWER_GEYSER, + CB_POWER_POOL, + CB_POWER_GENERATED, + CB_ENERGY_MAINS, + CB_ENERGY_GEYSER, + CB_ENERGY_POOL, + CB_ENERGY_PLUGS, + CB_ENERGY_LIGHTS, + CB_ENERGY_HOUSE, + CB_ENERGY_GENERATED, + CB_ENERGY_LOSS, + CB_CONTROLLER_STATES, + CB_GEYSER_TEMPERATURE_TOP, + CB_GEYSER_TEMPERATURE_BOTTOM, + CB_GEYSER_TEMPERATURE_AMBIENT, + CB_GEYSER_HEATING, + CB_GEYSER_ACTIVE_SCHEDULE, + }; + + //Property battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit }; + //Property battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit}; + static const std::string heartbeat; + + cbf_sthome() = default; + cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr); + void clear(); + virtual std::string to_string() const override; + cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon + // using default (compiler auto generated) copy and move constructors and assignment operators + virtual ~cbf_sthome() = default; + bool verify_heartbeat() const; + }; + +} // namespace solar #endif // __SOLAR_CBF_STHOME_H \ No newline at end of file diff --git a/cbf_store.cpp b/source/solar/cbf_store.cpp similarity index 98% rename from cbf_store.cpp rename to source/solar/cbf_store.cpp index 9f6b327..5d98ac0 100644 --- a/cbf_store.cpp +++ b/source/solar/cbf_store.cpp @@ -1,235 +1,235 @@ -#include "source/solar/cbf_store.h" - -namespace solar -{ - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id) - : cb_frame(msg_id, can_id) - { - this->id = id; // used for debugging, can be omitted - this->count = 0; - this->first_timestamp = 0; - this->last_timestamp = 0; - // ESP_LOGI(tag("store CTOR1").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, time_t first_timestamp, time_t last_timestamp) - : cb_frame(msg_id, can_id) - { - this->id = id; - this->count = 0; - this->first_timestamp = first_timestamp; - this->last_timestamp = last_timestamp; - // ESP_LOGI(tag("store CTOR2").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& frame, bool rtr, time_t first_timestamp, time_t last_timestamp) - : cb_frame(msg_id, can_id, frame, rtr) - { - this->id = id; - this->count = 0; - this->first_timestamp = first_timestamp; - this->last_timestamp = last_timestamp; - // ESP_LOGI(tag("store CTOR3").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); - } - cbf_store::cbf_store(const cbf_store& b, int id) - : cb_frame(b) - { - this->id = id; - this->count = b.count; - this->first_timestamp = b.first_timestamp; - this->last_timestamp = b.last_timestamp; - // ESP_LOGI(tag("store CCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); - } - cbf_store::cbf_store(const cbf_store&& b, int id) - : cb_frame(b) - { - this->id = id; - this->count = b.count; - this->first_timestamp = b.first_timestamp; - this->last_timestamp = b.last_timestamp; - // ESP_LOGI(tag("store MCCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); - } - std::shared_ptr cbf_store::clone() const - { - return clone_impl(); - } - std::shared_ptr cbf_store::clone_impl() const - { - ESP_LOGW(tag("store CLONE").c_str(), "%-20s", "Cloning store"); // this should happen as all cloning should be done by derived classes - return std::make_shared(*this); - } - bool cbf_store::is_publish_expired(time_t currenttime, int update_interval) const - { - return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= update_interval); - } - bool cbf_store::is_validity_expired(time_t currenttime, int timeout_interval) const - { - return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); - } - cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, const int *comparecolumns) - { - ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing the publish_specs as parameters - return update(newitem, publish_spec_t{1, 5, 10}, publish_spec_t{1, 5, 10}, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation - } - cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns) - { - if(!is_valid()) { - return cbf_updateresult::stu_NONE; // cannot update an invalid store - } - auto pbspec = rtr ? rtr_publish_spec : publish_spec; - time_t newtime = newitem.last_timestamp; - bool publish_expired = this->is_publish_expired(newtime, pbspec.interval); - bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout); - bool isduplicate = this->compare(newitem, comparecolumns) == 0; - time_t reset_timer = this->first_timestamp + pbspec.interval - newtime; - int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; - auto result = isduplicate ? cbf_updateresult::stu_DUPLICATE : cbf_updateresult::stu_NONE; - // auto ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); - // auto ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); - // auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); - // if(rtr) - // ESP_LOGI(this->tag("store UPDATE").c_str(), "A: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec// .interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(),// result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); - if(validity_expired) { - *this = newitem; - if(!isduplicate) { - this->count = 1; - publish_expired = false; - } - } - if(isduplicate || publish_expired) { - this->count++; - if(this->count == pbspec.on_count) - this->setpublish(); // must be reset by caller after publish - if(this->getpublish()) - result = static_cast(result | cbf_updateresult::stu_PUBLISH); - this->last_timestamp = newtime; - if(reset_timer <= 0) { - this->first_timestamp = newtime; - this->count = 1; - } - } - // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); - // ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); - // if(rtr) - // ESP_LOGI(this->tag("store UPDATE").c_str(), "B: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec.interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(), result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); - - return result; - } - int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const - { - int result = 0; - bool stopcompare = false; - bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; - int ncmpcols = 0; - while (comparecolumns[ncmpcols] != 0) ncmpcols++; - if (isdefaultcompare) { - for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { - bool validnextcol = i < sisortEND - 1; - result = compare(b, i, &stopcompare, validnextcol); - } - } - else { - for (int i = 0; i < sisortEND && !stopcompare && result == 0 && comparecolumns[i] != 0; i++) { - int cmpcol = comparecolumns[i]; - bool validnextcol = cmpcol != 0 && comparecolumns[i + 1] != 0; - result = compare(b, cmpcol, &stopcompare, validnextcol); - } - } - return result; - } - int cbf_store::compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const - { - int result = 0; - int reverseorderflag = cmpflag & sisortreverse; - int casesensitiveflag = cmpflag & sisortcase; - int nulliswildcardflag = cmpflag & sisortwild; - int stopcompareflag = cmpflag & sistopcomparecol; - cmpflag = cmpflag & ~FLAGBITS; - if (cmpflag == 0) - return result; - bool casesensitive = casesensitiveflag != 0; - bool sortwild = nulliswildcardflag != 0; - bool stopcompare = stopcompareflag != 0; - *stopcomparesignal = false; - switch (cmpflag) { - case sisortNULL: - return 0; - case sisortcanid: - { - bool bothvalid = this->can_id != 0 && b.can_id != 0; - if (bothvalid) { - result = num_compare(this->can_id, b.can_id); - *stopcomparesignal = stopcompare; // set flag only if both items are valid - } else { - if (sortwild) - result = 0; - else { - bool bothinvalid = !(this->can_id || b.can_id); - if (validnextcol && bothinvalid) - result = 0; //if validnextcol then return 0 if both IDs are invalid - result = this->can_id ? 1 : -1; - } - } - break; - } - case sisortmsgid: - result = num_compare(this->msg_id, b.msg_id); - break; - case sisortcount: - { - result = num_compare(this->count, b.count); - break; - } - case sisortfirst_timestamp: - { - auto a_ts = this->first_timestamp; - auto b_ts = b.first_timestamp; - result = num_compare(a_ts, b_ts); - break; - } - case sisortlast_timestamp: - { - auto a_ts = this->last_timestamp; - auto b_ts = b.last_timestamp; - result = num_compare(a_ts, b_ts); - break; - } - case sisortrtr: - result = bool_compare(this->rtr, b.rtr); - break; - case sisortframe: - { - result = this->compare_frame(b.frame); - break; - } - default: - result = 0; - // ESP_LOGE("cbf_store::compare", "Unknown compare column %d", cmpflag); - *stopcomparesignal = true; // stop compare as we don't know how to handle - // should never reach here as all cases must be dealt with - break; - } - if (reverseorderflag != 0) - result = -result; - return result; - } - std::string cbf_store::tag(const std::string& prefix, int prefixlen) const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - char tag[48]; - if(prefix.length() == 0) - snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " "); - else - snprintf(tag, sizeof(tag), "%-*s #%04d s%d 0x%03X %s", prefixlen, prefix.c_str(), msg_id, id, can_id, getpublish() ? "P" : " "); - return std::string(tag); - } - std::string cbf_store::to_string() const - { - // Be extra careful with the placement of printf format specifiers and their corresponding arguments. - // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. - char buffer[80]; - auto ftstime = is_valid() ? ESPTime::from_epoch_local(first_timestamp).strftime("%H:%M:%S") : "N/A"; - auto ltstime = is_valid() ? ESPTime::from_epoch_local(last_timestamp).strftime("%H:%M:%S") : "N/A"; - snprintf(buffer, sizeof(buffer), " Fts: %s Lts: %s Count: %2d %s", ftstime.c_str(), ltstime.c_str(), count, cb_frame::to_string().c_str()); - return std::string(buffer); - } -} // namespace solar +#include "source/solar/cbf_store.h" + +namespace solar +{ + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id) + : cb_frame(msg_id, can_id) + { + this->id = id; // used for debugging, can be omitted + this->count = 0; + this->first_timestamp = 0; + this->last_timestamp = 0; + // ESP_LOGI(tag("store CTOR1").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR2").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(int msg_id, uint32_t can_id, int id, const byte_vector& frame, bool rtr, time_t first_timestamp, time_t last_timestamp) + : cb_frame(msg_id, can_id, frame, rtr) + { + this->id = id; + this->count = 0; + this->first_timestamp = first_timestamp; + this->last_timestamp = last_timestamp; + // ESP_LOGI(tag("store CTOR3").c_str(), "%-20s %s", "Created store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store CCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + cbf_store::cbf_store(const cbf_store&& b, int id) + : cb_frame(b) + { + this->id = id; + this->count = b.count; + this->first_timestamp = b.first_timestamp; + this->last_timestamp = b.last_timestamp; + // ESP_LOGI(tag("store MCCTOR2").c_str(), "%-20s %s", "Copied store", this->to_string().c_str()); + } + std::shared_ptr cbf_store::clone() const + { + return clone_impl(); + } + std::shared_ptr cbf_store::clone_impl() const + { + ESP_LOGW(tag("store CLONE").c_str(), "%-20s", "Cloning store"); // this should happen as all cloning should be done by derived classes + return std::make_shared(*this); + } + bool cbf_store::is_publish_expired(time_t currenttime, int update_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= update_interval); + } + bool cbf_store::is_validity_expired(time_t currenttime, int timeout_interval) const + { + return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); + } + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, const int *comparecolumns) + { + ESP_LOGW(this->tag("store UPDATE").c_str(), "%-20s %s", "Default update call", this->to_string().c_str()); // this should happen as all updating should be called from derived classes, i.e. providing the publish_specs as parameters + return update(newitem, publish_spec_t{1, 5, 10}, publish_spec_t{1, 5, 10}, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation + } + cbf_store::cbf_updateresult cbf_store::update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, const int *comparecolumns) + { + if(!is_valid()) { + return cbf_updateresult::stu_NONE; // cannot update an invalid store + } + auto pbspec = rtr ? rtr_publish_spec : publish_spec; + time_t newtime = newitem.last_timestamp; + bool publish_expired = this->is_publish_expired(newtime, pbspec.interval); + bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout); + bool isduplicate = this->compare(newitem, comparecolumns) == 0; + time_t reset_timer = this->first_timestamp + pbspec.interval - newtime; + int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; + auto result = isduplicate ? cbf_updateresult::stu_DUPLICATE : cbf_updateresult::stu_NONE; + // auto ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // auto ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "A: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec// .interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(),// result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); + if(validity_expired) { + *this = newitem; + if(!isduplicate) { + this->count = 1; + publish_expired = false; + } + } + if(isduplicate || publish_expired) { + this->count++; + if(this->count == pbspec.on_count) + this->setpublish(); // must be reset by caller after publish + if(this->getpublish()) + result = static_cast(result | cbf_updateresult::stu_PUBLISH); + this->last_timestamp = newtime; + if(reset_timer <= 0) { + this->first_timestamp = newtime; + this->count = 1; + } + } + // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S"); + // ltstime = ESPTime::from_epoch_local(this->last_timestamp).strftime("%H:%M:%S"); + // if(rtr) + // ESP_LOGI(this->tag("store UPDATE").c_str(), "B: %s PBS %d,%d,%d PE:%s VE:%s DUP:%s FTS %s LTS %s NTS %s RES[D%s P%s]", rtr ? "RTR" : "NML", pbspec.on_count, pbspec.interval, pbspec.timeout, publish_expired ? "Y" : "N", validity_expired ? "Y" : "N", isduplicate ? "Y" : "N", ftstime.c_str(), ltstime.c_str(), ntstime.c_str(), result & cbf_updateresult::stu_DUPLICATE ? "Y" : "N", result & cbf_updateresult::stu_PUBLISH ? "Y" : "N"); + + return result; + } + int cbf_store::compare(const cbf_store& b, const int *comparecolumns) const + { + int result = 0; + bool stopcompare = false; + bool isdefaultcompare = comparecolumns == nullptr || *comparecolumns == 0; + int ncmpcols = 0; + while (comparecolumns[ncmpcols] != 0) ncmpcols++; + if (isdefaultcompare) { + for (int i = sisortNULL + 1; i < sisortEND && !stopcompare && result == 0; i++) { + bool validnextcol = i < sisortEND - 1; + result = compare(b, i, &stopcompare, validnextcol); + } + } + else { + for (int i = 0; i < sisortEND && !stopcompare && result == 0 && comparecolumns[i] != 0; i++) { + int cmpcol = comparecolumns[i]; + bool validnextcol = cmpcol != 0 && comparecolumns[i + 1] != 0; + result = compare(b, cmpcol, &stopcompare, validnextcol); + } + } + return result; + } + int cbf_store::compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const + { + int result = 0; + int reverseorderflag = cmpflag & sisortreverse; + int casesensitiveflag = cmpflag & sisortcase; + int nulliswildcardflag = cmpflag & sisortwild; + int stopcompareflag = cmpflag & sistopcomparecol; + cmpflag = cmpflag & ~FLAGBITS; + if (cmpflag == 0) + return result; + bool casesensitive = casesensitiveflag != 0; + bool sortwild = nulliswildcardflag != 0; + bool stopcompare = stopcompareflag != 0; + *stopcomparesignal = false; + switch (cmpflag) { + case sisortNULL: + return 0; + case sisortcanid: + { + bool bothvalid = this->can_id != 0 && b.can_id != 0; + if (bothvalid) { + result = num_compare(this->can_id, b.can_id); + *stopcomparesignal = stopcompare; // set flag only if both items are valid + } else { + if (sortwild) + result = 0; + else { + bool bothinvalid = !(this->can_id || b.can_id); + if (validnextcol && bothinvalid) + result = 0; //if validnextcol then return 0 if both IDs are invalid + result = this->can_id ? 1 : -1; + } + } + break; + } + case sisortmsgid: + result = num_compare(this->msg_id, b.msg_id); + break; + case sisortcount: + { + result = num_compare(this->count, b.count); + break; + } + case sisortfirst_timestamp: + { + auto a_ts = this->first_timestamp; + auto b_ts = b.first_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortlast_timestamp: + { + auto a_ts = this->last_timestamp; + auto b_ts = b.last_timestamp; + result = num_compare(a_ts, b_ts); + break; + } + case sisortrtr: + result = bool_compare(this->rtr, b.rtr); + break; + case sisortframe: + { + result = this->compare_frame(b.frame); + break; + } + default: + result = 0; + // ESP_LOGE("cbf_store::compare", "Unknown compare column %d", cmpflag); + *stopcomparesignal = true; // stop compare as we don't know how to handle + // should never reach here as all cases must be dealt with + break; + } + if (reverseorderflag != 0) + result = -result; + return result; + } + std::string cbf_store::tag(const std::string& prefix, int prefixlen) const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char tag[48]; + if(prefix.length() == 0) + snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " "); + else + snprintf(tag, sizeof(tag), "%-*s #%04d s%d 0x%03X %s", prefixlen, prefix.c_str(), msg_id, id, can_id, getpublish() ? "P" : " "); + return std::string(tag); + } + std::string cbf_store::to_string() const + { + // Be extra careful with the placement of printf format specifiers and their corresponding arguments. + // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. + char buffer[80]; + auto ftstime = is_valid() ? ESPTime::from_epoch_local(first_timestamp).strftime("%H:%M:%S") : "N/A"; + auto ltstime = is_valid() ? ESPTime::from_epoch_local(last_timestamp).strftime("%H:%M:%S") : "N/A"; + snprintf(buffer, sizeof(buffer), " Fts: %s Lts: %s Count: %2d %s", ftstime.c_str(), ltstime.c_str(), count, cb_frame::to_string().c_str()); + return std::string(buffer); + } +} // namespace solar diff --git a/cbf_store.h b/source/solar/cbf_store.h similarity index 100% rename from cbf_store.h rename to source/solar/cbf_store.h diff --git a/cbf_store_pylon.cpp b/source/solar/cbf_store_pylon.cpp similarity index 97% rename from cbf_store_pylon.cpp rename to source/solar/cbf_store_pylon.cpp index fd7636d..a32a9db 100644 --- a/cbf_store_pylon.cpp +++ b/source/solar/cbf_store_pylon.cpp @@ -1,71 +1,71 @@ -#include "cbf_store_pylon.h" - -namespace solar -{ - cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) - : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_pylon(msg_id, can_id, frame, rtr) - { - this->id = 0; - this->count = 0; - // ESP_LOGI(tag("store_pylon CTOR1").c_str(), "%-20s %s", "Created store_pylon", this->to_string().c_str()); - } - cbf_store_pylon::cbf_store_pylon(const cbf_store_pylon& b) - : cb_frame(b), cbf_store(b) // call base class copy constructor - { - // ESP_LOGI(tag("store_pylon CCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); - } - cbf_store_pylon& cbf_store_pylon::operator=(const cbf_store_pylon& b) - { - if (this != &b) { - cbf_store_pylon tmp(b); - swap(tmp); - // ESP_LOGI(tag("store_pylon AOPP").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); - } - return *this; - } - cbf_store_pylon::cbf_store_pylon(cbf_store_pylon&& src) - : cb_frame(src), cbf_store(src) - { - src.clear(); - // ESP_LOGI(tag("store_pylon MCCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); - } - cbf_store_pylon& cbf_store_pylon::operator=(cbf_store_pylon&& src) - { - if (this != &src) { - cbf_store::operator=(std::move(src)); - cb_frame::operator=(std::move(src)); - src.clear(); - // ESP_LOGI(tag("store_pylon MOASS").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); - } - return *this; - } - std::shared_ptr cbf_store_pylon::clone() const - { - return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon - } - std::shared_ptr cbf_store_pylon::clone_impl() const - { - //ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon"); - return std::make_shared(*this); - } - void cbf_store_pylon::clear() - { - cb_frame::clear(); - cbf_store::clear(); - } - void cbf_store_pylon::swap(cbf_store_pylon &s) - { - cb_frame::swap(s); - cbf_store::swap(s); - } - cbf_store::cbf_updateresult cbf_store_pylon::update(const cbf_store& newitem, const int *comparecolumns) - { - //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); - return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); - } - std::string cbf_store_pylon::to_string() const - { - return cb_frame::to_string() + " " + cbf_pylon::to_string(); - } - +#include "cbf_store_pylon.h" + +namespace solar +{ + cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_pylon(msg_id, can_id, frame, rtr) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_pylon CTOR1").c_str(), "%-20s %s", "Created store_pylon", this->to_string().c_str()); + } + cbf_store_pylon::cbf_store_pylon(const cbf_store_pylon& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_pylon CCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(const cbf_store_pylon& b) + { + if (this != &b) { + cbf_store_pylon tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_pylon AOPP").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + cbf_store_pylon::cbf_store_pylon(cbf_store_pylon&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_pylon MCCTOR").c_str(), "%-20s %s", "Copied store_pylon", this->to_string().c_str()); + } + cbf_store_pylon& cbf_store_pylon::operator=(cbf_store_pylon&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_pylon MOASS").c_str(), "%-20s %s", "Assigned store_pylon", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_pylon::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon + } + std::shared_ptr cbf_store_pylon::clone_impl() const + { + //ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon"); + return std::make_shared(*this); + } + void cbf_store_pylon::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_pylon::swap(cbf_store_pylon &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + cbf_store::cbf_updateresult cbf_store_pylon::update(const cbf_store& newitem, const int *comparecolumns) + { + //ESP_LOGI(tag("store_pylon UPDATE").c_str(), "%-20s %s", "Updating store_pylon", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); + } + std::string cbf_store_pylon::to_string() const + { + return cb_frame::to_string() + " " + cbf_pylon::to_string(); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_store_pylon.h b/source/solar/cbf_store_pylon.h similarity index 97% rename from cbf_store_pylon.h rename to source/solar/cbf_store_pylon.h index 85739da..3a854f0 100644 --- a/cbf_store_pylon.h +++ b/source/solar/cbf_store_pylon.h @@ -1,34 +1,34 @@ -#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD -#define __SOLAR_CBF_STORE_PYLON -#include "esphome.h" -#include -#include "common.h" -#include "cbf_store.h" -#include "cbf_pylon.h" -using namespace esphome; - -namespace solar -{ - class cbf_store_pylon : public cbf_store, public cbf_pylon - { - public: - cbf_store_pylon() = delete; // default constructor not allowed - cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); - virtual ~cbf_store_pylon() = default; // virtual destructor for base class - std::shared_ptr clone() const; - void clear(); - void swap(cbf_store_pylon &s); - virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; - virtual std::string to_string() const override; - - cbf_store_pylon(const cbf_store_pylon& b); - cbf_store_pylon& operator=(const cbf_store_pylon& b); - cbf_store_pylon(cbf_store_pylon&& src); - cbf_store_pylon& operator=(cbf_store_pylon&& src); - - private: - virtual std::shared_ptr clone_impl() const override; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD +#define __SOLAR_CBF_STORE_PYLON +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_pylon.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_pylon : public cbf_store, public cbf_pylon + { + public: + cbf_store_pylon() = delete; // default constructor not allowed + cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_pylon() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_pylon &s); + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; + + cbf_store_pylon(const cbf_store_pylon& b); + cbf_store_pylon& operator=(const cbf_store_pylon& b); + cbf_store_pylon(cbf_store_pylon&& src); + cbf_store_pylon& operator=(cbf_store_pylon&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar #endif // __SOLAR_CBF_STORE_PYLON \ No newline at end of file diff --git a/cbf_store_sthome.cpp b/source/solar/cbf_store_sthome.cpp similarity index 97% rename from cbf_store_sthome.cpp rename to source/solar/cbf_store_sthome.cpp index 216b859..b872d15 100644 --- a/cbf_store_sthome.cpp +++ b/source/solar/cbf_store_sthome.cpp @@ -1,71 +1,71 @@ -#include "cbf_store_sthome.h" - -namespace solar -{ - cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) - : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr) - { - this->id = 0; - this->count = 0; - // ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str()); - } - cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b) - : cb_frame(b), cbf_store(b) // call base class copy constructor - { - // ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); - } - cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b) - { - if (this != &b) { - cbf_store_sthome tmp(b); - swap(tmp); - // ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); - } - return *this; - } - cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src) - : cb_frame(src), cbf_store(src) - { - src.clear(); - // ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); - } - cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src) - { - if (this != &src) { - cbf_store::operator=(std::move(src)); - cb_frame::operator=(std::move(src)); - src.clear(); - // ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); - } - return *this; - } - std::shared_ptr cbf_store_sthome::clone() const - { - return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome - } - std::shared_ptr cbf_store_sthome::clone_impl() const - { - //ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome"); - return std::make_shared(*this); - } - void cbf_store_sthome::clear() - { - cb_frame::clear(); - cbf_store::clear(); - } - void cbf_store_sthome::swap(cbf_store_sthome &s) - { - cb_frame::swap(s); - cbf_store::swap(s); - } - cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns) - { - //ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str()); - return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); - } - std::string cbf_store_sthome::to_string() const - { - return cb_frame::to_string() + " " + cbf_sthome::to_string(); - } - +#include "cbf_store_sthome.h" + +namespace solar +{ + cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp) + : cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr) + { + this->id = 0; + this->count = 0; + // ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str()); + } + cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b) + : cb_frame(b), cbf_store(b) // call base class copy constructor + { + // ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b) + { + if (this != &b) { + cbf_store_sthome tmp(b); + swap(tmp); + // ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src) + : cb_frame(src), cbf_store(src) + { + src.clear(); + // ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str()); + } + cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src) + { + if (this != &src) { + cbf_store::operator=(std::move(src)); + cb_frame::operator=(std::move(src)); + src.clear(); + // ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str()); + } + return *this; + } + std::shared_ptr cbf_store_sthome::clone() const + { + return std::static_pointer_cast(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome + } + std::shared_ptr cbf_store_sthome::clone_impl() const + { + //ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome"); + return std::make_shared(*this); + } + void cbf_store_sthome::clear() + { + cb_frame::clear(); + cbf_store::clear(); + } + void cbf_store_sthome::swap(cbf_store_sthome &s) + { + cb_frame::swap(s); + cbf_store::swap(s); + } + cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns) + { + //ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str()); + return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns); + } + std::string cbf_store_sthome::to_string() const + { + return cb_frame::to_string() + " " + cbf_sthome::to_string(); + } + } // namespace solar \ No newline at end of file diff --git a/cbf_store_sthome.h b/source/solar/cbf_store_sthome.h similarity index 97% rename from cbf_store_sthome.h rename to source/solar/cbf_store_sthome.h index bd182bb..f8075f0 100644 --- a/cbf_store_sthome.h +++ b/source/solar/cbf_store_sthome.h @@ -1,34 +1,34 @@ -#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD -#define __SOLAR_CBF_STORE_STHOME_H -#include "esphome.h" -#include -#include "common.h" -#include "cbf_store.h" -#include "cbf_sthome.h" -using namespace esphome; - -namespace solar -{ - class cbf_store_sthome : public cbf_store, public cbf_sthome - { - public: - cbf_store_sthome() = delete; // default constructor not allowed - cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); - virtual ~cbf_store_sthome() = default; // virtual destructor for base class - std::shared_ptr clone() const; - void clear(); - void swap(cbf_store_sthome &s); - virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; - virtual std::string to_string() const override; - - cbf_store_sthome(const cbf_store_sthome& b); - cbf_store_sthome& operator=(const cbf_store_sthome& b); - cbf_store_sthome(cbf_store_sthome&& src); - cbf_store_sthome& operator=(cbf_store_sthome&& src); - - private: - virtual std::shared_ptr clone_impl() const override; - }; - -} // namespace solar +#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD +#define __SOLAR_CBF_STORE_STHOME_H +#include "esphome.h" +#include +#include "common.h" +#include "cbf_store.h" +#include "cbf_sthome.h" +using namespace esphome; + +namespace solar +{ + class cbf_store_sthome : public cbf_store, public cbf_sthome + { + public: + cbf_store_sthome() = delete; // default constructor not allowed + cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp); + virtual ~cbf_store_sthome() = default; // virtual destructor for base class + std::shared_ptr clone() const; + void clear(); + void swap(cbf_store_sthome &s); + virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override; + virtual std::string to_string() const override; + + cbf_store_sthome(const cbf_store_sthome& b); + cbf_store_sthome& operator=(const cbf_store_sthome& b); + cbf_store_sthome(cbf_store_sthome&& src); + cbf_store_sthome& operator=(cbf_store_sthome&& src); + + private: + virtual std::shared_ptr clone_impl() const override; + }; + +} // namespace solar #endif // __SOLAR_CBF_STORE_STHOME_H \ No newline at end of file diff --git a/common.h b/source/solar/common.h similarity index 97% rename from common.h rename to source/solar/common.h index 9f866ad..ae88eaf 100644 --- a/common.h +++ b/source/solar/common.h @@ -1,48 +1,48 @@ -#ifndef __SOLAR_COMMON // include GUARD -#define __SOLAR_COMMON - -namespace solar -{ - - #define FLAGBITS ~0x0FFF // bitwise one's complement (sort column info fits into lower three nibbles) - #define SORTCASE 0x0001000 // case sensitive - #define SORTREVERSE 0x0002000 // reverse order - #define WILDNULL 0x0004000 // zero denotes wildcard, i.e. anything is equal to zero - #define STOPCOMPARE 0x0080000 // stop compare after this column if both items to be compared are valid and a compare was possible - #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) - #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 - #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 - #define max(a, b) ((a) > (b)) ? (a) : (b) - #define min(a, b) ((a) < (b)) ? (a) : (b) - - typedef unsigned int uint; - typedef std::vector byte_vector; - - struct publish_spec_t - { - int on_count; // how many times a duplicate of this frame was received; used to signal publish - int interval; // used for checking whether updateinterval has expired, i.e. to force a publish - int timeout; // used for checking whether timeout has expired, i.e. frame has become stale - publish_spec_t(int on_count, int interval, int timeout) - : on_count(on_count), interval(interval), timeout(timeout) {} - }; - - inline std::string ltrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - size_t start = str.find_first_not_of(chars); - return (start == std::string::npos) ? "" : str.substr(start); - } - - inline std::string rtrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - size_t end = str.find_last_not_of(chars); - return (end == std::string::npos) ? "" : str.substr(0, end + 1); - } - - inline std::string trim(const std::string& str, const std::string& chars = " \t\n\r\f\v") - { - return rtrim(ltrim(str, chars), chars); - } -} // namespace solar - +#ifndef __SOLAR_COMMON // include GUARD +#define __SOLAR_COMMON + +namespace solar +{ + + #define FLAGBITS ~0x0FFF // bitwise one's complement (sort column info fits into lower three nibbles) + #define SORTCASE 0x0001000 // case sensitive + #define SORTREVERSE 0x0002000 // reverse order + #define WILDNULL 0x0004000 // zero denotes wildcard, i.e. anything is equal to zero + #define STOPCOMPARE 0x0080000 // stop compare after this column if both items to be compared are valid and a compare was possible + #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) + #define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 + #define bool_compare(a, b) ((!(a) && !(b)) || ((a) && (b))) ? 0 : (a) ? +1 : -1 + #define max(a, b) ((a) > (b)) ? (a) : (b) + #define min(a, b) ((a) < (b)) ? (a) : (b) + + typedef unsigned int uint; + typedef std::vector byte_vector; + + struct publish_spec_t + { + int on_count; // how many times a duplicate of this frame was received; used to signal publish + int interval; // used for checking whether updateinterval has expired, i.e. to force a publish + int timeout; // used for checking whether timeout has expired, i.e. frame has become stale + publish_spec_t(int on_count, int interval, int timeout) + : on_count(on_count), interval(interval), timeout(timeout) {} + }; + + inline std::string ltrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t start = str.find_first_not_of(chars); + return (start == std::string::npos) ? "" : str.substr(start); + } + + inline std::string rtrim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + size_t end = str.find_last_not_of(chars); + return (end == std::string::npos) ? "" : str.substr(0, end + 1); + } + + inline std::string trim(const std::string& str, const std::string& chars = " \t\n\r\f\v") + { + return rtrim(ltrim(str, chars), chars); + } +} // namespace solar + #endif // __SOLAR_COMMON \ No newline at end of file