Browse Source

polling several inverter on individual RF channels, decoding in hmDecoder.h

pull/478/head
golfi200 2 years ago
parent
commit
e5afc9ff70
  1. 2
      tools/nano/AhoyUL/.gitignore
  2. 5
      tools/nano/AhoyUL/src/SerialUtils.h
  3. 4
      tools/nano/AhoyUL/src/config.h
  4. 380
      tools/nano/AhoyUL/src/hmDecode.h
  5. 394
      tools/nano/AhoyUL/src/hmDecoder.h
  6. 271
      tools/nano/AhoyUL/src/hmDefines.h
  7. 162
      tools/nano/AhoyUL/src/hmRadio.h
  8. 229
      tools/nano/AhoyUL/src/main.cpp

2
tools/nano/AhoyUL/.gitignore

@ -6,5 +6,5 @@
.log
config_override.h
smac_examples.txt
HmDecoder.*

5
tools/nano/AhoyUL/src/utils_serial.h → tools/nano/AhoyUL/src/SerialUtils.h

@ -8,6 +8,11 @@
#include "config.h"
#include "dbg.h"
/**
* Class for serial functions (read, eval, etc...)
*/
class SerialUtils {
public:
SerialUtils() {

4
tools/nano/AhoyUL/src/config.h

@ -46,7 +46,9 @@
#define SEND_INTERVAL (60) //send interval if Rx OK
#define SEND_NOSIGNAL_SHORT (10) //short send interval if no RX (used initial sync or signal loss)
#define SEND_REPEAT (6) //number of tries of short send interval to sync faster to inverter after night
#define SEND_NOSIGNAL_LONG (20*60) //long TX interval whne no SIGNAL for long time, e.g. over night
#define SEND_NOSIGNAL_LONG (20*60) //long TX interval when no SIGNAL for long time, e.g. over night
#define INVERTER_TIMEOUT_sec (SEND_INTERVAL * 2) //max duration of send to one inverter
// maximum human readable inverter name length
#define MAX_NAME_LENGTH 16

380
tools/nano/AhoyUL/src/hmDecode.h

@ -1,380 +0,0 @@
//
// todo: map hmDefines.h into this hmDecode.h class, no yet used and working
//
#include <Arduino.h>
#include <stdint.h>
#include <stdio.h>
#include "config.h"
#include "dbg.h"
class HmDecode {
public:
HmDecode() {
// some member vars here
}
~HmDecode() {}
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// units
enum { UNIT_V = 0,
UNIT_A,
UNIT_W,
UNIT_WH,
UNIT_KWH,
UNIT_HZ,
UNIT_C,
UNIT_PCT,
UNIT_VAR,
UNIT_NONE };
// const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""};
const char PGM_units[][6] PROGMEM{" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; // one hidden byte needed at the end for '\0'
// field types
enum { FLD_UDC = 0,
FLD_IDC,
FLD_PDC,
FLD_YD,
FLD_YW,
FLD_YT,
FLD_UAC,
FLD_IAC,
FLD_PAC,
FLD_F,
FLD_T,
FLD_PF,
FLD_EFF,
FLD_IRR,
FLD_Q,
FLD_EVT,
FLD_FW_VERSION,
FLD_FW_BUILD_YEAR,
FLD_FW_BUILD_MONTH_DAY,
FLD_FW_BUILD_HOUR_MINUTE,
FLD_HW_ID,
FLD_ACT_ACTIVE_PWR_LIMIT,
/*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE };
// PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem
const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM{
{"U_DC"},
{"I_DC"},
{"P_DC"},
{"YieldDay"},
{"YieldWeek"},
{"YieldTotal"},
{"U_AC"},
{"I_AC"},
{"P_AC"},
{"F_AC"},
{"Temp"},
{"PF_AC"},
{"Efficiency"},
{"Irradiation"},
{"Q_AC"},
{"ALARM_MES_ID"},
{"FWVersion"},
{"FWBuildYear"},
{"FWBuildMonthDay"},
{"FWBuildHourMinute"},
{"HWPartId"},
{"active PowerLimit"},
/* {"reactive PowerLimit"},
{"Powerfactor"}*/
{"LastAlarmCode"},
};
// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
const char* const notAvail = "n/a";
// mqtt discovery device classes
enum { DEVICE_CLS_NONE = 0,
DEVICE_CLS_CURRENT,
DEVICE_CLS_ENERGY,
DEVICE_CLS_PWR,
DEVICE_CLS_VOLTAGE,
DEVICE_CLS_FREQ,
DEVICE_CLS_TEMP };
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
enum { STATE_CLS_NONE = 0,
STATE_CLS_MEASUREMENT,
STATE_CLS_TOTAL_INCREASING };
const char* const stateClasses[] = {0, "measurement", "total_increasing"};
typedef struct {
uint8_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
} byteAssign_fieldDeviceClass;
const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
{FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}};
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass))
// indices to calculation functions, defined in hmInverter.h
enum { CALC_YT_CH0 = 0,
CALC_YD_CH0,
CALC_UDC_CH,
CALC_PDC_CH0,
CALC_EFF_CH0,
CALC_IRR_CH };
enum { CMD_CALC = 0xffff };
// CH0 is default channel (freq, ac, temp)
enum { CH0 = 0,
CH1,
CH2,
CH3,
CH4 };
enum { INV_TYPE_1CH = 0,
INV_TYPE_2CH,
INV_TYPE_4CH };
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 4
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command
} byteAssign_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM-Series
//-------------------------------------
const byteAssign_t InfoAssignment[] = {
{FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1},
{FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1},
{FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1},
{FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1},
{FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1}};
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
#define HMINFO_PAYLOAD_LEN 14
const byteAssign_t SystemConfigParaAssignment[] = {
{FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10} /*,
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
};
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
#define HMSYSTEM_PAYLOAD_LEN 14
const byteAssign_t AlarmDataAssignment[] = {
{FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1}};
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off
//-------------------------------------
// HM300, HM350, HM400
//-------------------------------------
const byteAssign_t hm1chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 6, 2, 10},
{FLD_YD, UNIT_WH, CH1, 12, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 8, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 14, 2, 10},
{FLD_IAC, UNIT_A, CH0, 22, 2, 100},
{FLD_PAC, UNIT_W, CH0, 18, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 20, 2, 10},
{FLD_F, UNIT_HZ, CH0, 16, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 24, 2, 1000},
{FLD_T, UNIT_C, CH0, 26, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 28, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}};
#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t))
#define HM1CH_PAYLOAD_LEN 30
//-------------------------------------
// HM600, HM700, HM800
//-------------------------------------
const byteAssign_t hm2chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 6, 2, 10},
{FLD_YD, UNIT_WH, CH1, 22, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 14, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UDC, UNIT_V, CH2, 8, 2, 10},
{FLD_IDC, UNIT_A, CH2, 10, 2, 100},
{FLD_PDC, UNIT_W, CH2, 12, 2, 10},
{FLD_YD, UNIT_WH, CH2, 24, 2, 1},
{FLD_YT, UNIT_KWH, CH2, 18, 4, 1000},
{FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 26, 2, 10},
{FLD_IAC, UNIT_A, CH0, 34, 2, 100},
{FLD_PAC, UNIT_W, CH0, 30, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 32, 2, 10},
{FLD_F, UNIT_HZ, CH0, 28, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 36, 2, 1000},
{FLD_T, UNIT_C, CH0, 38, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 40, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}
};
#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t))
#define HM2CH_PAYLOAD_LEN 42
//-------------------------------------
// HM1200, HM1500
//-------------------------------------
const byteAssign_t hm4chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 8, 2, 10},
{FLD_YD, UNIT_WH, CH1, 20, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 12, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC},
{FLD_IDC, UNIT_A, CH2, 6, 2, 100},
{FLD_PDC, UNIT_W, CH2, 10, 2, 10},
{FLD_YD, UNIT_WH, CH2, 22, 2, 1},
{FLD_YT, UNIT_KWH, CH2, 16, 4, 1000},
{FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC},
{FLD_UDC, UNIT_V, CH3, 24, 2, 10},
{FLD_IDC, UNIT_A, CH3, 26, 2, 100},
{FLD_PDC, UNIT_W, CH3, 30, 2, 10},
{FLD_YD, UNIT_WH, CH3, 42, 2, 1},
{FLD_YT, UNIT_KWH, CH3, 34, 4, 1000},
{FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC},
{FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC},
{FLD_IDC, UNIT_A, CH4, 28, 2, 100},
{FLD_PDC, UNIT_W, CH4, 32, 2, 10},
{FLD_YD, UNIT_WH, CH4, 44, 2, 1},
{FLD_YT, UNIT_KWH, CH4, 38, 4, 1000},
{FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 46, 2, 10},
{FLD_IAC, UNIT_A, CH0, 54, 2, 100},
{FLD_PAC, UNIT_W, CH0, 50, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 52, 2, 10},
{FLD_F, UNIT_HZ, CH0, 48, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 56, 2, 1000},
{FLD_T, UNIT_C, CH0, 58, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 60, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}};
#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t))
#define HM4CH_PAYLOAD_LEN 62
/**
* simple decoding of 2ch HM-inverter only
*/
static void decode_userPayload(uint8_t _cmd, uint8_t* _user_payload, uint8_t _ulen, uint32_t _ts, char* _strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) {
volatile uint32_t _val = 0L;
#if defined(ESP8266)
// for esp8266 environment
byteAssign_t _bp; // volatile byteAssign_t not compiling ESP8266
#else
volatile byteAssign_t _bp; // must be volatile for Arduino-Nano, otherwise _bp.ch stays zero
#endif
volatile uint8_t _x;
volatile uint8_t _end;
volatile float _fval;
volatile uint8_t _tmp8 = 0xff;
//_str80[80] = '\0';
snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000);
Serial.print(_strout);
if (_cmd == 0x95 and _ulen == 42) {
//!!! simple HM600/700/800 2ch decoding for cmd=0x95 only !!!!
for (_x = 0; _x < HM2CH_LIST_LEN; _x++) {
// read values from given positions in payload
_bp = hm2chAssignment[_x];
_val = 0L;
_end = _bp.start + _bp.num;
if (_tmp8 != _bp.ch) {
snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); // must be a small "l" in %08lX for Arduino-Nano
Serial.print(_strout);
// snprintf_P(_strout, _strlen, PSTR("ch%02d/"), _bp.ch);
// Serial.print(_strout);
}
_tmp8 = _bp.ch;
if (CMD_CALC != _bp.div && _bp.div != 0) {
strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen);
Serial.print(_strout);
Serial.print(F(": "));
do {
_val <<= 8;
_val |= _user_payload[_bp.start];
} while (++_bp.start != _end);
_fval = (int)_val / (float)_bp.div;
if (_bp.unitId == UNIT_NONE) {
Serial.print(_val);
Serial.print(F(" "));
continue;
}
Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does
// Serial.print(units[_bp.unitId]);
strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen);
Serial.print(_strout);
} else {
// do calculations
// Serial.print(F("not yet"));
}
} // end for()
Serial.println();
} else {
Serial.print(F("NO DECODER "));
Serial.print(_cmd, HEX);
}
} // end decodePayload()
private:
};

