mirror of https://github.com/lumapu/ahoy.git
				
				
			
							committed by
							
								
								GitHub
							
						
					
				
				 2 changed files with 858 additions and 130 deletions
			
			
		@ -0,0 +1,689 @@ | 
				
			|||
//-----------------------------------------------------------------------------
 | 
				
			|||
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
 | 
				
			|||
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
 | 
				
			|||
//-----------------------------------------------------------------------------
 | 
				
			|||
 | 
				
			|||
#ifndef __HM_INVERTER_H__ | 
				
			|||
#define __HM_INVERTER_H__ | 
				
			|||
 | 
				
			|||
#if defined(ESP32) && defined(F) | 
				
			|||
  #undef F | 
				
			|||
  #define F(sl) (sl) | 
				
			|||
#endif | 
				
			|||
 | 
				
			|||
#include "hmDefines.h" | 
				
			|||
#include <memory> | 
				
			|||
#include <queue> | 
				
			|||
#include "../config/settings.h" | 
				
			|||
 | 
				
			|||
/**
 | 
				
			|||
 * For values which are of interest and not transmitted by the inverter can be | 
				
			|||
 * calculated automatically. | 
				
			|||
 * A list of functions can be linked to the assignment and will be executed | 
				
			|||
 * automatically. Their result does not differ from original read values. | 
				
			|||
 */ | 
				
			|||
 | 
				
			|||
// forward declaration of class
 | 
				
			|||
template <class REC_TYP=float> | 
				
			|||
