Added sthome canbus

This commit is contained in:
Chris Stuurman 2025-09-03 22:38:18 +02:00
parent 03045de7c2
commit ea865dfa94
13 changed files with 361 additions and 163 deletions

View File

@ -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. // 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 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) cb_frame::cb_frame(int msg_id, uint32_t can_id)
{ {
this->msg_id = msg_id; this->msg_id = msg_id;
this->publish = false;
this->rtr = false; this->rtr = false;
this->can_id = can_id; this->can_id = can_id;
this->frame.clear(); 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) cb_frame::cb_frame(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr)
{ {
this->msg_id = msg_id; this->msg_id = msg_id;
this->publish = false;
this->rtr = rtr; this->rtr = rtr;
this->can_id = can_id; this->can_id = can_id;
this->frame = frame; this->frame = frame;
@ -21,6 +34,7 @@ namespace solar
void cb_frame::clear() void cb_frame::clear()
{ {
this->msg_id = 0; this->msg_id = 0;
this->publish = false;
this->rtr = false; this->rtr = false;
this->can_id = 0; this->can_id = 0;
this->frame.clear(); this->frame.clear();
@ -28,6 +42,7 @@ namespace solar
void cb_frame::swap(cb_frame &s) void cb_frame::swap(cb_frame &s)
{ {
std::swap(this->msg_id, s.msg_id); std::swap(this->msg_id, s.msg_id);
std::swap(this->publish, s.publish);
std::swap(this->rtr, s.rtr); std::swap(this->rtr, s.rtr);
std::swap(this->can_id, s.can_id); std::swap(this->can_id, s.can_id);
this->frame.swap(s.frame); this->frame.swap(s.frame);
@ -36,6 +51,34 @@ namespace solar
{ {
return this->can_id != 0; 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 cb_frame::compare(const cb_frame& b, const int *comparecolumns) const
{ {
int result = 0; int result = 0;
@ -132,13 +175,13 @@ namespace solar
} }
return result; 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]; char tag[48];
if(prefix.length() == 0) if(prefix.length() == 0)
snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id); snprintf(tag, sizeof(tag), "%04d 0x%03X ", msg_id, can_id);
else 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); return std::string(tag);
} }
std::string cb_frame::to_string() const std::string cb_frame::to_string() const
@ -147,13 +190,19 @@ namespace solar
// Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes.
std::string text = ""; std::string text = "";
char hex[10]; char hex[10];
snprintf(hex, sizeof(hex), " %s", rtr ? "RTR" : " "); std::string line;
std::string line(hex); int n = 0;
if(rtr) {
line = "<remote request> ";
text = " ";
n = 6; // to align text output
}
else {
if (this->frame.empty()) { if (this->frame.empty()) {
line += " <empty>"; return "<empty>";
return line;
} }
for (const auto& byte : frame) { for (const auto& byte : frame) {
n++;
snprintf(hex, sizeof(hex), "%02X ", byte); snprintf(hex, sizeof(hex), "%02X ", byte);
line += hex; line += hex;
if(byte > 31 && byte < 127) { if(byte > 31 && byte < 127) {
@ -163,6 +212,83 @@ namespace solar
text += "."; text += ".";
} }
} }
}
for (int i = n; i < 8; i++) {
line += " ";
text += " ";
}
return 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<double>::quiet_NaN();
}
if (is_signed) {
value = static_cast<int16_t>((msb << 8) + lsb);
} else {
value = static_cast<uint16_t>((msb << 8) + lsb);
}
//ESP_LOGI("cb_frame::get_double", "0x%02X%02X unsigned %d, scale %d", msb, lsb, value, scale);
return static_cast<double>(value) / inv_scale;
}
// Helper function to convert four scaled values into a byte stream
// set scale to negative for signed values
std::vector<uint8_t> 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<uint8_t> byte_stream(8, 0);
int value;
if(scale1 != 0) {
value = (scale1 < 0) ? static_cast<int16_t>(value1 * -scale1) : static_cast<uint16_t>(value1 * scale1);
byte_stream[0] = value % 256;
byte_stream[1] = (value >> 8) % 256;
}
if(scale2 != 0) {
value = (scale2 < 0) ? static_cast<int16_t>(value2 * -scale2) : static_cast<uint16_t>(value2 * scale2);
byte_stream[2] = value % 256;
byte_stream[3] = (value >> 8) % 256;
}
if(scale3 != 0) {
value = (scale3 < 0) ? static_cast<int16_t>(value3 * -scale3) : static_cast<uint16_t>(value3 * scale3);
byte_stream[4] = value % 256;
byte_stream[5] = (value >> 8) % 256;
}
if(scale4 != 0) {
value = (scale4 < 0) ? static_cast<int16_t>(value4 * -scale4) : static_cast<uint16_t>(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<uint8_t> cb_frame::get_byte_stream(double value1, int scale1, double value2, int scale2, double value3, int scale3, double value4, double scale4)
{
std::vector<uint8_t> byte_stream(8, 0);
int value;
if(scale1 != 0) {
value = (scale1 < 0) ? static_cast<int16_t>(value1 * -scale1) : static_cast<uint16_t>(value1 * scale1);
byte_stream[0] = value % 256;
byte_stream[1] = (value >> 8) % 256;
}
if(scale2 != 0) {
value = (scale2 < 0) ? static_cast<int16_t>(value2 * -scale2) : static_cast<uint16_t>(value2 * scale2);
byte_stream[2] = value % 256;
byte_stream[3] = (value >> 8) % 256;
}
if(scale3 != 0) {
value = (scale3 < 0) ? static_cast<int16_t>(value3 * -scale3) : static_cast<uint16_t>(value3 * scale3);
byte_stream[4] = value % 256;
byte_stream[5] = (value >> 8) % 256;
}
value = (scale4 < 0) ? static_cast<int16_t>(round(value4 * -scale4)) : static_cast<uint16_t>(round(value4 * scale4));
byte_stream[6] = value % 256;
byte_stream[7] = (value >> 8) % 256;
return byte_stream;
}
} // namespace solar } // namespace solar

View File

@ -9,13 +9,15 @@ namespace solar
{ {
class cbf_store; class cbf_store;
class cb_frame { class cb_frame {
private:
mutable bool publish; // whether this frame should be published
public: public:
enum cbf_store_sortcolumns : int enum cbf_sortcolumns : int
{ {
sisortNULL, sisortNULL,
sisortcanid, sisortcanid,
sisortframe,
sisortrtr, sisortrtr,
sisortframe,
sisortmsgid, sisortmsgid,
sisortEND, sisortEND,
sisortcase = SORTCASE, // case sensitive sisortcase = SORTCASE, // case sensitive
@ -27,7 +29,7 @@ namespace solar
bool rtr; bool rtr;
uint32_t can_id; uint32_t can_id;
byte_vector frame; 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);
cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr); cb_frame(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr);
void clear(); 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, const int *comparecolumns) const;
virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; virtual int compare(const cb_frame &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const;
int compare_frame(const byte_vector& _frame) 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 std::string to_string() const;
virtual ~cb_frame() = default; virtual ~cb_frame() = default;
// using default (compiler auto generated) copy and move constructors and assignment operators // 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<uint8_t> 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<uint8_t> 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 } // namespace solar
#endif // __SOLAR_CB_FRAME #endif // __SOLAR_CB_FRAME

View File

@ -32,15 +32,16 @@ namespace solar
} }
auto& kvp = *ret.first; auto& kvp = *ret.first;
auto& item = kvp.second; auto& item = kvp.second;
bool publish = false; auto updateresult = item.update(storeitem, comparecolumns, 1);
if(item.update(storeitem, publish, 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()); // 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 // 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()); // 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); item.update(storeitem);
//cache_map.erase(kvp.first); //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.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"); // 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 } // namespace solar

View File

@ -12,9 +12,8 @@ using namespace esphome;
namespace solar namespace solar
{ {
class cbf_cache { class cbf_cache {
private:
std::map<uint32_t, cbf_cache_item> cache_map; // map of CAN IDs to cache items
public: public:
std::map<uint32_t, cbf_cache_item> cache_map; // map of CAN IDs to cache items
cbf_cache() = default; cbf_cache() = default;
void clear(); void clear();
int size() const; int size() const;
@ -23,7 +22,13 @@ namespace solar
const cbf_cache_item& getitem(uint32_t can_id) const; const cbf_cache_item& getitem(uint32_t can_id) const;
// Add a new item to the cache or update an existing one // Add a new item to the cache or update an existing one
bool additem(const cbf_store& item); 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 // using default (compiler auto generated) copy and move constructors and assignment operators
}; };
} // namespace solar } // namespace solar

View File

@ -15,10 +15,7 @@ namespace solar
this->store0 = store0.clone(); this->store0 = store0.clone();
this->store1 = store0.clone(); this->store1 = store0.clone();
this->store0->id = 1; this->store0->id = 1;
//this->store1->clear();
this->store1->id = 2; 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() void cbf_cache_item::clear()
{ {
@ -32,59 +29,68 @@ namespace solar
} }
bool cbf_cache_item::update(const cbf_store& newitem) 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()) { if(!this->store0->is_valid()) {
store0 = newitem.clone(); store0 = newitem.clone();
store0->id = 1; store0->id = 1;
// ESP_LOGI(store0->tag("== ST1 INV NEW ==").c_str(), store0->to_string().c_str());
return true; return true;
} }
if(!this->store1->is_valid()) { if(!this->store1->is_valid()) {
store1 = newitem.clone(); store1 = newitem.clone();
store1->id = 2; store1->id = 2;
// ESP_LOGI(store1->tag("== ST2 INV NEW ==").c_str(), store1->to_string().c_str());
return true; return true;
} }
bool result = store1->last_timestamp > store0->last_timestamp; bool result = store1->last_timestamp > store0->last_timestamp;
if(result) { if(result) {
store0 = newitem.clone(); store0 = newitem.clone();
this->store0->id = 1; this->store0->id = 1;
// ESP_LOGI(store0->tag("== ST1 OLD NEW ==").c_str(), store0->to_string().c_str());
} }
else { else {
store1 = newitem.clone(); store1 = newitem.clone();
this->store1->id = 2; this->store1->id = 2;
// ESP_LOGI(store1->tag("== OLD ST2 NEW ==").c_str(), store1->to_string().c_str());
} }
return result; 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) { switch(store_selector) {
case 2: case 2:
result = this->store1->update(newitem, publish, comparecolumns); result = this->store1->update(newitem, comparecolumns);
break; break;
default: default:
result = this->store0->update(newitem, publish, comparecolumns); result = this->store0->update(newitem, comparecolumns);
break; 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; 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 std::string cbf_cache_item::tag(const std::string& prefix) const
{ {
char tag[24]; return get_store().tag(prefix, prefix.length());
snprintf(tag, sizeof(tag), "%-18s ", prefix.c_str()); }
return std::string(tag); std::string cbf_cache_item::to_string() const
{
return get_store().to_string();
} }
std::string cbf_cache_item::tag0(const std::string& prefix) const std::string cbf_cache_item::tag0(const std::string& prefix) const
{ {
@ -94,15 +100,16 @@ namespace solar
{ {
return store1->tag(prefix); 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. char buffer[150];
// Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes. snprintf(buffer, sizeof(buffer), "ST1: [%s] %s", trim(tag0()).c_str(), store0->to_string().c_str());
char buffer[180]; return std::string(buffer);
// trim }
auto trimmedtag0 = trim(tag0()); std::string cbf_cache_item::st1_tostring() const
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), "ST2: [%s] %s", trim(tag1()).c_str(), store1->to_string().c_str());
return std::string(buffer); return std::string(buffer);
} }

View File

@ -20,12 +20,15 @@ namespace solar
void clear(); void clear();
void swap(cbf_cache_item &s); void swap(cbf_cache_item &s);
bool update(const cbf_store& newitem); 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 tag0(const std::string& prefix = "") const;
virtual std::string tag1(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 tag(const std::string& prefix = "") const;
virtual std::string to_string() 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 // the default copy and move constructors and assignment operators are fine
// because shared_ptr takes care of the underlying memory management // because shared_ptr takes care of the underlying memory management
cbf_cache_item(const cbf_cache_item& b) = default; cbf_cache_item(const cbf_cache_item& b) = default;

View File

@ -4,14 +4,15 @@ namespace solar
{ {
// see common.h for definition of publish_spec_t // see common.h for definition of publish_spec_t
// publish_spec_t(int on_count, int interval, int timeout) // 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) cbf_pylon& cbf_pylon::operator=(cbf_pylon&& src)
{ {
if (this != &src) { if (this != &src) {
cb_frame::operator=(std::move(src)); cb_frame::operator=(std::move(src));
src.clear(); 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; return *this;
} }
@ -19,6 +20,13 @@ namespace solar
{ {
cb_frame::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 // Function to build a message from the pylon canbus frame
std::string cbf_pylon::to_string() const std::string cbf_pylon::to_string() const
{ {
@ -27,6 +35,9 @@ namespace solar
{ {
case CB_BATTERY_LIMITS: case CB_BATTERY_LIMITS:
{ {
if(rtr) {
return "Request for BATTERY LIMITS info";
}
if (this->frame.size() < 6) { if (this->frame.size() < 6) {
return "Invalid frame size for CB_BATTERY_LIMITS"; return "Invalid frame size for CB_BATTERY_LIMITS";
} }
@ -40,6 +51,9 @@ namespace solar
break; break;
case CB_BATTERY_STATE: case CB_BATTERY_STATE:
{ {
if(rtr) {
return "Request for BATTERY STATE info";
}
if (this->frame.size() < 4) { if (this->frame.size() < 4) {
return "Invalid frame size for CB_BATTERY_STATE"; return "Invalid frame size for CB_BATTERY_STATE";
} }
@ -52,6 +66,9 @@ namespace solar
break; break;
case CB_BATTERY_STATUS: case CB_BATTERY_STATUS:
{ {
if(rtr) {
return "Request for BATTERY STATUS info";
}
if (this->frame.size() < 6) { if (this->frame.size() < 6) {
return "Invalid frame size for CB_BATTERY_STATUS"; return "Invalid frame size for CB_BATTERY_STATUS";
} }
@ -65,6 +82,9 @@ namespace solar
break; break;
case CB_BATTERY_FAULT: case CB_BATTERY_FAULT:
{ {
if(rtr) {
return "Request for BATTERY FAULT info";
}
if (this->frame.size() < 8) { if (this->frame.size() < 8) {
return "Invalid frame size for CB_BATTERY_FAULT"; return "Invalid frame size for CB_BATTERY_FAULT";
} }
@ -96,6 +116,9 @@ namespace solar
break; break;
case CB_BATTERY_REQUEST_FLAGS: case CB_BATTERY_REQUEST_FLAGS:
{ {
if(rtr) {
return "Request for BATTERY REQUEST FLAGS info";
}
if (this->frame.size() < 1) { if (this->frame.size() < 1) {
return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS"; return "Invalid frame size for CB_BATTERY_REQUEST_FLAGS";
} }
@ -112,6 +135,9 @@ namespace solar
break; break;
case CB_BATTERY_MANUFACTURER: case CB_BATTERY_MANUFACTURER:
{ {
if(rtr) {
return "Request for BATTERY MANUFACTURER info";
}
if (this->frame.size() < 8) { if (this->frame.size() < 8) {
return "Invalid frame size for CB_BATTERY_MANUFACTURER"; return "Invalid frame size for CB_BATTERY_MANUFACTURER";
} }
@ -124,6 +150,9 @@ namespace solar
} }
break; break;
} }
if(rtr) {
return "Request for unknown CAN ID info";
}
return "Unknown CAN ID"; return "Unknown CAN ID";
} }
} // namespace solar } // namespace solar

View File

@ -8,70 +8,30 @@ using namespace esphome;
namespace solar namespace solar
{ {
class cbf_pylon : virtual public cb_frame { class cbf_pylon : virtual public cb_frame {
public: 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 // 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 // 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 // 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_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_STATE = 0x355; // VERIFIED: Pylontech battery state message
static const int CB_BATTERY_STATUS = 0x356; // VERIFIED: Pylontech battery status 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_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_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_MANUFACTURER = 0x35E; // VERIFIED: Pylontech battery manufacturer message
static const int CB_BATTERY_MODEL = 0x35D;
static const int CB_BATTERY_SERIAL_NUMBER = 0x35F; //Property<int, cbf_pylon> battery_charge_voltage_limit {this, &cbf_pylon::_set_battery_charge_voltage_limit, &cbf_pylon::_get_battery_charge_voltage_limit };
static const int CB_BATTERY_VERSION = 0x360; //Property<float, cbf_pylon> battery_charge_voltage_limit {this, &cbf_pylon::_get_battery_charge_voltage_limit};
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; cbf_pylon() = default;
cbf_pylon(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr);
void clear(); 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 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 // using default (compiler auto generated) copy and move constructors and assignment operators
}; };

View File

@ -64,24 +64,29 @@ namespace solar
{ {
return (this->last_timestamp == 0) || ((currenttime - this->last_timestamp) >= timeout_interval); 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 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, comparecolumns); // we are forced to instantiate this class, so we provide a default implementation 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()) { 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; time_t newtime = newitem.last_timestamp;
bool publish_expired = this->is_publish_expired(newtime, publish_spec.interval); bool publish_expired = this->is_publish_expired(newtime, pbspec.interval);
bool validity_expired = this->is_validity_expired(newtime, publish_spec.timeout); bool validity_expired = this->is_validity_expired(newtime, pbspec.timeout);
bool isduplicate = this->compare(newitem, comparecolumns) == 0; bool isduplicate = this->compare(newitem, comparecolumns) == 0;
time_t reset_timer = this->first_timestamp + publish_spec.interval - newtime; time_t reset_timer = this->first_timestamp + pbspec.interval - newtime;
auto ntstime = ESPTime::from_epoch_local(newtime).strftime("%H:%M:%S");
int timediff = is_valid() ? (int)(newtime - this->last_timestamp) : -1; 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()); 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) { if(validity_expired) {
*this = newitem; *this = newitem;
if(!isduplicate) { if(!isduplicate) {
@ -91,15 +96,22 @@ namespace solar
} }
if(isduplicate || publish_expired) { if(isduplicate || publish_expired) {
this->count++; 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<cbf_updateresult>(result | cbf_updateresult::stu_PUBLISH);
this->last_timestamp = newtime; this->last_timestamp = newtime;
if(reset_timer <= 0) { if(reset_timer <= 0) {
this->first_timestamp = newtime; this->first_timestamp = newtime;
this->count = 1; 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()); // ftstime = ESPTime::from_epoch_local(this->first_timestamp).strftime("%H:%M:%S");
return isduplicate; // 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 cbf_store::compare(const cbf_store& b, const int *comparecolumns) const
{ {
@ -199,15 +211,15 @@ namespace solar
result = -result; result = -result;
return 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. // 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. // Incorrect placement / misaligned arguments can lead to undefined behavior and processor crashes.
char tag[48]; char tag[48];
if(prefix.length() == 0) if(prefix.length() == 0)
snprintf(tag, sizeof(tag), "%04d id%d 0x%03X ", msg_id, id, can_id); snprintf(tag, sizeof(tag), "#%04d s%d 0x%03X %s", msg_id, id, can_id, getpublish() ? "P" : " ");
else else
snprintf(tag, sizeof(tag), "%-18s %04d id%d 0x%03X ", prefix.c_str(), msg_id, id, can_id); 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); return std::string(tag);
} }
std::string cbf_store::to_string() const std::string cbf_store::to_string() const

View File

@ -16,12 +16,12 @@ namespace solar
int count; // how many times a duplicate of this frame was received; used to signal publish 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 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 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, sisortNULL,
sisortcanid = cb_frame::sisortcanid, sisortcanid = cb_frame::sisortcanid,
sisortframe = cb_frame::sisortframe,
sisortrtr = cb_frame::sisortrtr, sisortrtr = cb_frame::sisortrtr,
sisortframe = cb_frame::sisortframe,
sisortmsgid = cb_frame::sisortmsgid, sisortmsgid = cb_frame::sisortmsgid,
sisortcount, sisortcount,
sisortfirst_timestamp, sisortfirst_timestamp,
@ -32,6 +32,14 @@ namespace solar
sisortwild = WILDNULL, // where applicable, an empty or null entry equates to wildcard 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 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() = 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);
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, time_t _first_timestamp, time_t _last_timestamp);
@ -43,12 +51,12 @@ namespace solar
std::shared_ptr<cbf_store> clone() const; std::shared_ptr<cbf_store> clone() const;
int compare(const cbf_store& b, const int *comparecolumns) const; int compare(const cbf_store& b, const int *comparecolumns) const;
std::string tag(const std::string& prefix = "") const override; virtual std::string tag(const std::string& prefix = "", int prefixlen = 18) const override;
std::string to_string() const override; virtual std::string to_string() const override;
bool is_publish_expired(time_t currenttime, int update_interval) const; bool is_publish_expired(time_t currenttime, int update_interval) const;
bool is_validity_expired(time_t currenttime, int timeout_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); cbf_updateresult update(const cbf_store& newitem, publish_spec_t publish_spec, publish_spec_t rtr_publish_spec, 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 virtual cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns); // should be pure virtual function, but we need to instantiate this class
private: private:
int compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const; int compare(const cbf_store &b, int cmpflag, bool *stopcomparesignal, bool validnextcol) const;

View File

@ -2,8 +2,8 @@
namespace solar namespace solar
{ {
cbf_store_pylon::cbf_store_pylon(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr, time_t 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) : 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->id = 0;
this->count = 0; this->count = 0;
@ -58,14 +58,14 @@ namespace solar
cb_frame::swap(s); cb_frame::swap(s);
cbf_store::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()); //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 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 } // namespace solar

View File

@ -18,8 +18,8 @@ namespace solar
std::shared_ptr<cbf_store_pylon> clone() const; std::shared_ptr<cbf_store_pylon> clone() const;
void clear(); void clear();
void swap(cbf_store_pylon &s); void swap(cbf_store_pylon &s);
virtual bool update(const cbf_store& newitem, bool& publish, const int *comparecolumns) override; virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override;
std::string to_string() const override; virtual std::string to_string() const override;
cbf_store_pylon(const cbf_store_pylon& b); cbf_store_pylon(const cbf_store_pylon& b);
cbf_store_pylon& operator=(const cbf_store_pylon& b); cbf_store_pylon& operator=(const cbf_store_pylon& b);

View File

@ -12,6 +12,8 @@ namespace solar
#define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards) #define SEARCHWILD 0x0100000 // do wildcard comparison (only searchkey should contain wildcards)
#define num_compare(a, b) ((a) > (b)) ? +1 : ((a) < (b)) ? -1 : 0 #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 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 unsigned int uint;
typedef std::vector<uint8_t> byte_vector; typedef std::vector<uint8_t> byte_vector;