394
tools/nano/AhoyUL/src/hmDecoder.h

@ -0,0 +1,394 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
// 2022 mb using PROGMEM for lage char* arrays, used and tested for HM800 decoding (2channel), no calculation yet
// - strip down of hmDefines.h and rename to hmDecode.h
// todo: make a decoding class out of this
#ifndef __HM_DEFINES_H__
#define __HM_DEFINES_H__
#include "dbg.h"
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// units
enum { UNIT_V = 0,
UNIT_A,
UNIT_W,
UNIT_WH,
UNIT_KWH,
UNIT_HZ,
UNIT_C,
UNIT_PCT,
UNIT_VAR,
UNIT_NONE };
// const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""};
const char PGM_units[][6] PROGMEM = {" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; // one hidden byte needed at the end for '\0'
// field types
enum { FLD_UDC = 0,
FLD_IDC,
FLD_PDC,
FLD_YD,
FLD_YW,
FLD_YT,
FLD_UAC,
FLD_IAC,
FLD_PAC,
FLD_F,
FLD_T,
FLD_PF,
FLD_EFF,
FLD_IRR,
FLD_Q,
FLD_EVT,
FLD_FW_VERSION,
FLD_FW_BUILD_YEAR,
FLD_FW_BUILD_MONTH_DAY,
FLD_FW_BUILD_HOUR_MINUTE,
FLD_HW_ID,
FLD_ACT_ACTIVE_PWR_LIMIT,
/*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE };
// PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem
const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM = {
{"U_DC"},
{"I_DC"},
{"P_DC"},
{"YieldDay"},
{"YieldWeek"},
{"YieldTotal"},
{"U_AC"},
{"I_AC"},
{"P_AC"},
{"F_AC"},
{"Temp"},
{"PF_AC"},
{"Efficiency"},
{"Irradiation"},
{"Q_AC"},
{"ALARM_MES_ID"},
{"FWVersion"},
{"FWBuildYear"},
{"FWBuildMonthDay"},
{"FWBuildHourMinute"},
{"HWPartId"},
{"active PowerLimit"},
/* {"reactive PowerLimit"},
{"Powerfactor"}*/
{"LastAlarmCode"},
};
const char PGM_invname[][8] PROGMEM = { "HM400", "HM600", "HM800", "HM1200", "HM1500"};
// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
const char* const notAvail = "n/a";
// mqtt discovery device classes
enum { DEVICE_CLS_NONE = 0,
DEVICE_CLS_CURRENT,
DEVICE_CLS_ENERGY,
DEVICE_CLS_PWR,
DEVICE_CLS_VOLTAGE,
DEVICE_CLS_FREQ,
DEVICE_CLS_TEMP };
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
enum { STATE_CLS_NONE = 0,
STATE_CLS_MEASUREMENT,
STATE_CLS_TOTAL_INCREASING };
const char* const stateClasses[] = {0, "measurement", "total_increasing"};
typedef struct {
uint8_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
} byteAssign_fieldDeviceClass;
const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
{FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}};
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass))
// indices to calculation functions, defined in hmInverter.h
enum { CALC_YT_CH0 = 0,
CALC_YD_CH0,
CALC_UDC_CH,
CALC_PDC_CH0,
CALC_EFF_CH0,
CALC_IRR_CH };
enum { CMD_CALC = 0xffff };
// CH0 is default channel (freq, ac, temp)
enum { CH0 = 0,
CH1,
CH2,
CH3,
CH4 };
enum { INV_TYPE_1CH = 0,
INV_TYPE_2CH,
INV_TYPE_4CH };
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 4
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command
} byteAssign_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM-Series
//-------------------------------------
const byteAssign_t InfoAssignment[] = {
{FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1},
{FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1},
{FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1},
{FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1},
{FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1}};
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
#define HMINFO_PAYLOAD_LEN 14
const byteAssign_t SystemConfigParaAssignment[] = {
{FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10} /*,
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
};
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
#define HMSYSTEM_PAYLOAD_LEN 14
const byteAssign_t AlarmDataAssignment[] = {
{FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1}};
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off
//-------------------------------------
// HM300, HM350, HM400
//-------------------------------------
const byteAssign_t hm1chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 6, 2, 10},
{FLD_YD, UNIT_WH, CH1, 12, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 8, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 14, 2, 10},
{FLD_IAC, UNIT_A, CH0, 22, 2, 100},
{FLD_PAC, UNIT_W, CH0, 18, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 20, 2, 10},
{FLD_F, UNIT_HZ, CH0, 16, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 24, 2, 1000},
{FLD_T, UNIT_C, CH0, 26, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 28, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}};
#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t))
#define HM1CH_PAYLOAD_LEN 30
//-------------------------------------
// HM600, HM700, HM800
//-------------------------------------
const byteAssign_t hm2chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 6, 2, 10},
{FLD_YD, UNIT_WH, CH1, 22, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 14, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UDC, UNIT_V, CH2, 8, 2, 10},
{FLD_IDC, UNIT_A, CH2, 10, 2, 100},
{FLD_PDC, UNIT_W, CH2, 12, 2, 10},
{FLD_YD, UNIT_WH, CH2, 24, 2, 1},
{FLD_YT, UNIT_KWH, CH2, 18, 4, 1000},
{FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 26, 2, 10},
{FLD_IAC, UNIT_A, CH0, 34, 2, 100},
{FLD_PAC, UNIT_W, CH0, 30, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 32, 2, 10},
{FLD_F, UNIT_HZ, CH0, 28, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 36, 2, 1000},
{FLD_T, UNIT_C, CH0, 38, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 40, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}
};
#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t))
#define HM2CH_PAYLOAD_LEN 42
//-------------------------------------
// HM1200, HM1500
//-------------------------------------
const byteAssign_t hm4chAssignment[] = {
{FLD_UDC, UNIT_V, CH1, 2, 2, 10},
{FLD_IDC, UNIT_A, CH1, 4, 2, 100},
{FLD_PDC, UNIT_W, CH1, 8, 2, 10},
{FLD_YD, UNIT_WH, CH1, 20, 2, 1},
{FLD_YT, UNIT_KWH, CH1, 12, 4, 1000},
{FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC},
{FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC},
{FLD_IDC, UNIT_A, CH2, 6, 2, 100},
{FLD_PDC, UNIT_W, CH2, 10, 2, 10},
{FLD_YD, UNIT_WH, CH2, 22, 2, 1},
{FLD_YT, UNIT_KWH, CH2, 16, 4, 1000},
{FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC},
{FLD_UDC, UNIT_V, CH3, 24, 2, 10},
{FLD_IDC, UNIT_A, CH3, 26, 2, 100},
{FLD_PDC, UNIT_W, CH3, 30, 2, 10},
{FLD_YD, UNIT_WH, CH3, 42, 2, 1},
{FLD_YT, UNIT_KWH, CH3, 34, 4, 1000},
{FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC},
{FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC},
{FLD_IDC, UNIT_A, CH4, 28, 2, 100},
{FLD_PDC, UNIT_W, CH4, 32, 2, 10},
{FLD_YD, UNIT_WH, CH4, 44, 2, 1},
{FLD_YT, UNIT_KWH, CH4, 38, 4, 1000},
{FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC},
{FLD_UAC, UNIT_V, CH0, 46, 2, 10},
{FLD_IAC, UNIT_A, CH0, 54, 2, 100},
{FLD_PAC, UNIT_W, CH0, 50, 2, 10},
{FLD_Q, UNIT_VAR, CH0, 52, 2, 10},
{FLD_F, UNIT_HZ, CH0, 48, 2, 100},
{FLD_PF, UNIT_NONE, CH0, 56, 2, 1000},
{FLD_T, UNIT_C, CH0, 58, 2, 10},
{FLD_EVT, UNIT_NONE, CH0, 60, 2, 1},
{FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC},
{FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC},
{FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC},
{FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC}};
#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t))
#define HM4CH_PAYLOAD_LEN 62
/**
* simple decoding of 1,2,4ch HM-inverter only
*/
static void decode_InfoREQResp(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, uint32_t _ts, char *_strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) {
volatile int32_t _val = 0L;
#if defined(ESP8266)
// for esp8266 environment
byteAssign_t _bp; //volatile byteAssign_t not compiling ESP8266
#else
volatile byteAssign_t _bp; //must be volatile for Arduino-Nano, otherwise _bp.ch stays zero
#endif
volatile uint8_t _x;
volatile uint8_t _end;
volatile float _fval;
volatile uint8_t _tmp8 = 0xff;
volatile uint8_t LIST_LEN = 0;
volatile uint8_t ixn = 0;
//_str80[80] = '\0';
snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000);
Serial.print(_strout);
if (_cmd == 0x95) {
//check given payload len
if(_ulen == HM2CH_PAYLOAD_LEN) {
ixn = 2;
LIST_LEN = HM2CH_LIST_LEN;
} else if (_ulen == HM4CH_PAYLOAD_LEN) {
ixn = 4;
LIST_LEN = HM4CH_LIST_LEN;
} else if (_ulen == HM1CH_PAYLOAD_LEN) {
ixn = 0;
LIST_LEN = HM1CH_LIST_LEN;
} else {
Serial.print(F("ERR Wrong PL_LEN"));
return;
}
for (_x = 0; _x < LIST_LEN; _x++) {
// read values from given positions in payload
_bp = hm2chAssignment[_x];
_val = 0L;
_end = _bp.start + _bp.num;
if (_tmp8 != _bp.ch) {
//snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano
snprintf_P(_strout, _strlen, PSTR("\n%S/%04X%08lX/ch%02d "), PGM_invname[ixn],_invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano, https://forum.arduino.cc/t/f-macro-and-sprintf/894536/10
Serial.print(_strout);
}
_tmp8 = _bp.ch;
if (CMD_CALC != _bp.div && _bp.div != 0) {
strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen);
Serial.print(_strout);
Serial.print(F(": "));
do {
_val <<= 8;
_val |= _user_payload[_bp.start];
} while (++_bp.start != _end);
if (_bp.num > 2) {
_fval = (int32_t)_val / (float)_bp.div; //must be int32 to handle 4bit value of yield total
} else {
_fval = (int16_t)_val / (float)_bp.div; //for negative temparture value it must be int16
}
if (_bp.unitId == UNIT_NONE) {
Serial.print(_val);
Serial.print(F(" "));
continue;
}
Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does
// Serial.print(units[_bp.unitId]);
strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen);
Serial.print(_strout);
} else {
// do calculations
// Serial.print(F("not yet"));
}
} // end for()
Serial.println();
} else {
Serial.print(F("NO DECODER "));
Serial.print(_cmd, HEX);
}
}//end decodePayload()
#endif /*__HM_DEFINES_H__*/

