mirror of https://github.com/lumapu/ahoy.git
				
				
			
							committed by
							
								 GitHub
								GitHub
							
						
					
				
				 1 changed files with 0 additions and 689 deletions
			
			
		| @ -1,689 +0,0 @@ | |||
| //-----------------------------------------------------------------------------
 | |||
| // 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