iobroker.ecoflow-mqtt
Version:
connects to ecoflow products
1,757 lines (1,726 loc) • 135 kB
JavaScript
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']) {