271
tools/nano/AhoyUL/src/hmDefines.h

@ -1,271 +0,0 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
//2022 mb using PROGMEM for lage char* arrays, used and tested for HM800 decoding (2channel), no calculation yet
//todo: make a decoding class out of this
#ifndef __HM_DEFINES_H__
#define __HM_DEFINES_H__
#include "dbg.h"
union serial_u {
uint64_t u64;
uint8_t b[8];
};
// units
enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE};
//const char* const units[] = {" V", " A", " W", " Wh", " kWh", " Hz", " °C", " %", " var", ""};
const char PGM_units[][6] PROGMEM {" V ", " A ", " W ", " Wh ", " kWh ", " Hz ", " °C ", " % ", " var ", " "}; //one hidden byte needed at the end for '\0'
// field types
enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT,
FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF,
FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR,
FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID,
FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE};
//PGM Flash memory usage instead of RAM for ARDUINO NANO, idea given by Nick Gammon's great webpage http://gammon.com.au/progmem
const char PGM_fields[/*NUM ELEMENTS*/][25] PROGMEM {
{"U_DC"},
{"I_DC"},
{"P_DC"},
{"YieldDay"},
{"YieldWeek"},
{"YieldTotal"},
{"U_AC"},
{"I_AC"},
{"P_AC"},
{"F_AC"},
{"Temp"},
{"PF_AC"},
{"Efficiency"},
{"Irradiation"},
{"Q_AC"},
{"ALARM_MES_ID"},
{"FWVersion"},
{"FWBuildYear"},
{"FWBuildMonthDay"},
{"FWBuildHourMinute"},
{"HWPartId"},
{"active PowerLimit"},
/* {"reactive PowerLimit"},
{"Powerfactor"}*/
{"LastAlarmCode"},
};
// const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal",
// "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC",
// "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId",
// "active PowerLimit", /*"reactive PowerLimit","Powerfactor",*/ "LastAlarmCode"};
const char* const notAvail = "n/a";
// mqtt discovery device classes
enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP};
const char* const deviceClasses[] = {0, "current", "energy", "power", "voltage", "frequency", "temperature"};
enum {STATE_CLS_NONE = 0, STATE_CLS_MEASUREMENT, STATE_CLS_TOTAL_INCREASING};
const char* const stateClasses[] = {0, "measurement", "total_increasing"};
typedef struct {
uint8_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
} byteAssign_fieldDeviceClass;
const byteAssign_fieldDeviceClass deviceFieldAssignment[] = {
{FLD_UDC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IDC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PDC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_YD, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YW, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_YT, DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING},
{FLD_UAC, DEVICE_CLS_VOLTAGE, STATE_CLS_MEASUREMENT},
{FLD_IAC, DEVICE_CLS_CURRENT, STATE_CLS_MEASUREMENT},
{FLD_PAC, DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT},
{FLD_F, DEVICE_CLS_FREQ, STATE_CLS_NONE},
{FLD_T, DEVICE_CLS_TEMP, STATE_CLS_MEASUREMENT},
{FLD_PF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_EFF, DEVICE_CLS_NONE, STATE_CLS_NONE},
{FLD_IRR, DEVICE_CLS_NONE, STATE_CLS_NONE}
};
#define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass))
// indices to calculation functions, defined in hmInverter.h
enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH};
enum {CMD_CALC = 0xffff};
// CH0 is default channel (freq, ac, temp)
enum {CH0 = 0, CH1, CH2, CH3, CH4};
enum {INV_TYPE_1CH = 0, INV_TYPE_2CH, INV_TYPE_4CH};
typedef struct {
uint8_t fieldId; // field id
uint8_t unitId; // uint id
uint8_t ch; // channel 0 - 4
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command
} byteAssign_t;
/**
* indices are built for the buffer starting with cmd-id in first byte
* (complete payload in buffer)
* */
//-------------------------------------
// HM-Series
//-------------------------------------
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
};
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
#define HMINFO_PAYLOAD_LEN 14
const byteAssign_t SystemConfigParaAssignment[] = {
{ FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*,
{ FLD_ACT_REACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 4, 2, 10 },
{ FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
};
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
#define HMSYSTEM_PAYLOAD_LEN 14
const byteAssign_t AlarmDataAssignment[] = {
{ FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 }
};
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t))
#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off
//-------------------------------------
// HM300, HM350, HM400
//-------------------------------------
const byteAssign_t hm1chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 12, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 14, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 22, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 18, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 20, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 16, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 24, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 26, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 28, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t))
#define HM1CH_PAYLOAD_LEN 30
//-------------------------------------
// HM600, HM700, HM800
//-------------------------------------
const byteAssign_t hm2chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 6, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 22, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UDC, UNIT_V, CH2, 8, 2, 10 },
{ FLD_IDC, UNIT_A, CH2, 10, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, 12, 2, 10 },
{ FLD_YD, UNIT_WH, CH2, 24, 2, 1 },
{ FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 26, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 34, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 30, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 32, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 28, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 36, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 38, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 40, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t))
#define HM2CH_PAYLOAD_LEN 42
//-------------------------------------
// HM1200, HM1500
//-------------------------------------
const byteAssign_t hm4chAssignment[] = {
{ FLD_UDC, UNIT_V, CH1, 2, 2, 10 },
{ FLD_IDC, UNIT_A, CH1, 4, 2, 100 },
{ FLD_PDC, UNIT_W, CH1, 8, 2, 10 },
{ FLD_YD, UNIT_WH, CH1, 20, 2, 1 },
{ FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC },
{ FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC },
{ FLD_IDC, UNIT_A, CH2, 6, 2, 100 },
{ FLD_PDC, UNIT_W, CH2, 10, 2, 10 },
{ FLD_YD, UNIT_WH, CH2, 22, 2, 1 },
{ FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC },
{ FLD_UDC, UNIT_V, CH3, 24, 2, 10 },
{ FLD_IDC, UNIT_A, CH3, 26, 2, 100 },
{ FLD_PDC, UNIT_W, CH3, 30, 2, 10 },
{ FLD_YD, UNIT_WH, CH3, 42, 2, 1 },
{ FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC },
{ FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC },
{ FLD_IDC, UNIT_A, CH4, 28, 2, 100 },
{ FLD_PDC, UNIT_W, CH4, 32, 2, 10 },
{ FLD_YD, UNIT_WH, CH4, 44, 2, 1 },
{ FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 },
{ FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC },
{ FLD_UAC, UNIT_V, CH0, 46, 2, 10 },
{ FLD_IAC, UNIT_A, CH0, 54, 2, 100 },
{ FLD_PAC, UNIT_W, CH0, 50, 2, 10 },
{ FLD_Q, UNIT_VAR, CH0, 52, 2, 10 },
{ FLD_F, UNIT_HZ, CH0, 48, 2, 100 },
{ FLD_PF, UNIT_NONE, CH0, 56, 2, 1000 },
{ FLD_T, UNIT_C, CH0, 58, 2, 10 },
{ FLD_EVT, UNIT_NONE, CH0, 60, 2, 1 },
{ FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC },
{ FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
{ FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }
};
#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t))
#define HM4CH_PAYLOAD_LEN 62
#endif /*__HM_DEFINES_H__*/

162
tools/nano/AhoyUL/src/hmRadio.h

@ -6,6 +6,7 @@
// 2022 mb modified for AHOY-UL (Hoymiles Arduino Nano, USB light IF)
// - RF handling and function sendPacket_raw() without automatic channel increment
// - loop() saves new packet fragments only to save space
// 2023 mb: add parameter inverter-ix to each send-function, to be able to handle the Tx/Rx channel per inverter, otherwise sync needed after each inv switch
#ifndef __RADIO_H__
#define __RADIO_H__
@ -61,10 +62,11 @@ template <uint8_t CE_PIN, uint8_t CS_PIN, class BUFFER, uint64_t DTU_ID = DTU_RA
class HmRadio {
uint8_t mTxCh;
uint8_t mRxCh; // only dummy use
uint8_t mTxChIdx;
uint8_t mRfChLst[RF_CHANNELS];
uint8_t mRxChIdx;
uint8_t mTxChIdx[MAX_NUM_INVERTERS];
uint8_t mRxChIdx[MAX_NUM_INVERTERS];
uint16_t mRxLoopCnt;
uint8_t mInvIx = 0;
RF24 mNrf24;
// RF24 mNrf24(CE_PIN, CS_PIN, SPI_SPEED);
@ -88,8 +90,11 @@ class HmRadio {
mRfChLst[3] = 61;
mRfChLst[4] = 75;
mTxChIdx = 2;
mRxChIdx = 4;
// set default channel-idx per inverter-ix, only keep the index per inverter, the real channel exits only once
for (uint8_t ix = 0; ix < MAX_NUM_INVERTERS; ix++) {
mTxChIdx[ix] = 2;
mRxChIdx[ix] = 4;
}
mRxLoopCnt = RF_LOOP_CNT;
mSendCnt = 0;
@ -137,8 +142,9 @@ class HmRadio {
mNrf24.printPrettyDetails();
}
mTxCh = setDefaultChannels();
mRxCh = 0; // only dummy
//index to rf-channel
mTxCh = mRfChLst[mTxChIdx[0]];
mRxCh = mRfChLst[mRxChIdx[0]]; // only dummy
if (!mNrf24.isChipConnected()) {
DPRINT(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
@ -166,7 +172,7 @@ class HmRadio {
if (!mBufCtrl->full()) {
_status = true;
p = mBufCtrl->getFront(); // init pointer address with circular buffer space
p->rfch = mRxCh;
p->rfch = mRxCh; // save the rx-channel in packet-struct
p->plen = mNrf24.getPayloadSize();
// p->plen = mNrf24.getDynamicPayloadSize(); //is not the real RF payload fragment (packet) length, calculate later
if (p->plen < 0) {
@ -209,16 +215,16 @@ class HmRadio {
mIrqRcvd = true;
}
uint8_t setDefaultChannels(void) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels"));
mTxChIdx = 2; // Start TX with 40
mRxChIdx = 4; // Start RX with 75
return mRfChLst[mTxChIdx];
}
// uint8_t setDefaultChannels(void) {
// // DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels"));
// mTxChIdx[] = 2; // Start TX with 40
// mRxChIdx = 4; // Start RX with 75
// return mRfChLst[mTxChIdx];
// }
void sendControlPacket(uint8_t *_radio_id, uint8_t cmd, uint16_t *data) {
void sendControlPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t cmd, uint16_t *data) {
//DPRINTLN(DBG_INFO, F("hmRadio:sendControlPacket"));
sendCmdPacket(_radio_id, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code
sendCmdPacket(_radio_id, _inv_ix, TX_REQ_DEVCONTROL, ALL_FRAMES, false); // 0x80 implementation as original DTU code
int cnt = 0;
mTxBuf[10] = cmd; // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor
mTxBuf[10 + (++cnt)] = 0x00;
@ -236,12 +242,12 @@ class HmRadio {
cnt += 1;
mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt);
sendPacket(_radio_id, mTxBuf, 10 + (++cnt), true);
sendPacket(_radio_id, _inv_ix, mTxBuf, 10 + (++cnt), true);
}
void sendTimePacket(uint8_t *_radio_id, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) {
void sendTimePacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t cmd, uint32_t ts, uint16_t alarmMesId) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendTimePacket"));
sendCmdPacket(_radio_id, TX_REQ_INFO, ALL_FRAMES, false);
sendCmdPacket(_radio_id, _inv_ix, TX_REQ_INFO, ALL_FRAMES, false);
mTxBuf[10] = cmd; // cid
mTxBuf[11] = 0x00;
CP_U32_LittleEndian(&mTxBuf[12], ts); //?? adapt for atmega/nano
@ -259,10 +265,10 @@ class HmRadio {
mTxBuf[25] = (crc)&0xff;
mTxBuf[26] = Hoymiles::crc8(mTxBuf, 26);
sendPacket(_radio_id, mTxBuf, 27, true);
sendPacket(_radio_id, _inv_ix, mTxBuf, 27, true);
}
void sendCmdPacket(uint8_t *_radio_id, uint8_t mid, uint8_t pid, bool calcCrc = true) {
void sendCmdPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t mid, uint8_t pid, bool calcCrc = true) {
// DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendCmdPacket"));
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
mTxBuf[0] = mid; // message id
@ -272,7 +278,7 @@ class HmRadio {
mTxBuf[9] = pid;
if (calcCrc) {
mTxBuf[10] = Hoymiles::crc8(mTxBuf, 10);
sendPacket(_radio_id, mTxBuf, 11, false);
sendPacket(_radio_id, _inv_ix, mTxBuf, 11, false);
}
}
@ -303,21 +309,21 @@ class HmRadio {
return valid;
}
bool switchRxCh(uint16_t addLoop = 0) {
DPRINT(DBG_DEBUG, F("hmRadio.h:switchRxCh: try"));
// DPRINTLN(DBG_VERBOSE, F("R"));
// bool switchRxCh(uint16_t addLoop = 0) {
// DPRINT(DBG_DEBUG, F("hmRadio.h:switchRxCh: try"));
// // DPRINTLN(DBG_VERBOSE, F("R"));
mRxLoopCnt += addLoop;
if (mRxLoopCnt != 0) {
mRxLoopCnt--;
DISABLE_IRQ;
mNrf24.stopListening();
mNrf24.setChannel(getRxNxtChannel());
mNrf24.startListening();
RESTORE_IRQ;
}
return (0 == mRxLoopCnt); // receive finished
}
// mRxLoopCnt += addLoop;
// if (mRxLoopCnt != 0) {
// mRxLoopCnt--;
// DISABLE_IRQ;
// mNrf24.stopListening();
// mNrf24.setChannel(getRxNxtChannel());
// mNrf24.startListening();
// RESTORE_IRQ;
// }
// return (0 == mRxLoopCnt); // receive finished
// }
/**
* Hex string output of uint8_t array
@ -412,12 +418,12 @@ class HmRadio {
uint8_t ix;
ix = getChanIdx(_txch);
ix = (ix + 2) % RF_CHANNELS;
return mRfChLst[mRxChIdx];
return mRfChLst[ix];
}
uint8_t getRxChan() {
return mRfChLst[mRxChIdx];
}
// uint8_t getRxChan() {
// return mRfChLst[mRxChIdx];
// }
uint8_t getChanIdx(uint8_t _channel) {
// uses global channel list
@ -428,8 +434,9 @@ class HmRadio {
return ix;
}
//saves the RxChIdx for inverter-ix zero
void setRxChanIdx(uint8_t ix) {
mRxChIdx = ix;
mRxChIdx[0] = ix;
}
void print_radio_details() {
@ -456,18 +463,18 @@ class HmRadio {
/**
* That is the actual send function
*/
bool sendPacket(uint8_t *_radio_id, uint8_t buf[], uint8_t len, bool clear = false, bool doSend = true) {
bool sendPacket(uint8_t *_radio_id, uint8_t _inv_ix, uint8_t buf[], uint8_t len, bool clear = false, bool doSend = true) {
// DPRINT(DBG_DEBUG, F("hmRadio.h:sendPacket"));
//bool _tx_ok = 0; // could also query the num of tx retries as uplink quality indication, no lib interface seen yet
uint8_t _arc = 0;
// DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt));
// dumpBuf("SEN ", buf, len);
mTxCh = mRfChLst[mTxChIdx[_inv_ix]];
if (mSerialDebug) {
DPRINT(DBG_INFO, F("TX Ch"));
if (mRfChLst[mTxChIdx] < 10) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, mRfChLst[mTxChIdx]);
if (mTxCh < 10) _DPRINT(DBG_INFO, F("0"));
_DPRINT(DBG_INFO, mTxCh);
_DPRINT(DBG_INFO, F(" "));
_DPRINT(DBG_INFO, len);
_DPRINT(DBG_INFO, F("B | "));
@ -484,9 +491,8 @@ class HmRadio {
mNrf24.stopListening();
if (clear)
mRxLoopCnt = RF_LOOP_CNT;
mNrf24.setChannel(mRfChLst[mTxChIdx]);
mTxCh = getTxNxtChannel(); // prepare Tx channel for next packet
mNrf24.setChannel(mTxCh); // set Tx channel in nRF24
// mTxCh = getTxNxtChannel(_inv_ix); // prepare Tx channel for next packet
// mNrf24.openWritingPipe(invId); // TODO: deprecated, !!!! invID must be given in big endian uint8_t*
mNrf24.openWritingPipe(_radio_id);
mNrf24.setCRCLength(RF24_CRC_16);
@ -495,12 +501,12 @@ class HmRadio {
mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms, I guess Hoymiles has disabled autoack, thus always max loops send
if (doSend) mNrf24.write(buf, len); // only send in case of send-flag true, _tx_ok seems to be always false
//_arc = mNrf24.getARC(); // is always 15, hoymiles receiver might have autoack=false
// Try to avoid zero payload acks (has no effect)
mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated
// mRxChIdx = 0;
mNrf24.setChannel(mRfChLst[mRxChIdx]); // switch to Rx channel that matches to Tx channel
mRxCh = mRfChLst[mRxChIdx];
// prepare Rx
mRxCh = mRfChLst[mRxChIdx[_inv_ix]]; // switch to Rx channel that matches to last Tx channel
mNrf24.setChannel(mRxCh);
mNrf24.setAutoAck(false);
mNrf24.setRetries(0, 0);
mNrf24.disableDynamicPayloads();
@ -510,42 +516,46 @@ class HmRadio {
if (mSerialDebug) {
//_DPRINT(DBG_INFO, F(" ->ARC ")); _DPRINT(DBG_INFO, _arc);
DPRINT(DBG_VERBOSE, "RX Ch");
if (mRfChLst[mRxChIdx] < 10) _DPRINT(DBG_VERBOSE, F("0"));
_DPRINT(DBG_VERBOSE, mRfChLst[mRxChIdx]);
if (mRxCh < 10) _DPRINT(DBG_VERBOSE, F("0"));
_DPRINT(DBG_VERBOSE, mRxCh);
_DPRINT(DBG_VERBOSE, F(" wait"));
getRxNxtChannel(); // prepare Rx channel for next packet
//getRxNxtChannel(); // prepare Rx channel for next packet
}
incr_mTxRxChIdx(_inv_ix);
mSendCnt++;
return true;
}
uint8_t getTxNxtChannel(void) {
if (++mTxChIdx >= RF_CHANNELS)
mTxChIdx = 0;
// DPRINT(DBG_DEBUG, F("next TX Ch"));
//_DPRINT(DBG_DEBUG, mRfChLst[mTxChIdx]);
return mRfChLst[mTxChIdx];
void incr_mTxRxChIdx(uint8_t _inv_ix) {
if (++mTxChIdx[_inv_ix] >= RF_CHANNELS)
mTxChIdx[_inv_ix] = 0;
if (++mRxChIdx[_inv_ix] >= RF_CHANNELS)
mRxChIdx[_inv_ix] = 0;
return;
}
uint8_t getRxNxtChannel(void) {
if (++mRxChIdx >= RF_CHANNELS)
mRxChIdx = 0;
// PRINT(DBG_DEBUG, F("next RX Ch"));
//_DPRINT(DBG_DEBUG, mRfChLst[mRxChIdx]);
return mRfChLst[mRxChIdx];
}
void setChanIdx(uint8_t _txch) {
mTxChIdx = getChanIdx(_txch);
mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS;
}
// uint8_t getTxNxtChannel(void) {
// if (++mTxChIdx >= RF_CHANNELS)
// mTxChIdx = 0;
// // DPRINT(DBG_DEBUG, F("next TX Ch"));
// //_DPRINT(DBG_DEBUG, mRfChLst[mTxChIdx]);
// return mRfChLst[mTxChIdx];
// }
uint8_t getRxChannel(void) {
return mRxCh;
}
// uint8_t getRxNxtChannel(void) {
// if (++mRxChIdx >= RF_CHANNELS)
// mRxChIdx = 0;
// // PRINT(DBG_DEBUG, F("next RX Ch"));
// //_DPRINT(DBG_DEBUG, mRfChLst[mRxChIdx]);
// return mRfChLst[mRxChIdx];
// }
// void setChanIdx(uint8_t _txch) {
// mTxChIdx = getChanIdx(_txch);
// mRxChIdx = (mTxChIdx + 2) % RF_CHANNELS;
// }
}; // end class

229
tools/nano/AhoyUL/src/main.cpp

@ -24,9 +24,10 @@
#include "config.h"
#include "dbg.h"
#include "defines.h"
#include "hmDefines.h"
// #include "hmDefines.h"
#include "SerialUtils.h"
#include "hmDecoder.h"
#include "hmRadio.h"
#include "utils_serial.h"
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType;
typedef HmRadio<DEF_RF24_CE_PIN, DEF_RF24_CS_PIN, BufferType> RadioType;
@ -36,7 +37,7 @@ typedef HmRadio<DEF_RF24_CE_PIN, DEF_RF24_CS_PIN, BufferType> RadioType;
// declaration of functions
static void resetSystem(void);
static void loadDefaultConfig(config_t *);
void mSwitchCasesSer(char);
void mHandleCases_of_SerialInput(char);
// utility function
static int availableMemory(void);
static void swap_bytes(uint8_t *, uint32_t);
@ -55,7 +56,7 @@ static bool resetPayload(invPayload_t *);
static bool out_uart_smac_resp_OK(invPayload_t *);
static bool out_uart_smac_resp_ERR(invPayload_t *);
static uint8_t collect_and_return_userPayload(invPayload_t *, uint8_t *, uint8_t);
static void decode_userPayload(uint8_t, uint8_t *, uint8_t, uint32_t, char *, uint8_t, uint16_t, uint32_t);
static void decode_InfoREQResp(uint8_t, uint8_t *, uint8_t, uint32_t, char *, uint8_t, uint16_t, uint32_t); // in file hmDecoder.h, class did not work with PROGMEM
// interrupt handler
static void handleIntr(void);
@ -86,7 +87,9 @@ static RadioType hmRadio;
// static uint8_t radio_id[5]; //todo: use the mPayload[].id field ,this defines the radio-id (domain) of the rf24 transmission, will be derived from inverter id
// static uint64_t radio_id64 = 0ULL;
#define DEF_VERSION "\n version 2023-01-27 22:42"
// static HmDecoder Decoder;
#define DEF_VERSION "\n version 2023-03-25 23:35"
#define P(x) (__FlashStringHelper *)(x) // PROGMEM-Makro for variables
static const char COMPILE_DATE[] PROGMEM = {__DATE__};
static const char COMPILE_TIME[] PROGMEM = {__TIME__};
@ -108,9 +111,10 @@ static uint8_t rxch; // keeps the current RX channel
// volatile static uint32_t current_millis = 0;
static volatile uint32_t timer1_millis = 0L; // general loop timer
static volatile uint32_t timer2_millis = 0L; // send Request timer
static volatile uint32_t lastRx_millis = 0L; // last valid Rx packet fragment timer
static volatile uint32_t timer2_millis[MAX_NUM_INVERTERS]; // send Request timer
static volatile uint32_t lastRx_millis[MAX_NUM_INVERTERS]; // last valid Rx packet fragment timer
static volatile uint32_t tcmd_millis = 0L; // timer for smac-cmd start
static volatile uint32_t lastInvChange_millis = 0L; // timer for last inverter change
#define ONE_SECOND (1000L)
static uint8_t c_loop = 0;
@ -129,9 +133,10 @@ static uint8_t tmp8 = 0;
static uint8_t tmp81 = 0;
static uint32_t tmp32 = 0;
// static uint32_t min_SEND_SYNC_msec = MIN_SEND_INTERVAL_ms;
static uint8_t mCountdown_noSignal = SEND_REPEAT; // counter down of number of Inverter Requests if no response received, in dark or no signal
static volatile uint32_t mSendInterval_ms = SEND_NOSIGNAL_SHORT * ONE_SECOND;
static volatile uint32_t polling_req_msec = SEND_INTERVAL * ONE_SECOND; // will be set via serial interface
static uint8_t mCountdown_noSignal[MAX_NUM_INVERTERS]; // counter down of number of Inverter Requests if no response received, in dark or no signal
static volatile uint32_t mSendInterval_ms[MAX_NUM_INVERTERS];
static volatile uint32_t polling_req_msec[MAX_NUM_INVERTERS]; // will be set via serial interface
static volatile boolean m_next_invIX = false; //after successful receiving of one inverter switch to the next
static bool iv_devControlReq = false; // this is one kind of requests, see devControlCmds
static uint8_t iv_devControlCmd = ActivePowerContr; // for now the only devControlCmd
@ -177,6 +182,13 @@ void setup() {
swap_bytes(&mPayload[0].invId[1], (uint32_t)IV1_RADIO_ID); // high byte is at lowest index
mPayload[0].invType = (uint16_t)(IV1_RADIO_ID >> 32); // keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id for type
//2nd inverter
#if defined(IV2_RADIO_ID)
mPayload[1].invId[0] = (uint8_t)0x01;
swap_bytes(&mPayload[1].invId[1], (uint32_t)IV2_RADIO_ID); // high byte is at lowest index
mPayload[1].invType = (uint16_t)(IV2_RADIO_ID >> 32); // keep just upper 6 and 5th byte (e.g.0x1141) of interter plate id for type
#endif
// hmRadio.dumpBuf("\nsetup InvID ", &mPayload[0].invId[0], 5, DBG_DEBUG);
// alternativly radio-id
@ -184,6 +196,14 @@ void setup() {
// todo: load Inverter decoder depending on InvID
//some variables per inverter index
for (m_inv_ix = 0; m_inv_ix < MAX_NUM_INVERTERS; m_inv_ix++) {
timer2_millis[m_inv_ix] = 0L;
lastRx_millis[m_inv_ix] = 0L;
mCountdown_noSignal[m_inv_ix] = SEND_REPEAT;
mSendInterval_ms[m_inv_ix] = SEND_NOSIGNAL_SHORT * ONE_SECOND;
polling_req_msec[m_inv_ix] = SEND_INTERVAL * ONE_SECOND;
}
m_inv_ix = 0; // first inverter index
// global var for all inverter
@ -202,6 +222,8 @@ void setup() {
void loop() {
// the inverter id/ix I shall be handled in a loop if more than one inverter is registered for periodic polling
// counting the time reference of seconds
if (millis() - timer1_millis >= ONE_SECOND) {
timer1_millis = millis();
@ -220,41 +242,64 @@ void loop() {
if (Serial.available()) {
// wait char
inSer = Serial.read();
mSwitchCasesSer(inSer);
mHandleCases_of_SerialInput(inSer);
} // end if serial...
// automode RF-Tx-trigger
if (automode) {
// increment the the inverter-index
if ( m_next_invIX || (millis() > INVERTER_TIMEOUT_sec * 1000 && (millis() - lastInvChange_millis) > INVERTER_TIMEOUT_sec * 1000)) {
lastInvChange_millis = millis();
m_next_invIX = false;
DPRINT(DBG_INFO, F("Change inverter index to "));
tmp8 = m_inv_ix; //save last valid ix value
m_inv_ix++;
if (m_inv_ix >= MAX_NUM_INVERTERS) {
m_inv_ix = 0;
}
if (mPayload[m_inv_ix].invType == 0x0000) {
//inverter index is invalid
m_inv_ix = tmp8;
} else {
//is changed inverter index, make init for new inverter
}
_DPRINT(DBG_INFO, m_inv_ix);
}
// slow down the sending if no Rx for long time
if (millis() < (SEND_INTERVAL * ONE_SECOND) && !lastRx_millis) {
if (millis() < (SEND_INTERVAL * ONE_SECOND) && !lastRx_millis[m_inv_ix]) {
// initial fast send as long as no rx
mSendInterval_ms = (SEND_NOSIGNAL_SHORT * ONE_SECOND);
mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_SHORT * ONE_SECOND);
} else if (millis() - lastRx_millis > (8 * polling_req_msec)) {
} else if (millis() - lastRx_millis[m_inv_ix] > (8 * polling_req_msec[m_inv_ix])) {
// no Rx in time reduce Tx polling
if (mCountdown_noSignal) {
if (mCountdown_noSignal[m_inv_ix] ) {
// keep fast Tx on initial no-signal for countdown > 0
mSendInterval_ms = (SEND_NOSIGNAL_SHORT * ONE_SECOND);
mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_SHORT * ONE_SECOND);
} else {
// large no signal period
mSendInterval_ms = (SEND_NOSIGNAL_LONG * ONE_SECOND);
mSendInterval_ms[m_inv_ix] = (SEND_NOSIGNAL_LONG * ONE_SECOND);
}
} else {
// rx OK keep default Tx polling
mSendInterval_ms = polling_req_msec;
mCountdown_noSignal = SEND_REPEAT;
mSendInterval_ms[m_inv_ix] = polling_req_msec[m_inv_ix];
mCountdown_noSignal[m_inv_ix] = SEND_REPEAT;
}
// trigger Tx if long period not send
if ((millis() - timer2_millis >= (SEND_NOSIGNAL_LONG * ONE_SECOND))) {
if ((millis() - timer2_millis[m_inv_ix] >= (SEND_NOSIGNAL_LONG * ONE_SECOND))) {
// no send for long time, trigger fast send for countdown time
mCountdown_noSignal = SEND_REPEAT;
mCountdown_noSignal[m_inv_ix] = SEND_REPEAT;
}
// todo: add queue of cmds or schedule simple device control request for power limit values
if (sendNow || (millis() - timer2_millis > mSendInterval_ms)) {
timer2_millis = millis();
tcmd_millis = timer2_millis;
if (sendNow || (millis() - timer2_millis[m_inv_ix] > mSendInterval_ms[m_inv_ix])) {
timer2_millis[m_inv_ix] = millis();
tcmd_millis = timer2_millis[m_inv_ix];
smac_send |= showMAC;
// DISABLE_IRQ;
payload_used[m_inv_ix] = !resetPayload(&mPayload[m_inv_ix]);
@ -262,16 +307,16 @@ void loop() {
mPayload[m_inv_ix].receive = false;
mPayload[m_inv_ix].requested = true; // assume the previous sending is finished because of scheduled timing, therefore no check if still true
sendNow = false;
if (mCountdown_noSignal) mCountdown_noSignal--;
if (mCountdown_noSignal[m_inv_ix]) mCountdown_noSignal[m_inv_ix]--;
if (iv_devControlReq) {
// send devControlReq
hmRadio.sendControlPacket(&mPayload[m_inv_ix].invId[0], iv_devControlCmd, iv_powerLimit);
hmRadio.sendControlPacket(&mPayload[m_inv_ix].invId[0], m_inv_ix, iv_devControlCmd, iv_powerLimit);
iv_devControlReq = false; // todo: use timer that it is not send to quick (e.g. max once per 5sec)
} else {
// send regular info request
hmRadio.sendTimePacket(&mPayload[m_inv_ix].invId[0], 0x0B, mTimestamp, 0x0000);
hmRadio.sendTimePacket(&mPayload[m_inv_ix].invId[0], m_inv_ix, 0x0B, mTimestamp, 0x0000);
}
// RESTORE_IRQ;
}
@ -284,6 +329,7 @@ void loop() {
if (!packet_Buffer.empty()) {
packet_t *p;
p = packet_Buffer.getBack();
if (hmRadio.checkPaketCrc(&p->data[0], &p->plen, p->rfch)) {
// process buffer only on first occurrence
DPRINT(DBG_INFO, F("RX Ch"));
@ -295,15 +341,18 @@ void loop() {
hmRadio.dumpBuf(DBG_INFO, NULL, p->data, p->plen);
// mFrameCnt++;
// check received inv-ID per packet packet is matching with requested inv-ID (important if several inverter are queried by different data request units (DRU) like this one)
// actually seen that other inverter id packets are also received if requested by different DRUs
if (check_array(&p->data[1], &mPayload[m_inv_ix].invId[1], 4)) {
//data of received InvID matches with requested ID
if (p->plen) {
// no need to get the m_inv_ix, because only desired inverter will answer, payload buffer is CRC protected
// m_inv_ix = getInvID(mPayload, MAX_NUM_INVERTERS, &p->data[0]);
if (m_inv_ix >= 0 && m_inv_ix < MAX_NUM_INVERTERS) {
if (copyPacket2Payload(&mPayload[m_inv_ix], m_inv_ix, p, mPayload[m_inv_ix].isMACPacket)) {
lastRx_millis = millis();
lastRx_millis[m_inv_ix] = millis();
}
}
} // end if(plen)
} // end check_array()
} // end if(checkPacketCRC)
packet_Buffer.popBack(); // remove last entry after eval and print
DPRINT(DBG_DEBUG, F("Records in p_B "));
@ -313,7 +362,7 @@ void loop() {
// handle output of data, if ready, based on tx-timer
// todo: make one timer for tx an rx and make shorter timeout
// todo: don't call this section if listening mode only, because processPayload() triggers retransmission
if ((millis() - timer2_millis >= 450) && packet_Buffer.empty() && mPayload[m_inv_ix].requested && mPayload[m_inv_ix].receive) {
if ((millis() - timer2_millis[m_inv_ix] >= 450) && packet_Buffer.empty() && mPayload[m_inv_ix].requested && mPayload[m_inv_ix].receive) {
// process payload
// after 450ms all packets shall have been copied successfully to mPayload structure, run only if requested and some data received after REQ-trigger
@ -331,7 +380,9 @@ void loop() {
if (tmp8 == 42 && doDecode) {
tmp32 = swap_bytes(&mPayload[m_inv_ix].invId[1]);
decode_userPayload(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32);
decode_InfoREQResp(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32);
m_next_invIX = true; //enables next inverter request
// Decoder.decode_InfoREQResp(mPayload[m_inv_ix].txId, &user_payload[0], tmp8, user_pl_ts, m_strout_p, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32);
}
}
}
@ -425,18 +476,17 @@ static uint32_t swap_bytes(uint8_t *_inbuf) {
return _res;
}
////////////////// handle payload function, maybe later as own class //////////////////
/**
* compares the two arrays and returns true when equal
*/
// static bool check_array(uint8_t *invID, uint8_t *rcvID, uint8_t _len) {
// uint8_t i;
// for (i = 0; i < _len; i++) {
// if (invID[i] != rcvID[i]) return false;
// }
// return true;
// }
static bool check_array(uint8_t *invID, uint8_t *rcvID, uint8_t _len) {
uint8_t i;
for (i = 0; i < _len; i++) {
if (invID[i] != rcvID[i]) return false;
}
return true;
}
/**
* gets the payload index that matches to the received ID or the first empty payload structure possition,
@ -555,7 +605,7 @@ static bool copyPacket2Payload(invPayload_t *_payload, uint8_t _invx, packet_t *
} // end savePayload()
/**
* saves copies one received payload-packet into inverter memory structure
* saves copies of one received payload-packet into inverter memory structure
*/
static bool savePayloadfragment(invPayload_t *_payload, packet_t *_p, uint8_t _pid, uint8_t _ix) {
volatile bool _res = false;
@ -607,8 +657,8 @@ static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retr
_payload->retransmits++;
DPRINT(DBG_DEBUG, F(" req fragment "));
_DPRINTHEX(DBG_DEBUG, (uint8_t)(_reqfragment & 0x7f));
hmRadio.sendCmdPacket(_payload->invId, TX_REQ_INFO, _reqfragment, true);
timer2_millis = millis(); // set sending timer2
hmRadio.sendCmdPacket(_payload->invId, m_inv_ix, TX_REQ_INFO, _reqfragment, true);
timer2_millis[m_inv_ix] = millis(); // set sending timer2
// leaves with false to quickly receive again
// so far the Tx and Rx frequency is handled globally, it shall be individually per inverter ID?
}
@ -623,7 +673,7 @@ static bool processPayload(invPayload_t *_payload, config_t *_mconfig, bool retr
} // end processPayload()
/**
* checks the paypload of all received packet-fragments via CRC16 and length
* checks the paypload of all received packet-fragments via CRC16 and length, make sure before that all fragments come from same inverter
* returns:
* - 0x00 == OK if all fragments received and CRC OK,
* - PID + 0x81 can be used directly to request the first next missing packet
@ -681,7 +731,7 @@ static uint8_t checkPayload(invPayload_t *_payload) {
_DPRINTHEX(DBG_ERROR, crc);
DPRINT(DBG_ERROR, F(" crcRcv "));
_DPRINTHEX(DBG_ERROR, crcRcv);
// wrong CRC16 over all packets, must actually never happen for correct packets, must be programming bug
// wrong CRC16 over all packets, must actually never happen for correct packets, but if different inverter have answered and not sorted out the unrequested data fragments
DPRINT(DBG_ERROR, F(" cPL wrong req. "));
_DPRINTHEX(DBG_ERROR, (uint8_t)(i + 0x81));
_payload->receive = false; // stop further eval
@ -760,103 +810,30 @@ static uint8_t collect_and_return_userPayload(invPayload_t *_payload, uint8_t *_
return _offs;
} // end if returnPaylout
/**
* simple decoding of 2ch HM-inverter only
*/
static void decode_userPayload(uint8_t _cmd, uint8_t *_user_payload, uint8_t _ulen, uint32_t _ts, char *_strout, uint8_t _strlen, uint16_t _invtype, uint32_t _plateID) {
volatile uint32_t _val = 0L;
#if defined(ESP8266)
// for esp8266 environment
byteAssign_t _bp; //volatile byteAssign_t not compiling ESP8266
#else
volatile byteAssign_t _bp; //must be volatile for Arduino-Nano, otherwise _bp.ch stays zero
#endif
volatile uint8_t _x;
volatile uint8_t _end;
volatile float _fval;
volatile uint8_t _tmp8 = 0xff;
//_str80[80] = '\0';
snprintf_P(_strout, _strlen, PSTR("\ndata age: %d sec"), (millis() - _ts) / 1000);
Serial.print(_strout);
if (_cmd == 0x95 and _ulen == 42) {
//!!! simple HM600/700/800 2ch decoding for cmd=0x95 only !!!!
for (_x = 0; _x < HM2CH_LIST_LEN; _x++) {
// read values from given positions in payload
_bp = hm2chAssignment[_x];
_val = 0L;
_end = _bp.start + _bp.num;
if (_tmp8 != _bp.ch) {
snprintf_P(_strout, _strlen, PSTR("\nHM800/%04X%08lX/ch%02d "), _invtype, _plateID, _bp.ch); //must be a small "l" in %08lX for Arduino-Nano
Serial.print(_strout);
// snprintf_P(_strout, _strlen, PSTR("ch%02d/"), _bp.ch);
// Serial.print(_strout);
}
_tmp8 = _bp.ch;
if (CMD_CALC != _bp.div && _bp.div != 0) {
strncpy_P(_strout, &(PGM_fields[_bp.fieldId][0]), _strlen);
Serial.print(_strout);
Serial.print(F(": "));
do {
_val <<= 8;
_val |= _user_payload[_bp.start];
} while (++_bp.start != _end);
_fval = (int)_val / (float)_bp.div;
if (_bp.unitId == UNIT_NONE) {
Serial.print(_val);
Serial.print(F(" "));
continue;
}
Serial.print(_fval, 2); // arduino nano does not sprintf(float values), but print() does
// Serial.print(units[_bp.unitId]);
strncpy_P(_strout, &PGM_units[_bp.unitId][0], _strlen);
Serial.print(_strout);
} else {
// do calculations
// Serial.print(F("not yet"));
}
} // end for()
Serial.println();
} else {
Serial.print(F("NO DECODER "));
Serial.print(_cmd, HEX);
}
}//end decodePayload()
///////////////////////////////////////////////////////////
//
// handling of serial commands starting with first char _inSer, it uses many global variables to shorten function parameter handover
// handling of serial input commands starting with first char _inSer, it uses many global variables to shorten function parameter handover
//
void mSwitchCasesSer(char _inSer) {
void mHandleCases_of_SerialInput(char _inSer) {
switch (_inSer) {
case (char)'a': {
// enable automode with REQ polling interval via a10 => 10sec, a100 => 100sec or other range 5....3600sec
// extend automode with polling period in sec, inverter ix and id, all numerial value are optional from the back
// cmd: a[[[:<poll_sec>]:<invIX>]:<12digit invID>:] e.g. a:120:1:081511223344:, also polling only a:120: and polling and inv_ix via a:120:1
automode = true;
m_inv_ix = 0;
//m_inv_ix = 0;
uint8_t invIX = 0;
uint16_t invType = 0x4711;
uint8_t invID5[5] = {0x01, 0x44, 0x33, 0x22, 0x11};
mParams = utSer.getParamsBuf();
tmp8 = utSer.read_uart_cmd_param(mParams);
mCountdown_noSignal = SEND_REPEAT; //trigger sending if before no resonse
if (tmp8 > 0) {
// get polling interval from first parameter
tmp81 = strlen(&mParams[0][0]);
if (tmp81 > 0 && tmp81 < 5) {
polling_req_msec = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1);
//polling_req_msec[m_inv_ix] = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1);
tmp32 = 1000 * utSer.uart_eval_decimal_val(F("auto poll sec "), &mParams[0][0], 5, 5, 3600, 1);
}
if (tmp8 > 2) {
@ -884,6 +861,9 @@ void mSwitchCasesSer(char _inSer) {
} // end if(tmp8 > 2)-else
} // end if(tmp8 > 0)
Serial.println();
polling_req_msec[m_inv_ix] = tmp32;
mCountdown_noSignal[m_inv_ix] = SEND_REPEAT; // trigger sending if before no resonse
break;
} // end case a
@ -893,7 +873,7 @@ void mSwitchCasesSer(char _inSer) {
// simple decoding, can only handle the current active inverter index, maybe erased if new data arrive, switch other inverter via interter indexing in automode settings
tmp32 = swap_bytes(&mPayload[m_inv_ix].invId[1]);
// Serial.print("\nd tmp32 hex: "); Serial.print(tmp32,HEX);
decode_userPayload(TX_REQ_INFO + 0x80, user_payload, 42, user_pl_ts, utSer.mStrOutBuf, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32);
decode_InfoREQResp(TX_REQ_INFO + 0x80, user_payload, 42, user_pl_ts, utSer.mStrOutBuf, MAX_STRING_LEN, mPayload[m_inv_ix].invType, tmp32);
m_sread_len = utSer.serBlockRead_ms(utSer.mSerBuffer);
doDecode = (bool)utSer.uart_eval_decimal_val(F("decoding "), utSer.mSerBuffer, m_sread_len, 0, 255, 1);
break;
@ -1084,7 +1064,6 @@ void mSwitchCasesSer(char _inSer) {
} // end switch-case
} // end mSwitchCases()
///////////////////////////////////////////////////////////
//
// Interrupt handling

Loading…
Cancel
Save