first commit
This commit is contained in:
commit
03045de7c2
168
cb_frame.cpp
Normal file
168
cb_frame.cpp
Normal file
@ -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 += " <empty>";
|
||||
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
|
||||
46
cb_frame.h
Normal file
46
cb_frame.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef __SOLAR_CB_FRAME // include GUARD
|
||||
#define __SOLAR_CB_FRAME
|
||||
#include "esphome.h"
|
||||
#include <utility>
|
||||
#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
|
||||
54
cbf_cache.cpp
Normal file
54
cbf_cache.cpp
Normal file
@ -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
|
||||
31
cbf_cache.h
Normal file
31
cbf_cache.h
Normal file
@ -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 <utility>
|
||||
#include <map>
|
||||
#include "esphome.h"
|
||||
#include "cbf_store_pylon.h"
|
||||
#include "cbf_cache_item.h"
|
||||
using namespace esphome;
|
||||
|
||||
namespace solar
|
||||
{
|
||||
class cbf_cache {
|
||||
private:
|
||||
std::map<uint32_t, cbf_cache_item> 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
|
||||
110
cbf_cache_item.cpp
Normal file
110
cbf_cache_item.cpp
Normal file
@ -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<cbf_store>(0, 0, 1, byte_vector(), false, 0, 0); // make generic empty store
|
||||
this->store1 = std::make_shared<cbf_store>(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
|
||||
|
||||
39
cbf_cache_item.h
Normal file
39
cbf_cache_item.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef __SOLAR_CBF_CACHE_ITEM
|
||||
#define __SOLAR_CBF_CACHE_ITEM
|
||||
#include <utility>
|
||||
#include <map>
|
||||
#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<cbf_store> store0;
|
||||
std::shared_ptr<cbf_store> 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
|
||||
129
cbf_pylon.cpp
Normal file
129
cbf_pylon.cpp
Normal file
@ -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<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A
|
||||
float discharge_current_limit = 0.1 * static_cast<int16_t>((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<uint16_t>((x[1] << 8) + x[0]);
|
||||
uint soh = static_cast<uint16_t>((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<int16_t>((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<int16_t>((x[3] << 8) + x[2]); // unit = 0.1A Module or system total current
|
||||
float average_cell_temperature = 0.1 * static_cast<int16_t>((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<const char*>(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
|
||||
80
cbf_pylon.h
Normal file
80
cbf_pylon.h
Normal file
@ -0,0 +1,80 @@
|
||||
#ifndef __SOLAR_CBF_PYLON // include GUARD
|
||||
#define __SOLAR_CBF_PYLON
|
||||
#include "esphome.h"
|
||||
#include <utility>
|
||||
#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
|
||||
223
cbf_store.cpp
Normal file
223
cbf_store.cpp
Normal file
@ -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> cbf_store::clone() const
|
||||
{
|
||||
return clone_impl();
|
||||
}
|
||||
std::shared_ptr<cbf_store> 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<cbf_store>(*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<int>(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<int>(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
|
||||
58
cbf_store.h
Normal file
58
cbf_store.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef __SOLAR_CBF_STORE // include GUARD
|
||||
#define __SOLAR_CBF_STORE
|
||||
#include "esphome.h"
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#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<cbf_store> 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<cbf_store> clone_impl() const;
|
||||
};
|
||||
} // namespace solar
|
||||
#endif // __SOLAR_CBF_STORE
|
||||
71
cbf_store_pylon.cpp
Normal file
71
cbf_store_pylon.cpp
Normal file
@ -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> cbf_store_pylon::clone() const
|
||||
{
|
||||
return std::static_pointer_cast<cbf_store_pylon>(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_pylon
|
||||
}
|
||||
std::shared_ptr<cbf_store> cbf_store_pylon::clone_impl() const
|
||||
{
|
||||
//ESP_LOGI(tag("store_pylon CLONE").c_str(), "%-20s", "Cloning store_pylon");
|
||||
return std::make_shared<cbf_store_pylon>(*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
|
||||
34
cbf_store_pylon.h
Normal file
34
cbf_store_pylon.h
Normal file
@ -0,0 +1,34 @@
|
||||
#ifndef __SOLAR_CBF_STORE_PYLON // include GUARD
|
||||
#define __SOLAR_CBF_STORE_PYLON
|
||||
#include "esphome.h"
|
||||
#include <utility>
|
||||
#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<cbf_store_pylon> 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<cbf_store> clone_impl() const override;
|
||||
};
|
||||
|
||||
} // namespace solar
|
||||
#endif // __SOLAR_CBF_STORE_PYLON
|
||||
46
common.h
Normal file
46
common.h
Normal file
@ -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<uint8_t> 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
|
||||
Loading…
Reference in New Issue
Block a user