class Inverter; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
// prototypes
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcUdcCh(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcIrradiation(Inverter<> *iv, uint8_t arg0); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
using func_t = T (Inverter<> *, uint8_t); | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
struct calcFunc_t { | 
				
			|||
    uint8_t funcId; // unique id
 | 
				
			|||
    func_t<T>*  func;   // function pointer
 | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
struct record_t { | 
				
			|||
    byteAssign_t* assign; // assigment of bytes in payload
 | 
				
			|||
    uint8_t length;       // length of the assignment list
 | 
				
			|||
    T *record;            // data pointer
 | 
				
			|||
    uint32_t ts;          // timestamp of last received payload
 | 
				
			|||
    uint8_t pyldLen;      // expected payload length for plausibility check
 | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
class CommandAbstract { | 
				
			|||
    public: | 
				
			|||
        CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0) { | 
				
			|||
            _TxType = txType; | 
				
			|||
            _Cmd = cmd; | 
				
			|||
        }; | 
				
			|||
        virtual ~CommandAbstract() {}; | 
				
			|||
 | 
				
			|||
        const uint8_t getCmd() { | 
				
			|||
            return _Cmd; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    protected: | 
				
			|||
        uint8_t _TxType; | 
				
			|||
        uint8_t _Cmd; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
class InfoCommand : public CommandAbstract { | 
				
			|||
    public: | 
				
			|||
        InfoCommand(uint8_t cmd){ | 
				
			|||
            _TxType = 0x15; | 
				
			|||
            _Cmd = cmd; | 
				
			|||
        } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
class MiInfoCommand : public CommandAbstract { | 
				
			|||
    public: | 
				
			|||
        MiInfoCommand(uint8_t cmd){ | 
				
			|||
            _TxType = cmd; | 
				
			|||
            _Cmd = cmd; | 
				
			|||
        } | 
				
			|||
}; | 
				
			|||
// list of all available functions, mapped in hmDefines.h
 | 
				
			|||
template<class T=float> | 
				
			|||
const calcFunc_t<T> calcFunctions[] = { | 
				
			|||
    { CALC_YT_CH0,  &calcYieldTotalCh0 }, | 
				
			|||
    { CALC_YD_CH0,  &calcYieldDayCh0   }, | 
				
			|||
    { CALC_UDC_CH,  &calcUdcCh         }, | 
				
			|||
    { CALC_PDC_CH0, &calcPowerDcCh0    }, | 
				
			|||
    { CALC_EFF_CH0, &calcEffiencyCh0   }, | 
				
			|||
    { CALC_IRR_CH,  &calcIrradiation   } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
template <class REC_TYP> | 
				
			|||
class Inverter { | 
				
			|||
    public: | 
				
			|||
        uint8_t       ivGen;             // generation of inverter (HM / MI)
 | 
				
			|||
        cfgIv_t       *config;           // stored settings
 | 
				
			|||
        uint8_t       id;                // unique id
 | 
				
			|||
        uint8_t       type;              // integer which refers to inverter type
 | 
				
			|||
        uint16_t      alarmMesIndex;     // Last recorded Alarm Message Index
 | 
				
			|||
        uint16_t      powerLimit[2];     // limit power output
 | 
				
			|||
        float         actPowerLimit;     // actual power limit
 | 
				
			|||
        uint8_t       devControlCmd;     // carries the requested cmd
 | 
				
			|||
        serial_u      radioId;           // id converted to modbus
 | 
				
			|||
        uint8_t       channels;          // number of PV channels (1-4)
 | 
				
			|||
        record_t<REC_TYP> recordMeas;    // structure for measured values
 | 
				
			|||
        record_t<REC_TYP> recordInfo;    // structure for info values
 | 
				
			|||
        record_t<REC_TYP> recordConfig;  // structure for system config values
 | 
				
			|||
        record_t<REC_TYP> recordAlarm;   // structure for alarm values
 | 
				
			|||
        //String        lastAlarmMsg;
 | 
				
			|||
        bool          initialized;       // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
 | 
				
			|||
        bool          isConnected;       // shows if inverter was successfully identified (fw version and hardware info)
 | 
				
			|||
 | 
				
			|||
        Inverter() { | 
				
			|||
            ivGen              = IV_HM; | 
				
			|||
            powerLimit[0]      = 0xffff;               // 65535 W Limit -> unlimited
 | 
				
			|||
            powerLimit[1]      = AbsolutNonPersistent; // default power limit setting
 | 
				
			|||
            actPowerLimit      = 0xffff;               // init feedback from inverter to -1
 | 
				
			|||
            mDevControlRequest = false; | 
				
			|||
            devControlCmd      = InitDataState; | 
				
			|||
            initialized        = false; | 
				
			|||
            //lastAlarmMsg       = "nothing";
 | 
				
			|||
            alarmMesIndex      = 0; | 
				
			|||
            isConnected        = false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        ~Inverter() { | 
				
			|||
            // TODO: cleanup
 | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        template <typename T> | 
				
			|||
        void enqueCommand(uint8_t cmd) { | 
				
			|||
           _commandQueue.push(std::make_shared<T>(cmd)); | 
				
			|||
           DPRINTLN(DBG_INFO, F("(#") + String(id) + F(") enqueuedCmd: 0x") + String(cmd, HEX)); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void setQueuedCmdFinished() { | 
				
			|||
            if (!_commandQueue.empty()) { | 
				
			|||
                // Will destroy CommandAbstract Class Object (?)
 | 
				
			|||
                _commandQueue.pop(); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void clearCmdQueue() { | 
				
			|||
            DPRINTLN(DBG_INFO, F("clearCmdQueue")); | 
				
			|||
            while (!_commandQueue.empty()) { | 
				
			|||
                // Will destroy CommandAbstract Class Object (?)
 | 
				
			|||
                _commandQueue.pop(); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    	uint8_t getQueuedCmd() { | 
				
			|||
            if (_commandQueue.empty()) { | 
				
			|||
                if (ivGen != IV_MI) { | 
				
			|||
                    if (getFwVersion() == 0) | 
				
			|||
                        enqueCommand<InfoCommand>(InverterDevInform_All); // firmware version
 | 
				
			|||
                    enqueCommand<InfoCommand>(RealTimeRunData_Debug);  // live data
 | 
				
			|||
                } else if (ivGen == IV_MI){ | 
				
			|||
                    if (type == INV_TYPE_4CH) { | 
				
			|||
                        enqueCommand<InfoCommand>(0x36); | 
				
			|||
                        /*for(uint8_t i = 0x36; i <= 0x39; i++) {
 | 
				
			|||
                            enqueCommand<MiInfoCommand>(i);  // live data
 | 
				
			|||
                        }*/ | 
				
			|||
                    } else if (type == INV_TYPE_2CH) { | 
				
			|||
                        enqueCommand<InfoCommand>(0x09); | 
				
			|||
                        //enqueCommand<MiInfoCommand>(0x11);
 | 
				
			|||
                    } else if (type == INV_TYPE_1CH) { | 
				
			|||
                        enqueCommand<InfoCommand>(0x09); | 
				
			|||
                    } | 
				
			|||
                    //if (getFwVersion() == 0)
 | 
				
			|||
                    //    enqueCommand<MiInfoCommand>(InverterDevInform_All); // firmware version, might not work, esp. for 1/2 ch hardware
 | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                if ((actPowerLimit == 0xffff) && isConnected) | 
				
			|||
                    enqueCommand<InfoCommand>(SystemConfigPara); // power limit info
 | 
				
			|||
            } | 
				
			|||
            return _commandQueue.front().get()->getCmd(); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
        void init(void) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:init")); | 
				
			|||
            initAssignment(&recordMeas, RealTimeRunData_Debug); | 
				
			|||
            initAssignment(&recordInfo, InverterDevInform_All); | 
				
			|||
            initAssignment(&recordConfig, SystemConfigPara); | 
				
			|||
            initAssignment(&recordAlarm, AlarmData); | 
				
			|||
            toRadioId(); | 
				
			|||
            initialized = true; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        uint8_t getPosByChFld(uint8_t channel, uint8_t fieldId, record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getPosByChFld")); | 
				
			|||
            uint8_t pos = 0; | 
				
			|||
            if(NULL != rec) { | 
				
			|||
                for(; pos < rec->length; pos++) { | 
				
			|||
                    if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) | 
				
			|||
                        break; | 
				
			|||
                } | 
				
			|||
                return (pos >= rec->length) ? 0xff : pos; | 
				
			|||
            } | 
				
			|||
            else | 
				
			|||
                return 0xff; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        byteAssign_t *getByteAssign(uint8_t pos, record_t<> *rec) { | 
				
			|||
            return &rec->assign[pos]; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        const char *getFieldName(uint8_t pos, record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getFieldName")); | 
				
			|||
            if(NULL != rec) | 
				
			|||
                return fields[rec->assign[pos].fieldId]; | 
				
			|||
            return notAvail; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        const char *getUnit(uint8_t pos, record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getUnit")); | 
				
			|||
            if(NULL != rec) | 
				
			|||
                return units[rec->assign[pos].unitId]; | 
				
			|||
            return notAvail; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        uint8_t getChannel(uint8_t pos, record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getChannel")); | 
				
			|||
            if(NULL != rec) | 
				
			|||
                return rec->assign[pos].ch; | 
				
			|||
            return 0; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bool setDevControlRequest(uint8_t cmd) { | 
				
			|||
            if(isConnected) { | 
				
			|||
                mDevControlRequest = true; | 
				
			|||
                devControlCmd = cmd; | 
				
			|||
            } | 
				
			|||
            return isConnected; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void clearDevControlRequest() { | 
				
			|||
            mDevControlRequest = false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        inline bool getDevControlRequest() { | 
				
			|||
            return mDevControlRequest; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void addValue(uint8_t pos, uint8_t buf[], record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:addValue")); | 
				
			|||
            if(NULL != rec) { | 
				
			|||
                uint8_t  ptr = rec->assign[pos].start; | 
				
			|||
                uint8_t  end = ptr + rec->assign[pos].num; | 
				
			|||
                uint16_t div = rec->assign[pos].div; | 
				
			|||
 | 
				
			|||
                if(NULL != rec) { | 
				
			|||
                    if(CMD_CALC != div) { | 
				
			|||
                        uint32_t val = 0; | 
				
			|||
                        do { | 
				
			|||
                            val <<= 8; | 
				
			|||
                            val |= buf[ptr]; | 
				
			|||
                        } while(++ptr != end); | 
				
			|||
                        if (FLD_T == rec->assign[pos].fieldId) { | 
				
			|||
                            // temperature is a signed value!
 | 
				
			|||
                            rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div); | 
				
			|||
                        } else if (FLD_YT == rec->assign[pos].fieldId) { | 
				
			|||
                            rec->record[pos] = ((REC_TYP)(val) / (REC_TYP)(div)) + ((REC_TYP)config->yieldCor[rec->assign[pos].ch-1]); | 
				
			|||
                        } else { | 
				
			|||
                            if ((REC_TYP)(div) > 1) | 
				
			|||
                                rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); | 
				
			|||
                            else | 
				
			|||
                                rec->record[pos] = (REC_TYP)(val); | 
				
			|||
                        } | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                if(rec == &recordMeas) { | 
				
			|||
                    DPRINTLN(DBG_VERBOSE, "add real time"); | 
				
			|||
 | 
				
			|||
                    // get last alarm message index and save it in the inverter object
 | 
				
			|||
                    if (getPosByChFld(0, FLD_EVT, rec) == pos){ | 
				
			|||
                        if (alarmMesIndex < rec->record[pos]){ | 
				
			|||
                            alarmMesIndex = rec->record[pos]; | 
				
			|||
                            //enqueCommand<InfoCommand>(AlarmUpdate); // What is the function of AlarmUpdate?
 | 
				
			|||
 | 
				
			|||
                            DPRINTLN(DBG_INFO, "alarm ID incremented to " + String(alarmMesIndex)); | 
				
			|||
                            enqueCommand<InfoCommand>(AlarmData); | 
				
			|||
                        } | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
                else if (rec->assign == InfoAssignment) { | 
				
			|||
                    DPRINTLN(DBG_DEBUG, "add info"); | 
				
			|||
                    // eg. fw version ...
 | 
				
			|||
                    isConnected = true; | 
				
			|||
                } | 
				
			|||
                else if (rec->assign == SystemConfigParaAssignment) { | 
				
			|||
                    DPRINTLN(DBG_DEBUG, "add config"); | 
				
			|||
                    if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ | 
				
			|||
                        actPowerLimit = rec->record[pos]; | 
				
			|||
                        DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1)); | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
                else if (rec->assign == AlarmDataAssignment) { | 
				
			|||
                    DPRINTLN(DBG_DEBUG, "add alarm"); | 
				
			|||
                    //if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
 | 
				
			|||
                    //    lastAlarmMsg = getAlarmStr(rec->record[pos]);
 | 
				
			|||
                    //}
 | 
				
			|||
                } | 
				
			|||
                else | 
				
			|||
                    DPRINTLN(DBG_WARN, F("add with unknown assginment")); | 
				
			|||
            } | 
				
			|||
            else | 
				
			|||
                DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x")); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        /*inline REC_TYP getPowerLimit(void) {
 | 
				
			|||
            record_t<> *rec = getRecordStruct(SystemConfigPara); | 
				
			|||
            return getChannelFieldValue(CH0, FLD_ACT_ACTIVE_PWR_LIMIT, rec); | 
				
			|||
        }*/ | 
				
			|||
 | 
				
			|||
        bool setValue(uint8_t pos, record_t<> *rec, REC_TYP val) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue")); | 
				
			|||
            if(NULL == rec) | 
				
			|||
                return false; | 
				
			|||
            if(pos > rec->length) | 
				
			|||
                return false; | 
				
			|||
            rec->record[pos] = val; | 
				
			|||
            return true; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        REC_TYP getChannelFieldValue(uint8_t channel, uint8_t fieldId, record_t<> *rec) { | 
				
			|||
            uint8_t pos = 0; | 
				
			|||
            if(NULL != rec) { | 
				
			|||
                for(; pos < rec->length; pos++) { | 
				
			|||
                    if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) | 
				
			|||
                        break; | 
				
			|||
                } | 
				
			|||
                if(pos >= rec->length) | 
				
			|||
                    return 0; | 
				
			|||
 | 
				
			|||
                return rec->record[pos]; | 
				
			|||
            } | 
				
			|||
            else | 
				
			|||
                return 0; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        REC_TYP getValue(uint8_t pos, record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); | 
				
			|||
            if(NULL == rec) | 
				
			|||
                return 0; | 
				
			|||
            if(pos > rec->length) | 
				
			|||
                return 0; | 
				
			|||
            return rec->record[pos]; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void doCalculations() { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:doCalculations")); | 
				
			|||
            record_t<> *rec = getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
            for(uint8_t i = 0; i < rec->length; i++) { | 
				
			|||
                if(CMD_CALC == rec->assign[i].div) { | 
				
			|||
                    rec->record[i] = calcFunctions<REC_TYP>[rec->assign[i].start].func(this, rec->assign[i].num); | 
				
			|||
                } | 
				
			|||
                yield(); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bool isAvailable(uint32_t timestamp) { | 
				
			|||
           if((timestamp - recordMeas.ts) < INACT_THRES_SEC) | 
				
			|||
                return true; | 
				
			|||
            if((timestamp - recordInfo.ts) < INACT_THRES_SEC) | 
				
			|||
                return true; | 
				
			|||
            if((timestamp - recordConfig.ts) < INACT_THRES_SEC) | 
				
			|||
                return true; | 
				
			|||
            if((timestamp - recordAlarm.ts) < INACT_THRES_SEC) | 
				
			|||
                return true; | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bool isProducing(uint32_t timestamp) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:isProducing")); | 
				
			|||
            if(isAvailable(timestamp)) { | 
				
			|||
                uint8_t pos = getPosByChFld(CH0, FLD_PAC, &recordMeas); | 
				
			|||
                return (getValue(pos, &recordMeas) > INACT_PWR_THRESH); | 
				
			|||
            } | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        uint16_t getFwVersion() { | 
				
			|||
            record_t<> *rec = getRecordStruct(InverterDevInform_All); | 
				
			|||
            uint8_t pos = getPosByChFld(CH0, FLD_FW_VERSION, rec); | 
				
			|||
            return getValue(pos, rec); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        uint32_t getLastTs(record_t<> *rec) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs")); | 
				
			|||
            return rec->ts; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        record_t<> *getRecordStruct(uint8_t cmd) { | 
				
			|||
            switch (cmd) { | 
				
			|||
                case RealTimeRunData_Debug: return &recordMeas;   // 11 = 0x0b
 | 
				
			|||
                case InverterDevInform_All: return &recordInfo;   //  1 = 0x01
 | 
				
			|||
                case SystemConfigPara:      return &recordConfig; //  5 = 0x05
 | 
				
			|||
                case AlarmData:             return &recordAlarm;  // 17 = 0x11
 | 
				
			|||
                default:                    break; | 
				
			|||
            } | 
				
			|||
            return NULL; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        void initAssignment(record_t<> *rec, uint8_t cmd) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment")); | 
				
			|||
            rec->ts     = 0; | 
				
			|||
            rec->length = 0; | 
				
			|||
            switch (cmd) { | 
				
			|||
                case RealTimeRunData_Debug: | 
				
			|||
                    if (INV_TYPE_1CH == type) { | 
				
			|||
                        rec->length  = (uint8_t)(HM1CH_LIST_LEN); | 
				
			|||
                        rec->assign  = (byteAssign_t *)hm1chAssignment; | 
				
			|||
                        rec->pyldLen = HM1CH_PAYLOAD_LEN; | 
				
			|||
                        channels     = 1; | 
				
			|||
                    } | 
				
			|||
                    else if (INV_TYPE_2CH == type) { | 
				
			|||
                        rec->length  = (uint8_t)(HM2CH_LIST_LEN); | 
				
			|||
                        rec->assign  = (byteAssign_t *)hm2chAssignment; | 
				
			|||
                        rec->pyldLen = HM2CH_PAYLOAD_LEN; | 
				
			|||
                        channels     = 2; | 
				
			|||
                    } | 
				
			|||
                    else if (INV_TYPE_4CH == type) { | 
				
			|||
                        rec->length  = (uint8_t)(HM4CH_LIST_LEN); | 
				
			|||
                        rec->assign  = (byteAssign_t *)hm4chAssignment; | 
				
			|||
                        rec->pyldLen = HM4CH_PAYLOAD_LEN; | 
				
			|||
                        channels     = 4; | 
				
			|||
                    } | 
				
			|||
                    else { | 
				
			|||
                        rec->length  = 0; | 
				
			|||
                        rec->assign  = NULL; | 
				
			|||
                        rec->pyldLen = 0; | 
				
			|||
                        channels     = 0; | 
				
			|||
                    } | 
				
			|||
                    break; | 
				
			|||
                case InverterDevInform_All: | 
				
			|||
                    rec->length  = (uint8_t)(HMINFO_LIST_LEN); | 
				
			|||
                    rec->assign  = (byteAssign_t *)InfoAssignment; | 
				
			|||
                    rec->pyldLen = HMINFO_PAYLOAD_LEN; | 
				
			|||
                    break; | 
				
			|||
                case SystemConfigPara: | 
				
			|||
                    rec->length  = (uint8_t)(HMSYSTEM_LIST_LEN); | 
				
			|||
                    rec->assign  = (byteAssign_t *)SystemConfigParaAssignment; | 
				
			|||
                    rec->pyldLen = HMSYSTEM_PAYLOAD_LEN; | 
				
			|||
                    break; | 
				
			|||
                case AlarmData: | 
				
			|||
                    rec->length  = (uint8_t)(HMALARMDATA_LIST_LEN); | 
				
			|||
                    rec->assign  = (byteAssign_t *)AlarmDataAssignment; | 
				
			|||
                    rec->pyldLen = HMALARMDATA_PAYLOAD_LEN; | 
				
			|||
                    break; | 
				
			|||
                default: | 
				
			|||
                    DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented")); | 
				
			|||
                    break; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if(0 != rec->length) { | 
				
			|||
                rec->record = new REC_TYP[rec->length]; | 
				
			|||
                memset(rec->record, 0, sizeof(REC_TYP) * rec->length); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        uint16_t parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len, uint32_t *start, uint32_t *endTime) { | 
				
			|||
            uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE; | 
				
			|||
            if((startOff + ALARM_LOG_ENTRY_SIZE) > len) | 
				
			|||
                return 0; | 
				
			|||
 | 
				
			|||
            uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1]; | 
				
			|||
            uint32_t startTimeOffset = 0, endTimeOffset = 0; | 
				
			|||
 | 
				
			|||
            if (((wCode >> 13) & 0x01) == 1) // check if is AM or PM
 | 
				
			|||
                startTimeOffset = 12 * 60 * 60; | 
				
			|||
            if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM
 | 
				
			|||
                endTimeOffset = 12 * 60 * 60; | 
				
			|||
 | 
				
			|||
            *start     = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset; | 
				
			|||
            *endTime   = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset; | 
				
			|||
 | 
				
			|||
            DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(*start) + ", end: " + ah::getTimeStr(*endTime)); | 
				
			|||
            return pyld[startOff+1]; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        String getAlarmStr(uint16_t alarmCode) { | 
				
			|||
            switch (alarmCode) { // breaks are intentionally missing!
 | 
				
			|||
                case 1:    return String(F("Inverter start")); | 
				
			|||
                case 2:    return String(F("DTU command failed")); | 
				
			|||
                case 121:  return String(F("Over temperature protection")); | 
				
			|||
                case 125:  return String(F("Grid configuration parameter error")); | 
				
			|||
                case 126:  return String(F("Software error code 126")); | 
				
			|||
                case 127:  return String(F("Firmware error")); | 
				
			|||
                case 128:  return String(F("Software error code 128")); | 
				
			|||
                case 129:  return String(F("Software error code 129")); | 
				
			|||
                case 130:  return String(F("Offline")); | 
				
			|||
                case 141:  return String(F("Grid overvoltage")); | 
				
			|||
                case 142:  return String(F("Average grid overvoltage")); | 
				
			|||
                case 143:  return String(F("Grid undervoltage")); | 
				
			|||
                case 144:  return String(F("Grid overfrequency")); | 
				
			|||
                case 145:  return String(F("Grid underfrequency")); | 
				
			|||
                case 146:  return String(F("Rapid grid frequency change")); | 
				
			|||
                case 147:  return String(F("Power grid outage")); | 
				
			|||
                case 148:  return String(F("Grid disconnection")); | 
				
			|||
                case 149:  return String(F("Island detected")); | 
				
			|||
                case 205:  return String(F("Input port 1 & 2 overvoltage")); | 
				
			|||
                case 206:  return String(F("Input port 3 & 4 overvoltage")); | 
				
			|||
                case 207:  return String(F("Input port 1 & 2 undervoltage")); | 
				
			|||
                case 208:  return String(F("Input port 3 & 4 undervoltage")); | 
				
			|||
                case 209:  return String(F("Port 1 no input")); | 
				
			|||
                case 210:  return String(F("Port 2 no input")); | 
				
			|||
                case 211:  return String(F("Port 3 no input")); | 
				
			|||
                case 212:  return String(F("Port 4 no input")); | 
				
			|||
                case 213:  return String(F("PV-1 & PV-2 abnormal wiring")); | 
				
			|||
                case 214:  return String(F("PV-3 & PV-4 abnormal wiring")); | 
				
			|||
                case 215:  return String(F("PV-1 Input overvoltage")); | 
				
			|||
                case 216:  return String(F("PV-1 Input undervoltage")); | 
				
			|||
                case 217:  return String(F("PV-2 Input overvoltage")); | 
				
			|||
                case 218:  return String(F("PV-2 Input undervoltage")); | 
				
			|||
                case 219:  return String(F("PV-3 Input overvoltage")); | 
				
			|||
                case 220:  return String(F("PV-3 Input undervoltage")); | 
				
			|||
                case 221:  return String(F("PV-4 Input overvoltage")); | 
				
			|||
                case 222:  return String(F("PV-4 Input undervoltage")); | 
				
			|||
                case 301:  return String(F("Hardware error code 301")); | 
				
			|||
                case 302:  return String(F("Hardware error code 302")); | 
				
			|||
                case 303:  return String(F("Hardware error code 303")); | 
				
			|||
                case 304:  return String(F("Hardware error code 304")); | 
				
			|||
                case 305:  return String(F("Hardware error code 305")); | 
				
			|||
                case 306:  return String(F("Hardware error code 306")); | 
				
			|||
                case 307:  return String(F("Hardware error code 307")); | 
				
			|||
                case 308:  return String(F("Hardware error code 308")); | 
				
			|||
                case 309:  return String(F("Hardware error code 309")); | 
				
			|||
                case 310:  return String(F("Hardware error code 310")); | 
				
			|||
                case 311:  return String(F("Hardware error code 311")); | 
				
			|||
                case 312:  return String(F("Hardware error code 312")); | 
				
			|||
                case 313:  return String(F("Hardware error code 313")); | 
				
			|||
                case 314:  return String(F("Hardware error code 314")); | 
				
			|||
                case 5041: return String(F("Error code-04 Port 1")); | 
				
			|||
                case 5042: return String(F("Error code-04 Port 2")); | 
				
			|||
                case 5043: return String(F("Error code-04 Port 3")); | 
				
			|||
                case 5044: return String(F("Error code-04 Port 4")); | 
				
			|||
                case 5051: return String(F("PV Input 1 Overvoltage/Undervoltage")); | 
				
			|||
                case 5052: return String(F("PV Input 2 Overvoltage/Undervoltage")); | 
				
			|||
                case 5053: return String(F("PV Input 3 Overvoltage/Undervoltage")); | 
				
			|||
                case 5054: return String(F("PV Input 4 Overvoltage/Undervoltage")); | 
				
			|||
                case 5060: return String(F("Abnormal bias")); | 
				
			|||
                case 5070: return String(F("Over temperature protection")); | 
				
			|||
                case 5080: return String(F("Grid Overvoltage/Undervoltage")); | 
				
			|||
                case 5090: return String(F("Grid Overfrequency/Underfrequency")); | 
				
			|||
                case 5100: return String(F("Island detected")); | 
				
			|||
                case 5120: return String(F("EEPROM reading and writing error")); | 
				
			|||
                case 5150: return String(F("10 min value grid overvoltage")); | 
				
			|||
                case 5200: return String(F("Firmware error")); | 
				
			|||
                case 8310: return String(F("Shut down")); | 
				
			|||
                case 9000: return String(F("Microinverter is suspected of being stolen")); | 
				
			|||
                default:   return String(F("Unknown")); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    private: | 
				
			|||
        void toRadioId(void) { | 
				
			|||
            DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId")); | 
				
			|||
            radioId.u64  = 0ULL; | 
				
			|||
            radioId.b[4] = config->serial.b[0]; | 
				
			|||
            radioId.b[3] = config->serial.b[1]; | 
				
			|||
            radioId.b[2] = config->serial.b[2]; | 
				
			|||
            radioId.b[1] = config->serial.b[3]; | 
				
			|||
            radioId.b[0] = 0x01; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        std::queue<std::shared_ptr<CommandAbstract>> _commandQueue; | 
				
			|||
        bool          mDevControlRequest; // true if change needed
 | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
/**
 | 
				
			|||
 * To calculate values which are not transmitted by the unit there is a generic | 
				
			|||
 * list of functions which can be linked to the assignment. | 
				
			|||
 * The special command 0xff (CMDFF) must be used. | 
				
			|||
 */ | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcYieldTotalCh0(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldTotalCh0")); | 
				
			|||
    if(NULL != iv) { | 
				
			|||
        record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
        T yield = 0; | 
				
			|||
        for(uint8_t i = 1; i <= iv->channels; i++) { | 
				
			|||
            uint8_t pos = iv->getPosByChFld(i, FLD_YT, rec); | 
				
			|||
            yield += iv->getValue(pos, rec); | 
				
			|||
        } | 
				
			|||
        return yield; | 
				
			|||
    } | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcYieldDayCh0(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcYieldDayCh0")); | 
				
			|||
    if(NULL != iv) { | 
				
			|||
        record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
        T yield = 0; | 
				
			|||
        for(uint8_t i = 1; i <= iv->channels; i++) { | 
				
			|||
            uint8_t pos = iv->getPosByChFld(i, FLD_YD, rec); | 
				
			|||
            yield += iv->getValue(pos, rec); | 
				
			|||
        } | 
				
			|||
        return yield; | 
				
			|||
    } | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcUdcCh(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcUdcCh")); | 
				
			|||
    // arg0 = channel of source
 | 
				
			|||
    record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
    for(uint8_t i = 0; i < rec->length; i++) { | 
				
			|||
        if((FLD_UDC == rec->assign[i].fieldId) && (arg0 == rec->assign[i].ch)) { | 
				
			|||
            return iv->getValue(i, rec); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcPowerDcCh0(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcPowerDcCh0")); | 
				
			|||
    if(NULL != iv) { | 
				
			|||
        record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
        T dcPower = 0; | 
				
			|||
        for(uint8_t i = 1; i <= iv->channels; i++) { | 
				
			|||
            uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec); | 
				
			|||
            dcPower += iv->getValue(pos, rec); | 
				
			|||
        } | 
				
			|||
        return dcPower; | 
				
			|||
    } | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcEfficiencyCh0")); | 
				
			|||
    if(NULL != iv) { | 
				
			|||
        record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
        uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec); | 
				
			|||
        T acPower = iv->getValue(pos, rec); | 
				
			|||
        T dcPower = 0; | 
				
			|||
        for(uint8_t i = 1; i <= iv->channels; i++) { | 
				
			|||
            pos = iv->getPosByChFld(i, FLD_PDC, rec); | 
				
			|||
            dcPower += iv->getValue(pos, rec); | 
				
			|||
        } | 
				
			|||
        if(dcPower > 0) | 
				
			|||
            return acPower / dcPower * 100.0f; | 
				
			|||
    } | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
template<class T=float> | 
				
			|||
static T calcIrradiation(Inverter<> *iv, uint8_t arg0) { | 
				
			|||
    DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcIrradiation")); | 
				
			|||
    // arg0 = channel
 | 
				
			|||
    if(NULL != iv) { | 
				
			|||
        record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); | 
				
			|||
        uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec); | 
				
			|||
        if(iv->config->chMaxPwr[arg0-1] > 0) | 
				
			|||
            return iv->getValue(pos, rec) / iv->config->chMaxPwr[arg0-1] * 100.0f; | 
				
			|||
    } | 
				
			|||
    return 0.0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
#endif /*__HM_INVERTER_H__*/ | 
				
			|||
					Loading…
					
					
				
		Reference in new issue