UNPKG

iobroker.ecoflow-mqtt

Version:
1,757 lines (1,726 loc) 135 kB
const protobuf = require('protobufjs'); const https = require('https'); const protoSource = ` syntax = "proto3"; message Header { optional bytes pdata = 1; optional int32 src = 2; optional int32 dest = 3; optional int32 d_src= 4; optional int32 d_dest = 5; optional int32 enc_type = 6; optional int32 check_type = 7; optional int32 cmd_func = 8; optional int32 cmd_id = 9; optional int32 data_len = 10; optional int32 need_ack = 11; optional int32 is_ack = 12; optional int32 seq = 14; optional int32 product_id = 15; optional int32 version = 16; optional int32 payload_ver = 17; optional int32 time_snap = 18; optional int32 is_rw_cmd = 19; optional int32 is_queue = 20; optional int32 ack_type= 21; optional string code = 22; optional string from = 23; optional string module_sn = 24; optional string device_sn = 25; } message HeaderMessage { repeated Header header = 1; } message EventRecordItem { optional uint32 timestamp = 1; optional uint32 sys_ms = 2; optional uint32 event_no = 3; repeated float event_detail = 4; } message EventRecordReport { optional uint32 event_ver = 1; optional uint32 event_seq = 2; repeated EventRecordItem event_item = 3; } message EventInfoReportAck { optional uint32 result = 1; optional uint32 event_seq = 2; optional uint32 event_item_num = 3; } message ProductNameSet { optional string name = 1; } message ProductNameSetAck { optional uint32 result = 1; } message ProductNameGet {} message ProductNameGetAck { optional string name = 3; } message RTCTimeGet {} message RTCTimeGetAck { optional uint32 timestamp = 1; optional int32 timezone = 2; } message RTCTimeSet { optional uint32 timestamp = 1; optional int32 timezone = 2 [(nanopb).default = 0]; } message RTCTimeSetAck { optional uint32 result = 1; } message Send_Header_Msg { optional Header msg = 1; } message SendMsgHart { optional int32 link_id = 1; optional int32 src = 2; optional int32 dest = 3; optional int32 d_src = 4; optional int32 d_dest = 5; optional int32 enc_type = 6; optional int32 check_type = 7; optional int32 cmd_func = 8; optional int32 cmd_id = 9; optional int32 data_len = 10; optional int32 need_ack = 11; optional int32 is_ack = 12; optional int32 ack_type = 13; optional int32 seq = 14; optional int32 time_snap = 15; optional int32 is_rw_cmd = 16; optional int32 is_queue = 17; optional int32 product_id = 18; optional int32 version = 19; } message setMessage { setHeader header = 1; } message setHeader { setValue pdata = 1; int32 src = 2; int32 dest = 3; int32 d_src = 4; int32 d_dest = 5; int32 enc_type = 6; int32 check_type = 7; int32 cmd_func = 8; int32 cmd_id = 9; int32 data_len = 10; int32 need_ack = 11; int32 is_ack = 12; int32 seq = 14; int32 product_id = 15; int32 version = 16; int32 payload_ver = 17; int32 time_snap = 18; int32 is_rw_cmd = 19; int32 is_queue = 20; int32 ack_type = 21; string code = 22; string from = 23; string module_sn = 24; string device_sn = 25; } message setValue { optional int32 value = 1; optional int32 value2 = 2; } `; function decodeMsg(hexString, msgtype, protoSource) { try { const root = protobuf.parse(protoSource).root; const PowerMessage = root.lookupType(msgtype); //const message = PowerMessage.decode(hexString); /*global Buffer*/ /*eslint no-undef: "error"*/ const message = PowerMessage.decode(Buffer.from(hexString, 'hex')); const object = PowerMessage.toObject(message, { defaults: false }); return object; } catch (error) { throw new Error('decodeMsg -> ' + error); } } /** * @param {object} stateDictObj * @param {string} channel * @param {string} state * @param {string | number | boolean} value * @param {object} stateObj */ function convertData(stateDictObj, channel, state, value, stateObj) { let key = stateDictObj[channel][state]['entity']; if (value !== '' && value != null) { let val = value.toString(); switch (key) { case 'number': case 'level': //convert number if (Number.isInteger(value) || typeof value === 'string' || value instanceof String) { if (state !== 'brightness') { value = parseFloat(value) * stateObj[channel][key][state]['mult']; } else { value = parseInt(String(value * stateObj[channel][key][state]['mult'])); } } break; case 'string': value = value.toString(); break; case 'switch': //convert false/true if (value == 0 || value == false || value == '0') { value = false; } else value = true; break; case 'diagnostic': //convert string to item if (Object.prototype.hasOwnProperty.call(stateObj[channel][key][state][state], val)) { value = stateObj[channel][key][state][state][val].toString(); } else { value = val + ' not part of array'; } break; case 'array': value = JSON.stringify(value); break; default: break; } } return value; } /* pstream umbauen, damit Antworten mit mehr als einer Nachricht auch verarbeitet werden Rückgabe der decodierung als Object abspeichern mit loop durch Objekt */ function pstreamDecode(adapter, payload, usage, topic, msgtype, protoSourceDevice, protoMsg, log) { // @ts-ignore const buf = new Buffer.from(payload, 'base64'); try { if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] raw (hex): ' + payload.toString('hex') ); } let msgobj = decodeMsg(buf, 'HeaderMessage', protoSource); if (msgobj) { if (Array.isArray(msgobj.header)) { let returnobj = {}; let dbgmsg = {}; if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] stream has ' + msgobj.header.length + ' message(s)' ); } for (let i = 0; i < msgobj.header.length; i++) { for (let info in msgobj.header[i]) { if (info !== 'pdata') { dbgmsg[info] = msgobj.header[i][info]; } } if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' header ' + JSON.stringify(dbgmsg) ); } //evaluate cmdFunc and cmd Id let cmdFunc = msgobj.header[i].cmdFunc; let cmdId = msgobj.header[i].cmdId; let src = msgobj.header[i].src; const seq = msgobj.header[i].seq; let prototyp = ''; if (protoMsg && protoSourceDevice) { if (cmdId) { if (Object.prototype.hasOwnProperty.call(protoMsg['cmdId'], cmdId)) { if (cmdFunc) { if ( Object.prototype.hasOwnProperty.call( protoMsg['cmdId'][cmdId]['cmdFunc'], cmdFunc ) ) { prototyp = protoMsg['cmdId'][cmdId]['cmdFunc'][cmdFunc]; } else { prototyp = 'undef'; //no decode possible; if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' unknown cmdFunc ' ); } } } else { prototyp = 'undef'; //message without cmdFunc; if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' no cmdFunc in Message' ); } } } else { prototyp = 'undef'; //no decodei possible; if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' unknown cmdId' ); } } } else { prototyp = 'undef'; //message without packageType; if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' no cmdId in message' ); } if (msgobj.header[i].code) { if (msgobj.header[i].code == '-2') { returnobj['info'] = { status: 'offline' }; } } } } else { prototyp = 'undef'; //no objects adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] protoMsg ' + protoMsg + 'protoSourceDevice ' + protoSourceDevice + ' no protoMsg or protoSourceDevice objects' ); } // go to msg itself let msg = null; if (msgobj.header[i].pdata) { if (msgobj.header[i].encType) { if (msgobj.header[i].encType == 1 && src !== 32) { //alternator && src !== 32 //shp2 && src == 11 if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' Encrypted' ); } const typedArray = new Uint8Array(msgobj.header[i].pdata); const array = Array.from(typedArray); let modarray = []; for (let i = 0; i < array.length; i++) { modarray.push(array[i] ^ seq); } const newbuf = Buffer.from(modarray); msgobj.header[i].pdata = newbuf; } } if (log === true) { if (msgobj.header[i].pdata) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' pdata: ' + msgobj.header[i].pdata.toString('hex') ); } } if (prototyp !== 'undef') { try { if (log === true && !msgobj.header[i].pdata) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' => no pdata ' ); } msg = decodeMsg(msgobj.header[i].pdata, prototyp, protoSourceDevice); if (log === true) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' => ' + prototyp + ' ' + JSON.stringify(msg) ); } returnobj[prototyp] = msg; } catch (error) { adapter.log.debug( '[PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' => ' + ' id ' + prototyp + ' error at: ' + error ); } } } else { if (log === true) { adapter.log.debug( ' [PROTOBUF decode] ' + topic + ' [' + msgtype + '] msg#' + i + ' => no pdata' ); } } /* if (cmdId === 32 && cmdFunc !== 12) { if (usage === 'noderedchart') { try { let msgobj32 = msg; //adapter.log.debug('energy:' + JSON.stringify(msgobj32)); let chart = [ { label: 'no', series: [], data: [], labels: [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24' ] } ]; for (let i = 0; i < msgobj32.sysEnergyStream.length; i++) { let date = new Date(msgobj32.sysEnergyStream[i].timestamp * 1000); chart[0].label = date.toString(); chart[0].series.push( String(msgobj32.sysEnergyStream[i].watthType) + ' ' + msgobj32.sysEnergyStream[i].watth.reduce((a, b) => a + b, 0) ); chart[0].data.push(msgobj32.sysEnergyStream[i].watth); } return chart; } catch (error) { adapter.log.debug('id 32 error at: ' + error); } } else { let msgobj32 = msg; let watthType; let energy; let timeStamp; let energyobj = {}; try { for (let i = 0; i < msgobj32.sysEnergyStream.length; i++) { if ( msgobj32.sysEnergyStream[i].watthType && msgobj32.sysEnergyStream[i].watth && msgobj32.sysEnergyStream[i].timestamp ) { //adapter.log.debug('complete ' + i); //we assume all timestamps are equal and the last is used for historical message timeStamp = msgobj32.sysEnergyStream[i].timestamp; watthType = msgobj32.sysEnergyStream[i].watthType; energy = msgobj32.sysEnergyStream[i].watth.reduce((a, b) => a + b, 0); energyobj['watth' + watthType] = energy; } else { //adapter.log.debug('incomplete'); //incomplete telegram } } } catch (error) { adapter.log.debug('id 32 error at: ' + error); } if (msgobj32.sysSeq == 65535) { returnobj['energy'] = energyobj; } else { returnobj['hist_energy'] = { time: timeStamp, energy: energyobj }; } } } */ } adapter.quotas[topic] = returnobj; return returnobj; } } } catch (error) { adapter.log.debug('[PROTOBUF decode] error -> payload was: ' + payload.toString('hex')); throw new Error('[PROTOBUF decode] -> ' + error); } } async function setOnlineStatus(adapter, topic) { let status = await adapter.getStateAsync(topic + '.info.status').catch((e) => { adapter.log.warn('did not get old value .info.status ->' + e); }); if (status && status.val === 'offline') { await adapter.setStateAsync(topic + '.info.status', { val: 'online', ack: true }); adapter.log.info('[EF] ' + topic + ' is online with cloud'); } } async function setOfflineStatus(adapter, topic) { let status = await adapter.getStateAsync(topic + '.info.status').catch((e) => { adapter.log.warn('did not get old value .info.status ->' + e); }); if (status && status.val === 'online') { await adapter.setStateAsync(topic + '.info.status', { val: 'offline', ack: true }); adapter.log.info('[EF] ' + topic + ' is OFFLINE with cloud'); } } async function storeStreamPayload(adapter, stateDictObj, stateObj, topic, payload, devtype, haenable, logged) { let haUpdate = []; if (payload) { // try { /* if (adapter.config.msgUpdatePstream) { adapter.log.debug( 'got topic ' + topic + ' ( stateDictObj ' + stateDictObj + ' stateObj: ' + stateObj + ' )' ); } */ if (stateDictObj) { if (stateObj) { for (let channel in payload) { //other incomming data is ignored (EnergyPack/PowerPack/timeConfig...) switch (channel) { case 'plug_heartbeat': case 'inverter_heartbeat': case 'InverterHeartbeat2': //to delete case 'energy': case 'AppParaHeartbeatReport': case 'AppShowHeartbeatReport': case 'BackendRecordHeartbeatReport': case 'JTS1_ENERGY_STREAM_REPORT': case 'JTS1_EMS_CHANGE_REPORT': case 'EVChargingParamReport': case 'JTS1_EMS_PARAM_CHANGE_REPORT': case 'alternatorHeartbeat': // all cases without nested states await setOnlineStatus(adapter, topic); for (let state in payload[channel]) { let val; let haupd; val = payload[channel][state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state, val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; case 'BPInfoReport': await setOnlineStatus(adapter, topic); //BPInfo mit bpNo verknüpfen for (let i = 0; i < payload[channel]['bpInfo'].length; i++) { //BPInfo mit bpNo verknüpfen, wenn 1 dann ohne Anhang wegen Kompatibilität let name = payload[channel]['bpInfo'][i]['bpNo'] == 1 ? 'BPInfo' : ''.concat('BPInfo', String(payload[channel]['bpInfo'][i]['bpNo'])); for (let state in payload[channel]['bpInfo'][i]) { let val; let haupd; val = payload[channel]['bpInfo'][i][state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, name, //channel, state, val, 'BPInfo', //channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'JTS1_BP_STA_REPORT': await setOnlineStatus(adapter, topic); for (let i = 0; i < payload[channel]['bpSta'].length; i++) { for (let state in payload[channel]['bpSta'][i]) { let val; let haupd; val = payload[channel]['bpSta'][i][state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'statusReportBattery' + (i + 1), state, val, 'statusReportBattery1', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'JTS1_EMS_HEARTBEAT': await setOnlineStatus(adapter, topic); for (let state in payload[channel]) { switch (state) { case 'pcsAPhase': case 'pcsBPhase': case 'pcsCPhase': case 'pcsLoadInfo': for (let neststate in payload[channel][state]) { let val; let haupd; val = payload[channel][state][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state.concat('_', neststate), val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; case 'meterHeartBeat': if ( Object.prototype.hasOwnProperty.call( payload[channel][state], 'meterHeartBeat' ) ) { for (let i = 0; i < payload[channel][state]['meterHeartBeat'].length; i++) { for (let neststate in payload[channel][state]['meterHeartBeat']) { let val; let haupd; val = payload[channel][state][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state.concat('_', neststate, String(i)), val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } break; case 'mpptHeartBeat': if (Object.prototype.hasOwnProperty.call(payload[channel][state], 'mpptPv')) { for (let i = 0; i < payload[channel][state]['mpptPv'].length; i++) { for (let neststate in payload[channel][state]['mpptPv'][i]) { let val; let haupd; val = payload[channel][state]['mpptPv'][i][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state.concat('_mpptPv_', neststate, String(i)), val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } if ( Object.prototype.hasOwnProperty.call(payload[channel][state], 'mpptTempVal') ) { let val; let haupd; val = payload[channel][state]['mpptTempVal']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state.concat('_', 'mpptTempVal'), val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel][state], 'mpptInsResist' ) ) { let val; let haupd; val = payload[channel][state]['mpptInsResist']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state.concat('_', 'mpptInsResist'), val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; default: { let val; let haupd; val = payload[channel][state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state, val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; } } break; case 'ProtoTime': case 'ProtoTimeStat': // channel hart auf 'ProtoTime' gesetzt, damit die ProtoTimeStat in die gleiche Hierarchie schreibt await setOnlineStatus(adapter, topic); for (let state in payload[channel]) { let val; let haupd; switch (state) { case 'masterInfo': if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterInfo'], 'gridWatt' ) ) { val = payload[channel][state]['gridWatt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'master_gridWatt', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterInfo'], 'sysTimezone' ) ) { val = payload[channel][state]['sysTimezone']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'sysTimezone', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterInfo'], 'timezoneId' ) ) { val = payload[channel][state]['timezoneId']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'timezoneId', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; case 'loadInfo': if ( Object.prototype.hasOwnProperty.call( payload[channel]['loadInfo'], 'hall1Watt' ) ) { val = payload[channel][state]['hall1Watt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'hall1Watt', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); let power = 0; for (let i = 0; i < val.length; i++) { haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'loadPower', 'chWatt'.concat('_', String(i)), val[i], 'loadPower', logged ); power = power + val[i]; if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'loadPower', 'sumWatt', power, 'loadPower', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['loadInfo'], 'hall1Curr' ) ) { val = payload[channel][state]['hall1Curr']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'hall1Curr', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); let current = 0; for (let i = 0; i < val.length; i++) { haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'loadCurrent', 'cur'.concat('_', String(i)), val[i], 'loadCurrent', logged ); current = current + val[i]; if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'loadCurrent', 'sumCur', current, 'loadCurrent', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; case 'backupInfo': if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo'], 'chWatt' ) ) { //ggf. hier noch die einzelnen Datenpunkte des arrays durchlaufen val = payload[channel][state]['chWatt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'bkpChWatt', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo'], 'backupDischargeTime' ) ) { val = payload[channel][state]['backupDischargeTime']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'backupDischargeTime', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo'], 'energy_1' ) ) { if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo']['energy_1'], 'chargeTime' ) ) { val = payload[channel][state]['energy_1']['chargeTime']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'bkpEnerg1ChargeTime', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo']['energy_1'], 'dischargeTime' ) ) { val = payload[channel][state]['energy_1']['dischargeTime']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'bkpEnerg1DischargeTime', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo'], 'energy_2' ) ) { if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo']['energy_2'], 'chargeTime' ) ) { val = payload[channel][state]['energy_2']['chargeTime']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'bkpEnerg2ChargeTime', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['backupInfo']['energy_2'], 'dischargeTime' ) ) { val = payload[channel][state]['energy_2']['dischargeTime']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'bkpEnerg2DischargeTime', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'wattInfo': if ( Object.prototype.hasOwnProperty.call( payload[channel]['wattInfo'], 'gridWatt' ) ) { val = payload[channel][state]['gridWatt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'wattInfoGridWatt', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call(payload[channel]['wattInfo'], 'chWatt') ) { let sum = 0; for (let i = 0; i < payload[channel]['wattInfo']['chWatt'].length; i++) { val = payload[channel][state]['chWatt'][i]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'wattInfoChWatt'.concat(String(i + 1)), val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } val = payload[channel][state]['chWatt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'wattInfoChWatt', sum, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['wattInfo'], 'allHallWatt' ) ) { val = payload[channel][state]['allHallWatt']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'wattInfoAllHallWatt', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; case 'masterVerInfo': if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterVerInfo'], 'appMainVer' ) ) { val = payload[channel][state]['appMainVer']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'appMainVer', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterVerInfo'], 'appDbgVer' ) ) { val = payload[channel][state]['appDbgVer']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'appDbgVer', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel]['masterVerInfo'], 'appTestVer' ) ) { val = payload[channel][state]['appTestVer']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'ProtoTime', 'appTestVer', val, 'ProtoTime', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } break; //case timestampPlus für ProtoTimeStat default: break; } } break; case 'ProtoPushAndSet': await setOnlineStatus(adapter, topic); for (let state in payload[channel]) { let val; let haupd; switch (state) { case 'LoadStrategyCfg': for (let neststate in payload[channel][state]) { let val; let haupd; if ( neststate === 'hall1ChInfo' && payload[channel][state]['hall1ChInfo'].length === 12 ) { for (let i = 0; i < 12; i++) { if (payload[channel][state]['hall1ChInfo'][i]['loadIsEnable']) { val = payload[channel][state]['hall1ChInfo'][i]['loadIsEnable']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, 'loadIsEnable' + (i + 1), val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if (payload[channel][state]['hall1ChInfo'][i]['loadPriority']) { val = payload[channel][state]['hall1ChInfo'][i]['loadPriority']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, 'loadPriority' + (i + 1), val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } else { val = payload[channel][state][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate, val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'backupIncreInfo': for (let neststate in payload[channel][state]) { let val; let haupd; if ( neststate === 'Energy1Info' || neststate === 'Energy2Info' || neststate === 'Energy3Info' ) { for (let dp in payload[channel][state][neststate]) { let val; let haupd; if (dp === 'devInfo') { //todo define dps first } else { val = payload[channel][state][neststate][dp]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, neststate, //nested the state is channel dp, val, neststate, //'EnergyInfo', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } else if ( neststate === 'backupCh1Info' || neststate === 'backupCh2Info' || neststate === 'backupCh3Info' ) { for (let dp in payload[channel][state][neststate]) { let val; let haupd; val = payload[channel][state][neststate][dp]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, neststate, //nested the state is channel dp, val, neststate, logged //'backupChInfo' ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } else if (neststate === 'DeltaPro1Info' || neststate === 'DeltaPro2Info') { for (let dp in payload[channel][state][neststate]) { let val; let haupd; if (dp === 'modelInfo') { //todo define dps first } else { val = payload[channel][state][neststate][dp]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, neststate, //nested the state is channel dp, val, neststate, logged //'DeltaProInfo' ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } else if (neststate === 'errcode') { //todo } else { //no nesting val = payload[channel][state][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate, val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'loadIncreInfo': for (let neststate in payload[channel][state]['hall1IncreInfo']) { let val; let haupd; if ( neststate.includes('ch') && (neststate.includes('Info') || neststate.includes('Sta')) ) { for (let dp in payload[channel][state]['hall1IncreInfo'][neststate]) { if (dp === 'splitphase') { if ( Object.prototype.hasOwnProperty.call( payload[channel][state]['hall1IncreInfo'][neststate][ dp ], 'linkMark' ) ) { val = payload[channel][state]['hall1IncreInfo'][neststate][ dp ]['linkMark']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate + '_' + 'linkMark', val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } if ( Object.prototype.hasOwnProperty.call( payload[channel][state]['hall1IncreInfo'][neststate][ dp ], 'linkCh' ) ) { val = payload[channel][state]['hall1IncreInfo'][neststate][ dp ]['linkCh']; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate + '_' + 'linkCh', val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } else { val = payload[channel][state]['hall1IncreInfo'][neststate][dp]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate + '_' + dp, val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } } else if (neststate === 'errCode' || neststate === 'modelInfo') { //not yet } else { val = payload[channel][state]['hall1IncreInfo'][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate, val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'masterIncreInfo': //wie default for (let neststate in payload[channel][state]) { if (neststate === 'errCode') { //not yet } else { let val; let haupd; val = payload[channel][state][neststate]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, state, neststate, val, state, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } break; case 'TimeTaskCfg_1': case 'TimeTaskCfg_2': case 'TimeTaskCfg_3': case 'TimeTaskCfg_4': case 'TimeTaskCfg_5': case 'TimeTaskCfg_6': case 'TimeTaskCfg_7': case 'TimeTaskCfg_8': case 'TimeTaskCfg_9': case 'TimeTaskCfg_10': //timetask has own structure break; default: //directly accessibly datapoints val = payload[channel][state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, channel, state, val, channel, logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); break; } } break; case 'time_task_config_post': for (let task in payload[channel]) { for (let item in payload[channel][task]) { if (item !== 'timeRange') { await adapter.setStateAsync(topic + '.' + channel + '.' + task + '.' + item, { val: item === 'taskIndex' ? parseInt(payload[channel][task][item]) : String(payload[channel][task][item]), ack: true }); } else if (item === 'timeRange') { for (let range in payload[channel][task]['timeRange']) { if (range === 'startTime' || range === 'stopTime') { for (let time in payload[channel][task][item][range]) { await adapter.setStateAsync( topic + '.' + channel + '.' + task + '.' + item + '.' + range + '.' + time, { val: payload[channel][task][item][range][time], ack: true } ); } } else { //taskIndex or type // convert type fehlt noch await adapter.setStateAsync( topic + '.' + channel + '.' + task + '.' + item + '.' + range, { val: String(payload[channel][task][item][range]), ack: true } ); } } } } } break; case 'info': //channel info is only received when offline, no need to check the content await setOfflineStatus(adapter, topic); break; case 'EnergyPack': { let msgobj32 = payload[channel];; let watthType; let energy; //let timeStamp; let energyobj = {}; try { for (let i = 0; i < msgobj32.sysEnergyStream.length; i++) { if ( msgobj32.sysEnergyStream[i].watthType && msgobj32.sysEnergyStream[i].watth && msgobj32.sysEnergyStream[i].timestamp ) { //adapter.log.debug('complete ' + i); //we assume all timestamps are equal and the last is used for historical message // timeStamp = msgobj32.sysEnergyStream[i].timestamp; watthType = msgobj32.sysEnergyStream[i].watthType; energy = msgobj32.sysEnergyStream[i].watth.reduce((a, b) => a + b, 0); energyobj['watth' + watthType] = energy; } else { //adapter.log.debug('incomplete'); //incomplete telegram } } } catch (error) { adapter.log.debug('id 32 error at: ' + error); } if (msgobj32.sysSeq == 65535) { await setOnlineStatus(adapter, topic); for (let state in energyobj) { let val; let haupd; val = energyobj[state]; haupd = await compareUpdate( adapter, stateDictObj, stateObj, haenable, topic, 'energy', state, val, 'energy', logged ); if (Object.keys(haupd).length !== 0) haUpdate.push(haupd); } } else { // send "raw" history data // haUpdate['hist_energy'] = { time: timeStamp, energy: energyobj }; } } break; default: //nothing todo break; } } } else { adapter.log.warn('storeStreamPayload no stateObj'); } } else { adapter.log.warn('storeStreamPayload no stateDictobj'); } //} catch (error) { // adapter.log.debug('store stream payload ' + error); // adapter.log.debug('payload from ' + topic + ' was: ' + JSON.stringify(payload)); //} } else { adapter.log.debug('nothing to process'); } return haUpdate; } async function storeStationPayload(adapter, stateDictObj, stateObj, topic, payload, haenable, logged) { let haUpdate = []; if (payload) { /* if (adapter.config.msgUpdatePstation) { adapter.log.debug( 'got topic ' + topic + ' ( stateDictObj ' + stateDictObj + ' stateObj: ' + stateObj + ' )' ); } */ try { let payloadobj = null; //station cyclically receives params messages, so we can detect from here the online status let onlinestatus = await adapter.getStateAsync(topic + '.info.status').catch((e) => { adapter.log.warn('did not get old value .info.status ->' + e); }); if (payload['params']) { //skipping the incomming data when not valid if (payload['params']['ems.openBmsIdx']) { //only valid when payload ems.openBmsIdx=1 @Deltamax if (payload['params']['ems.openBmsIdx'] !== 0) { payloadobj = payload['params']; } } else if (payload['params']['bmsMaster.bqSysStatReg']) { //only valid when payload bmsMaster.bqSysStatReg = 128 @Deltamax if (payload['params']['bmsMaster.bqSysStatReg'] !== 0) { payloadobj = payload['params']; } } else if (payload['params']['bms_emsStatus.openBmsIdx']) {