You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

335 lines
13 KiB

//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __HM_SYSTEM_H__
#define __HM_SYSTEM_H__
#include "hmInverter.h"
#define AC_POWER_PATH AHOY_HIST_PATH "/ac_power"
#define AC_FORMAT_FILE_NAME "%02u_%02u_%04u.bin"
template <uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>>
class HmSystem {
public:
HmSystem() {}
void setup(uint32_t *timestamp) {
mTimestamp = timestamp;
mNumInv = 0;
}
void addInverters(cfgInst_t *config) {
Inverter<> *iv;
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = addInverter(&config->iv[i]);
if (0ULL != config->iv[i].serial.u64) {
if (NULL != iv) {
DPRINT(DBG_INFO, "added inverter ");
if(iv->config->serial.b[5] == 0x11)
DBGPRINT("HM");
else {
DBGPRINT(((iv->config->serial.b[4] & 0x03) == 0x01) ? " (2nd Gen) " : " (3rd Gen) ");
}
DBGPRINTLN(String(iv->config->serial.u64, HEX));
if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01))
DPRINTLN(DBG_WARN, F("MI Inverter are not fully supported now!!!"));
}
}
}
}
INVERTERTYPE *addInverter(cfgIv_t *config) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:addInverter"));
if(MAX_INVERTER <= mNumInv) {
DPRINT(DBG_WARN, F("max number of inverters reached!"));
return NULL;
}
INVERTERTYPE *p = &mInverter[mNumInv];
p->id = mNumInv;
p->config = config;
DPRINT(DBG_VERBOSE, "SERIAL: " + String(p->config->serial.b[5], HEX));
DPRINTLN(DBG_VERBOSE, " " + String(p->config->serial.b[4], HEX));
if((p->config->serial.b[5] == 0x11) || (p->config->serial.b[5] == 0x10)) {
switch(p->config->serial.b[4]) {
case 0x22:
case 0x21: p->type = INV_TYPE_1CH; break;
case 0x42:
case 0x41: p->type = INV_TYPE_2CH; break;
case 0x62:
case 0x61: p->type = INV_TYPE_4CH; break;
default:
DPRINTLN(DBG_ERROR, F("unknown inverter type"));
break;
}
if(p->config->serial.b[5] == 0x11)
p->ivGen = IV_HM;
else if((p->config->serial.b[4] & 0x03) == 0x02) // MI 3rd Gen -> same as HM
p->ivGen = IV_HM;
else // MI 2nd Gen
p->ivGen = IV_MI;
}
else if(p->config->serial.u64 != 0ULL)
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
p->init();
mNumInv ++;
return p;
}
INVERTERTYPE *findInverter(uint8_t buf[]) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:findInverter"));
INVERTERTYPE *p;
for(uint8_t i = 0; i < mNumInv; i++) {
p = &mInverter[i];
if((p->config->serial.b[3] == buf[0])
&& (p->config->serial.b[2] == buf[1])
&& (p->config->serial.b[1] == buf[2])
&& (p->config->serial.b[0] == buf[3]))
return p;
}
return NULL;
}
INVERTERTYPE *getInverterByPos(uint8_t pos, bool check = true) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:getInverterByPos"));
if(pos >= MAX_INVERTER)
return NULL;
else if((mInverter[pos].initialized && mInverter[pos].config->serial.u64 != 0ULL) || false == check)
return &mInverter[pos];
else
return NULL;
}
uint8_t getNumInverters(void) {
/*uint8_t num = 0;
INVERTERTYPE *p;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
p = &mInverter[i];
if(p->config->serial.u64 != 0ULL)
num++;
}
return num;*/
return MAX_NUM_INVERTERS;
}
//-----------------------------------------------------------------------------
void cleanup_history ()
{
time_t time_today;
INVERTERTYPE *p;
uint16_t i;
cur_pac_index = 0;
for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) {
p->pac_cnt = 0;
p->pac_sum = 0;
}
if ((time_today = *mTimestamp)) {
#ifdef ESP32
File ac_power_dir;
File file;
#else
Dir ac_power_dir;
#endif
char cur_file_name[sizeof (AC_FORMAT_FILE_NAME)];
if (mAcPowerFile) {
mAcPowerFile.close ();
mAcPowerFile = (File)NULL;
}
time_today = gTimezone.toLocal (time_today);
snprintf (cur_file_name, sizeof (cur_file_name), AC_FORMAT_FILE_NAME,
day(time_today), month(time_today), year(time_today));
/* design: no dataserver, cleanup old history */
#ifdef ESP32
if ((ac_power_dir = LittleFS.open (AC_POWER_PATH))) {
while ((file = ac_power_dir.openNextFile())) {
const char *fullName = file.name();
char *name;
if ((name = strrchr (fullName, '/')) && name[1]) {
name++;
} else {
name = (char *)fullName;
}
if (strcmp (name, cur_file_name)) {
char *path = strdup (fullName);
file.close ();
if (path) {
DPRINTLN (DBG_INFO, "Remove file " + String (fullName) +
", Size: " + String (ac_power_dir.size()));
LittleFS.remove (path);
free (path);
}
} else {
file.close();
}
}
ac_power_dir.close();
}
#else
ac_power_dir = LittleFS.openDir (AC_POWER_PATH);
while (ac_power_dir.next()) {
if (ac_power_dir.fileName() != cur_file_name) {
DPRINTLN (DBG_INFO, "Remove file " AC_POWER_PATH "/" + ac_power_dir.fileName() +
", Size: " + String (ac_power_dir.fileSize()));
LittleFS.remove (AC_POWER_PATH "/" + ac_power_dir.fileName());
}
}
#endif
} else {
DPRINTLN (DBG_WARN, "cleanup_history, no time yet");
}
}
//-----------------------------------------------------------------------------
File open_hist ()
{
time_t time_today;
File file = (File) NULL;
char file_name[sizeof (AC_POWER_PATH) + sizeof (AC_FORMAT_FILE_NAME)];
if ((time_today = *mTimestamp)) {
time_today = gTimezone.toLocal (time_today);
snprintf (file_name, sizeof (file_name), AC_POWER_PATH "/" AC_FORMAT_FILE_NAME,
day(time_today), month(time_today), year(time_today));
file = LittleFS.open (file_name, "r");
if (!file) {
DPRINT (DBG_VERBOSE, "open_hist, failed to open "); // typical: during night time after midnight
DBGPRINTLN (file_name);
}
} else {
DPRINTLN (DBG_WARN, "open_history, no time yet");
}
return file;
}
//-----------------------------------------------------------------------------
bool has_pac_value ()
{
if (*mTimestamp) {
INVERTERTYPE *p;
uint16_t i;
for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) {
if (p->config->serial.u64 && p->isConnected && p->pac_cnt) {
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
uint16_t get_pac_average (bool cleanup)
{
uint32_t pac_average = 0;
INVERTERTYPE *p;
uint16_t i;
for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) {
if (p->config->serial.u64 && p->isConnected && p->pac_cnt) {
pac_average += (p->pac_sum + (p->pac_cnt >> 1)) / p->pac_cnt;
}
if (cleanup) {
p->pac_sum = 0;
p->pac_cnt = 0;
}
}
if (pac_average > UINT16_MAX) {
pac_average = UINT16_MAX;
}
return (uint16_t)pac_average;
}
//-----------------------------------------------------------------------------
bool get_cur_value (uint16_t *interval, uint16_t *pac)
{
if (has_pac_value()) {
*interval = cur_pac_index;
*pac = get_pac_average (false);
return true;
}
DPRINTLN (DBG_VERBOSE, "get_cur_value: none");
return false;
}
//-----------------------------------------------------------------------------
void close_hist (File file)
{
if (file) {
file.close ();
}
}
//-----------------------------------------------------------------------------
void handle_pac (INVERTERTYPE *p, uint16_t pac)
{
time_t time_today;
if ((time_today = *mTimestamp)) {
uint32_t pac_index;
time_today = gTimezone.toLocal (time_today);
pac_index = hour(time_today) * 60 + minute(time_today);
pac_index /= AHOY_PAC_INTERVAL;
if (pac_index != cur_pac_index) {
if (has_pac_value ()) {
/* calc sum of all inverter averages for last interval */
/* and cleanup all counts and sums */
uint16_t pac_average = get_pac_average(true);
char file_name[sizeof (AC_POWER_PATH) + sizeof (AC_FORMAT_FILE_NAME)];
snprintf (file_name, sizeof (file_name), AC_POWER_PATH "/" AC_FORMAT_FILE_NAME,
day (time_today), month (time_today), year (time_today));
// append last average
if (mAcPowerFile || (mAcPowerFile = LittleFS.open (file_name, "a"))) {
unsigned char buf[4];
buf[0] = cur_pac_index & 0xff;
buf[1] = cur_pac_index >> 8;
buf[2] = pac_average & 0xff;
buf[3] = pac_average >> 8;
if (mAcPowerFile.write (buf, sizeof (buf)) != sizeof (buf)) {
DPRINTLN (DBG_WARN, "handle_pac, failed_to_write");
} else {
DPRINTLN (DBG_DEBUG, "handle_pac, write to " + String(file_name));
mAcPowerFile.flush ();
}
} else {
DPRINTLN (DBG_WARN, "handle_pac, failed to open");
}
}
cur_pac_index = pac_index;
}
if ((pac_index >= AHOY_MIN_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL) &&
(pac_index < AHOY_MAX_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL)) {
p->pac_sum += pac;
p->pac_cnt++;
} else {
DPRINTLN (DBG_DEBUG, "handle_pac, outside daylight, minutes: " + String (pac_index * AHOY_PAC_INTERVAL));
}
} else {
DPRINTLN (DBG_INFO, "handle_pac, no time2");
}
}
private:
INVERTERTYPE mInverter[MAX_INVERTER];
uint8_t mNumInv;
uint32_t *mTimestamp;
uint16_t cur_pac_index;
File mAcPowerFile;
};
#endif /*__HM_SYSTEM_H__*/