Old uncommited updates

This commit is contained in:
Chris Stuurman 2025-10-10 17:25:17 +02:00
parent d44b60f637
commit 9b71eca610
5 changed files with 531 additions and 0 deletions

View File

@ -0,0 +1,6 @@
# ESPHome config/source/solar Folder
This folder contains Contains source code files and related resources for solar projects.
For more information, visit the [ESPHome documentation](https://esphome.io/).

342
cbf_sthome.cpp Normal file
View File

@ -0,0 +1,342 @@
#include "cbf_sthome.h"
namespace solar
{
// see common.h for definition of publish_spec_t
// publish_spec_t(int on_count, int interval, int timeout)
const publish_spec_t cbf_sthome::publish_spec = publish_spec_t(3, 15, 30); // default publish spec
const publish_spec_t cbf_sthome::rtr_publish_spec = publish_spec_t(2, 10, 40); // for remote transmission requests
const std::string cbf_sthome::heartbeat = "HELLO\0\0\0"; // must be exactly 8 bytes including null terminators
//cbf_sthome::cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr)
// : cb_frame(msg_id, can_id, frame, rtr)
// {
// }
cbf_sthome& cbf_sthome::operator=(cbf_sthome&& src)
{
if (this != &src) {
cb_frame::operator=(std::move(src));
src.clear();
//ESP_LOGI(tag("pylon MOASS").c_str(), "%-20s %s", "Assigned pylon", this->to_string().c_str());
}
return *this;
}
void cbf_sthome::clear()
{
cb_frame::clear();
}
bool cbf_sthome::verify_heartbeat() const
{
const auto& x = this->frame;
std::string hb(reinterpret_cast<const char*>(x.data()), x.size());
hb.erase(std::find_if(hb.rbegin(), hb.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), hb.end());
return hb == heartbeat;
}
// float cbf_sthome::_get_battery_charge_voltage_limit() const
// {
// const auto& x = this->frame;
// return 0.1 * (float)((x[1] << 8) + x[0]);
// }
// Function to build a message from the pylon canbus frame
std::string cbf_sthome::to_string() const
{
char buffer[128];
const auto& x = this->frame;
switch (can_id)
{
case CB_CANBUS_ID01:
return rtr ? "Request for sthome-ut1 heartbeat" : verify_heartbeat() ? "sthome-ut1 alive" : "";
case CB_CANBUS_ID02:
return rtr ? "Request for sthome-ut2 heartbeat" : verify_heartbeat() ? "sthome-ut2 alive" : "";
case CB_CANBUS_ID03:
return rtr ? "Request for sthome-u3 heartbeat" : verify_heartbeat() ? "sthome-ut3 alive " : "";
case CB_CANBUS_ID04:
return rtr ? "Request for sthome-ut4 heartbeat" : verify_heartbeat() ? "sthome-ut4 alive" : "";
case CB_CANBUS_ID05:
return rtr ? "Request for sthome-ut5 heartbeat" : verify_heartbeat() ? "sthome-ut5 alive" : "";
case CB_CANBUS_ID06:
return rtr ? "Request for sthome-ut6 heartbeat" : verify_heartbeat() ? "sthome-ut6 alive" : "";
case CB_CANBUS_ID07:
return rtr ? "Request for sthome-ut7 heartbeat" : verify_heartbeat() ? "sthome-ut7 alive" : "";
case CB_CANBUS_ID08:
return rtr ? "Request for sthome-ut8 heartbeat" : verify_heartbeat() ? "sthome-ut8 alive" : "";
case CB_CANBUS_ID09:
return rtr ? "Request for sthome-ut9 heartbeat" : verify_heartbeat() ? "sthome-ut9 alive" : "";
case CB_CANBUS_ID10:
return rtr ? "Request for sthome-ut10 heartbeat" : verify_heartbeat() ? "sthome-ut10 alive" : "";
case CB_GEYSER_TEMPERATURE_TOP:
{
if(rtr) {
return "Request for geyser top temp";
}
double temp_top = get_double(x[0], x[1], 256, true);
snprintf (buffer, sizeof(buffer), "GEYSER TOP: %.2f°C", temp_top);
return buffer;
}
case CB_GEYSER_TEMPERATURE_BOTTOM:
{
if(rtr) {
return "Request for geyser bottom temp";
}
double temp_bot = get_double(x[0], x[1], 256, true);
snprintf (buffer, sizeof(buffer), "GEYSER BOT: %.2f°C", temp_bot);
return buffer;
}
case CB_GEYSER_TEMPERATURE_AMBIENT:
{
if(rtr) {
return "Request for geyser ambient temp";
}
double temp_amb = get_double(x[0], x[1], 256, true);
snprintf (buffer, sizeof(buffer), "GEYSER AMB: %.2f°C", temp_amb);
return buffer;
}
case CB_GEYSER_HEATING:
{
if(rtr) {
return "Request for geyser heating";
}
double heating_loss = get_double(x[0], x[1], 64, true); // -512W to 512W
double heat_gained = get_double(x[2], x[3], 128); // 0 to 512W
double calculated_heat_loss = get_double(x[4], x[5], 128); // 0 to 512W
unsigned int est_heating_time = (unsigned int) static_cast<uint16_t>(256 * x[7] + x[6]); // 0 to 65536 seconds
snprintf (buffer, sizeof(buffer), "Heat: loss %.3fW, gain %.3fW, calc loss %.3fW, est. heat time %ds", heating_loss, heat_gained, calculated_heat_loss, est_heating_time);
return buffer;
}
case CB_GEYSER_ACTIVE_SCHEDULE:
{
if(rtr) {
return "Request for geyser schedule";
}
double active_schedule_temp = get_double(x[0], x[1], 256, true); // -128 to 128°C
int active_heating_time = static_cast<int16_t>(256 * x[3] + x[2]); // -32768 to 32768s
double heating_overshoot_time = get_double(x[4], x[5], -64); // -512 to 512s
int active_schedule_day = static_cast<uint8_t>(x[6]);
snprintf (buffer, sizeof(buffer), "Geyser Schedule: target %.2f°C, heating time %ds, overshoot %.1fs, day %d", active_schedule_temp, active_heating_time, heating_overshoot_time, active_schedule_day);
return buffer;
}
case CB_POWER_MAINS:
{
if(rtr) {
return "Request for power mains";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "Mains : %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_INVERTER:
{
if(rtr) {
return "Request for power inverter";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "InvOut: %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_PLUGS:
{
if(rtr) {
return "Request for power plugs";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "Plugs : %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_LIGHTS:
{
if(rtr) {
return "Request for power lights";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "Lights: %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_GEYSER:
{
if(rtr) {
return "Request for power geyser";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "Geyser: %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_POOL:
{
if(rtr) {
return "Request for power pool";
}
double power = get_double(x[0], x[1], 2048); // 0 to 32kW
double voltage = get_double(x[2], x[3], 128); // 0 to 512V
double current = get_double(x[4], x[5], 512); // 0 to 128A
snprintf (buffer, sizeof(buffer), "Pool : %.3fV, %.3fA, %.3fkW", voltage, current, power);
return buffer;
}
case CB_POWER_GENERATED:
{
if(rtr) {
return "Request for power generated";
}
double power_generated = get_double(x[0], x[1], 2048); // 0 to 32kW
double power_loss = get_double(x[2], x[3], 2048); // 0 to 32kW
snprintf (buffer, sizeof(buffer), "Power : Generated %.3fkW, Loss %.3fkW", power_generated, power_loss);
return buffer;
}
case CB_ENERGY_MAINS:
{
if(rtr) {
return "Request for energy mains";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Mains Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_GEYSER:
{
if(rtr) {
return "Request for energy geyser";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Geyser Energy day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_POOL:
{
if(rtr) {
return "Request for energy pool";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Pool Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_PLUGS:
{
if(rtr) {
return "Request for energy plugs";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Plugs Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_LIGHTS:
{
if(rtr) {
return "Request for energy lights";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Lights Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_HOUSE:
{
if(rtr) {
return "Request for energy house";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "House Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_GENERATED:
{
if(rtr) {
return "Request for energy generated";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Generated Energy: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_ENERGY_LOSS:
{
if(rtr) {
return "Request for energy loss";
}
double energy_daily = get_double(x[0], x[1], 512); // 0 to 128kWh
double energy_monthly = get_double(x[2], x[3], 32); // 0 to 2048kWh
double energy_yearly = get_double(x[4], x[5], 2); // 0 to 32768kWh
double energy_lifetime = 0.01 * ((x[7] << 8) + x[6]); // 0 to 655MWh
snprintf (buffer, sizeof(buffer), "Energy Loss: day %.3fkWh, month %.3fkWh, year %.1fkWh, lifetime %.2fMWh", energy_daily, energy_monthly, energy_yearly, energy_lifetime);
return buffer;
}
case CB_CONTROLLER_STATES:
{
if(rtr) {
return "Request for controller states";
}
uint8_t alarms = x[0];
uint8_t states = x[1];
uint8_t modes = x[2];
bool inverter_1_battery_low = alarms & 0x80;
bool inverter_2_battery_low = alarms & 0x10;
bool inverter_overload = alarms & 0x08;
bool geyser_heating = states & 0x80;
bool geyser_energised = states & 0x40;
bool mains_supply_present = states & 0x20;
bool battery_charging = states & 0x08;
bool vacation_mode = modes & 0x80; // nobody at home
std::string mode = "";
int geysermode = (modes & 0x70) >> 4;
switch (geysermode) {
case GM_SUNDAY:
mode = "SUN";
break;
case GM_WORKDAY:
mode = "WRK";
break;
case GM_SATURDAY:
mode = "SAT";
break;
case GM_PUBLIC_HOLIDAY:
mode = "PUB";
break;
case GM_SCHOOL_HOLIDAY:
mode = "SCH";
break;
default:
mode = "UNK";
break;
}
snprintf(buffer, sizeof(buffer), "STATES: %s%s%s%s MODE: %s%s INV: %s%s%s", geyser_heating ? "HEAT " : "", geyser_energised ? "GEYSER " : "", mains_supply_present ? "MAINS " : "", battery_charging ? "BATCHG " : "", vacation_mode ? "VACA ": "", mode.c_str(), inverter_1_battery_low ? "INV LOBAT " : "", inverter_2_battery_low ? "INV2 LOBAT " : "", inverter_overload ? "INV OVL " : "");
return buffer;
}
}
if(rtr) {
return "Request for unknown sthome CAN ID";
}
return "Unknown CAN ID";
}
} // namespace solar

78
cbf_sthome.h Normal file
View File

@ -0,0 +1,78 @@
#ifndef __SOLAR_CBF_STHOME_H // include GUARD
#define __SOLAR_CBF_STHOME_H
#include "esphome.h"
#include <utility>
#include "common.h"
#include "cb_frame.h"
using namespace esphome;
namespace solar
{
class cbf_sthome : virtual public cb_frame {
private:
// float _get_battery_charge_voltage_limit() const;
public:
// STHOME publish spec
// This is used to determine when to publish the sthome data
static const publish_spec_t publish_spec; // default publish spec for sthome messages
static const publish_spec_t rtr_publish_spec; // for remote transmission requests
enum geyser_modes : int {
GM_SUNDAY,
GM_WORKDAY,
GM_SATURDAY,
GM_PUBLIC_HOLIDAY,
GM_SCHOOL_HOLIDAY,
};
enum canbus_ids : int {
CB_CANBUS_ID01 = 0x501,
CB_CANBUS_ID02,
CB_CANBUS_ID03,
CB_CANBUS_ID04,
CB_CANBUS_ID05,
CB_CANBUS_ID06,
CB_CANBUS_ID07,
CB_CANBUS_ID08,
CB_CANBUS_ID09,
CB_CANBUS_ID10,
CB_POWER_MAINS = 0x401,
CB_POWER_INVERTER,
CB_POWER_PLUGS,
CB_POWER_LIGHTS,
CB_POWER_GEYSER,
CB_POWER_POOL,
CB_POWER_GENERATED,
CB_ENERGY_MAINS,
CB_ENERGY_GEYSER,
CB_ENERGY_POOL,
CB_ENERGY_PLUGS,
CB_ENERGY_LIGHTS,
CB_ENERGY_HOUSE,
CB_ENERGY_GENERATED,
CB_ENERGY_LOSS,
CB_CONTROLLER_STATES,
CB_GEYSER_TEMPERATURE_TOP,
CB_GEYSER_TEMPERATURE_BOTTOM,
CB_GEYSER_TEMPERATURE_AMBIENT,
CB_GEYSER_HEATING,
CB_GEYSER_ACTIVE_SCHEDULE,
};
//Property<int, cbf_sthome> battery_charge_voltage_limit {this, &cbf_sthome::_set_battery_charge_voltage_limit, &cbf_sthome::_get_battery_charge_voltage_limit };
//Property<float, cbf_sthome> battery_charge_voltage_limit {this, &cbf_sthome::_get_battery_charge_voltage_limit};
static const std::string heartbeat;
cbf_sthome() = default;
cbf_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr);
void clear();
virtual std::string to_string() const override;
cbf_sthome& operator=(cbf_sthome&& src); // required due to double inheritance in cbf_store_pylon
// using default (compiler auto generated) copy and move constructors and assignment operators
virtual ~cbf_sthome() = default;
bool verify_heartbeat() const;
};
} // namespace solar
#endif // __SOLAR_CBF_STHOME_H

71
cbf_store_sthome.cpp Normal file
View File

@ -0,0 +1,71 @@
#include "cbf_store_sthome.h"
namespace solar
{
cbf_store_sthome::cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& frame, bool rtr, time_t timestamp)
: cb_frame(msg_id, can_id, frame, rtr), cbf_store(msg_id, can_id, 0, timestamp, timestamp) //, cbf_sthome(msg_id, can_id, frame, rtr)
{
this->id = 0;
this->count = 0;
// ESP_LOGI(tag("store_sthome CTOR1").c_str(), "%-20s %s", "Created store_sthome", this->to_string().c_str());
}
cbf_store_sthome::cbf_store_sthome(const cbf_store_sthome& b)
: cb_frame(b), cbf_store(b) // call base class copy constructor
{
// ESP_LOGI(tag("store_sthome CCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str());
}
cbf_store_sthome& cbf_store_sthome::operator=(const cbf_store_sthome& b)
{
if (this != &b) {
cbf_store_sthome tmp(b);
swap(tmp);
// ESP_LOGI(tag("store_sthome AOPP").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str());
}
return *this;
}
cbf_store_sthome::cbf_store_sthome(cbf_store_sthome&& src)
: cb_frame(src), cbf_store(src)
{
src.clear();
// ESP_LOGI(tag("store_sthome MCCTOR").c_str(), "%-20s %s", "Copied store_sthome", this->to_string().c_str());
}
cbf_store_sthome& cbf_store_sthome::operator=(cbf_store_sthome&& src)
{
if (this != &src) {
cbf_store::operator=(std::move(src));
cb_frame::operator=(std::move(src));
src.clear();
// ESP_LOGI(tag("store_sthome MOASS").c_str(), "%-20s %s", "Assigned store_sthome", this->to_string().c_str());
}
return *this;
}
std::shared_ptr<cbf_store_sthome> cbf_store_sthome::clone() const
{
return std::static_pointer_cast<cbf_store_sthome>(clone_impl()); // static_pointer_cast is safe here because we know the underlying type is cbf_store_sthome
}
std::shared_ptr<cbf_store> cbf_store_sthome::clone_impl() const
{
//ESP_LOGI(tag("store_sthome CLONE").c_str(), "%-20s", "Cloning store_sthome");
return std::make_shared<cbf_store_sthome>(*this);
}
void cbf_store_sthome::clear()
{
cb_frame::clear();
cbf_store::clear();
}
void cbf_store_sthome::swap(cbf_store_sthome &s)
{
cb_frame::swap(s);
cbf_store::swap(s);
}
cbf_store::cbf_updateresult cbf_store_sthome::update(const cbf_store& newitem, const int *comparecolumns)
{
//ESP_LOGI(tag("store_sthome UPDATE").c_str(), "%-20s %s", "Updating store_sthome", this->to_string().c_str());
return cbf_store::update(newitem, publish_spec, rtr_publish_spec, comparecolumns);
}
std::string cbf_store_sthome::to_string() const
{
return cb_frame::to_string() + " " + cbf_sthome::to_string();
}
} // namespace solar

34
cbf_store_sthome.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __SOLAR_CBF_STORE_STHOME_H // include GUARD
#define __SOLAR_CBF_STORE_STHOME_H
#include "esphome.h"
#include <utility>
#include "common.h"
#include "cbf_store.h"
#include "cbf_sthome.h"
using namespace esphome;
namespace solar
{
class cbf_store_sthome : public cbf_store, public cbf_sthome
{
public:
cbf_store_sthome() = delete; // default constructor not allowed
cbf_store_sthome(int msg_id, uint32_t can_id, const byte_vector& _frame, bool _rtr , time_t timestamp);
virtual ~cbf_store_sthome() = default; // virtual destructor for base class
std::shared_ptr<cbf_store_sthome> clone() const;
void clear();
void swap(cbf_store_sthome &s);
virtual cbf_store::cbf_updateresult update(const cbf_store& newitem, const int *comparecolumns) override;
virtual std::string to_string() const override;
cbf_store_sthome(const cbf_store_sthome& b);
cbf_store_sthome& operator=(const cbf_store_sthome& b);
cbf_store_sthome(cbf_store_sthome&& src);
cbf_store_sthome& operator=(cbf_store_sthome&& src);
private:
virtual std::shared_ptr<cbf_store> clone_impl() const override;
};
} // namespace solar
#endif // __SOLAR_CBF_STORE_STHOME_H