iobroker.ecoflow-mqtt
Version:
connects to ecoflow products
1,064 lines (1,032 loc) • 169 kB
JavaScript
const protobuf = require('protobufjs');
const https = require('node: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);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
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 - dictionary of datapoints
* @param {string} channel - upper struct
* @param {string} state - the datapoint
* @param {string | number | boolean} value - the value of datapoint
* @param {object} stateObj - datapoint definitions
*/
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 (state !== 'brightness') {
if (typeof value === 'string' || value instanceof String) {
value = (parseFloat(value) * stateObj[channel][key][state]['mult']).toFixed(2);
} else if (Number.isInteger(value)) {
value = value * stateObj[channel][key][state]['mult'];
} else {
value = Number((value * stateObj[channel][key][state]['mult']).toFixed(2));
}
}
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
*/
/**
* @param adapter - THIS transfer
* @param {import("node:buffer").WithImplicitCoercion<string>} payload - payload of MQTT msg
* @param {any} usage - switch to use it for other cases
* @param {string | number} topic - serial#
* @param {string} msgtype - set/get/update
* @param {any} protoSourceDevice - dictionary
* @param protoMsg - proto definition
* @param {boolean} log - log enable
*/
function pstreamDecode(adapter, payload, usage, topic, msgtype, protoSourceDevice, protoMsg, log) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @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 (cmdFunc) {
if (Object.prototype.hasOwnProperty.call(protoMsg['cmdFunc'], cmdFunc)) {
if (cmdId) {
if (
Object.prototype.hasOwnProperty.call(
protoMsg['cmdFunc'][cmdFunc]['cmdId'],
cmdId,
)
) {
prototyp = protoMsg['cmdFunc'][cmdFunc]['cmdId'][cmdId];
} else {
prototyp = 'undef';
//no decode possible;
if (log === true) {
adapter.log.debug(
`[PROTOBUF decode] ${topic} [${msgtype}] msg#${i} unknown cmdId `,
);
}
//continue;
}
} else {
prototyp = 'undef';
//message without cmdId;
if (log === true) {
adapter.log.debug(
`[PROTOBUF decode] ${topic} [${msgtype}] msg#${i} no cmdId in Message`,
);
}
}
} else {
prototyp = 'undef';
//no decodei possible;
if (log === true) {
adapter.log.debug(
`[PROTOBUF decode] ${topic} [${msgtype}] msg#${i} unknown cmdFunc`,
);
}
}
} else {
prototyp = 'undef';
//message without packageType;
if (log === true) {
adapter.log.debug(
`[PROTOBUF decode] ${topic} [${msgtype}] msg#${i} no cmdFunc in message`,
);
}
if (msgobj.header[i].code) {
if (msgobj.header[i].code == '-2') {
returnobj.push({ info: { status: 'offline' } });
}
}
}
} else {
prototyp = 'undef';
//no objects
adapter.log.debug(
`[PROTOBUF decode] no protoMsg or protoSourceDevice objects ${topic} [${msgtype}] protoMsg ${protoMsg}protoSourceDevice ${
protoSourceDevice
}`,
);
}
// 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.push({ [prototyp]: msg });
} catch (error) {
adapter.log.debug(
`[PROTOBUF decode] ${topic} [${msgtype}] msg#${i} => ` +
` id ${prototyp} error at: ${error}`,
);
}
} else {
writeUnknownMsg(adapter, {
device: String(topic),
topic: msgtype,
cmdFunc: String(cmdFunc),
cmdId: String(cmdId),
buffer: msgobj.header[i].pdata.toString('hex'),
src: src,
});
}
} else {
if (log === true) {
adapter.log.debug(` [PROTOBUF decode] ${topic} [${msgtype}] msg#${i} => no pdata`);
}
}
//Umschreiben, damit das für get_reply nutzbar wird
if (msgobj.header[i].pdata) {
//msgtype === 'get_reply' &&
msgobj.header[i].pdata = msgobj.header[i].pdata.toString('hex');
}
/*
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.push({'energy': energyobj});
} else {
returnobj.push({'hist_energy':{ time: timeStamp, energy: energyobj }});
}
}
}
*/
}
if (msgtype === 'get_reply') {
adapter.quotas[topic] = msgobj;
}
return returnobj;
}
}
} catch (error) {
adapter.log.debug(`[PROTOBUF decode] error -> payload was: ${payload.toString('hex')}`);
throw new Error(`[PROTOBUF decode] -> ${error}`);
}
}
function writeUnknownMsg(adapter, msg) {
if (!adapter.unknownPBmsg[msg.device]) {
adapter.unknownPBmsg[msg.device] = {};
}
if (!adapter.unknownPBmsg[msg.device][msg.topic]) {
adapter.unknownPBmsg[msg.device][msg.topic] = {};
}
if (!adapter.unknownPBmsg[msg.device][msg.topic][msg.cmdFunc]) {
adapter.unknownPBmsg[msg.device][msg.topic][msg.cmdFunc] = {};
}
if (!adapter.unknownPBmsg[msg.device][msg.topic][msg.cmdFunc][msg.cmdId]) {
adapter.unknownPBmsg[msg.device][msg.topic][msg.cmdFunc][msg.cmdId] = {
pbmsg: msg.buffer,
src: msg.src,
dst: msg.dst,
};
}
}
/**
* @param adapter - THIS transfer
* @param {any} topic - serial# of device
*/
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`);
}
}
/**
* @param adapter - THIS transfer
* @param {any} topic - serial# of device
*/
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`);
}
}
/**
* @param adapter - THIS transfer
* @param {object} stateDictObj - dictionary of states
* @param {object} stateObj - datapoint definition
* @param {string} topic - serial#
* @param {string} msgtype - set/get/update
* @param payload - payload
* @param {boolean} haenable - HA enabled for this device
* @param {boolean} logged - log enable
*/
async function storeStationPayload(adapter, stateDictObj, stateObj, topic, msgtype, 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
await setOnlineStatus(adapter, topic);
/*
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']) {
//only valid when payload ems.openBmsIdx=1 @Deltamax
if (payload['params']['bms_emsStatus.openBmsIdx'] !== 0) {
payloadobj = payload['params'];
}
} else if (payload['params']['bms_bmsStatus.bqSysStatReg']) {
//only valid when payload bmsMaster.bqSysStatReg = 128 @Deltamax
if (payload['params']['bms_bmsStatus.bqSysStatReg'] !== 0) {
payloadobj = payload['params'];
}
} else {
payloadobj = payload['params'];
}
/*
if (onlinestatus && onlinestatus.val === 'offline') {
await adapter.setStateAsync(topic + '.info.status', { val: 'online', ack: true });
adapter.log.info('[EF] ' + topic + ' is online with cloud');
}
*/
} else if (payload['data']) {
if (payload['data']['quotaMap']) {
let status = 'OK';
//skipping the incomming data when not valid
if (payload['data']['quotaMap']['ems.openBmsIdx']) {
if (payload['data']['quotaMap']['ems.openBmsIdx'] === 0) {
status = 'unreliable ems';
}
}
if (payload['data']['quotaMap']['bms_emsStatus.openBmsIdx']) {
if (payload['data']['quotaMap']['bms_emsStatus.openBmsIdx'] === 0) {
status = 'unreliable ems';
}
}
if (payload['data']['quotaMap']['bmsMaster.bqSysStatReg']) {
if (payload['data']['quotaMap']['bmsMaster.bqSysStatReg'] === 0) {
status = 'unreliable bmsMaster';
}
}
if (payload['data']['quotaMap']['bms_bmsStatus.bqSysStatReg']) {
if (payload['data']['quotaMap']['bms_bmsStatus.bqSysStatReg'] === 0) {
status = 'unreliable bms';
}
}
if (status === 'OK') {
payloadobj = payload['data']['quotaMap'];
}
/*
if (onlinestatus && onlinestatus.val === 'offline' && payload['data']['online'] === 1) {
await adapter.setStateAsync(topic + '.info.status', { val: 'online', ack: true });
adapter.log.info('[EF] ' + topic + ' is online with cloud');
}
*/
adapter.log.debug(`[${topic}] processing get_reply latest quotas !`);
//storage of latest quotas to object, can then be called from sendTo
if (msgtype === 'get_reply') {
adapter.quotas[topic] = payload;
}
if (payload['data']['quotaMap']['timeTask']) {
//logging
//totalTaskNum als Kennung für Stations (bei SHP1 nicht dabei)
if (payload['data']['timeTask']['totalTaskNum']) {
await adapter.setStateAsync(`${topic}.timeTask.totalTaskNum`, {
val: payload['data']['quotaMap']['timeTask']['totalTaskNum'],
ack: true,
});
let taskarr = payload['data']['quotaMap']['timeTask']['taskCfgs'];
for (let i = 0; i < taskarr.length; i++) {
const task = `task${i}`;
if (taskarr[i]['taskIndex'] !== 255) {
for (let key in taskarr[i]) {
if (key !== 'timeScale') {
await adapter.setStateAsync(`${topic}.timeTask.${task}.${key}`, {
val: taskarr[i][key],
ack: true,
});
} else {
//convert fehlt nich
await adapter.setStateAsync(`${topic}.timeTask.${task}.${key}`, {
val: JSON.stringify(taskarr[i][key]),
ack: true,
});
}
}
}
}
adapter.log.debug('processed get_reply timeTask !');
}
}
} else if (payload['data']['online'] === 0) {
//station reports the data.online=0 as offline
//it is ony part of messages with data (rquested by latestQuotas)
/*
let status = await adapter.getStateAsync(`${topic}.info.status`).catch(e => {
adapter.log.warn(`did not get old value .info.status ->${e}`);
});
*/
await setOfflineStatus(adapter, topic);
/*
if (status && status.val === 'online') {
await adapter.setStateAsync(topic + '.info.status', { val: 'offline', ack: true });
adapter.log.info('[EF] ' + topic + ' is OFFLINE with cloud');
}
*/
}
} else {
adapter.log.debug(`no params or data in payload ->${JSON.stringify(payload)}`);
}
if (payloadobj) {
//stations with dotted syntax
for (let comportion in payloadobj) {
let dotted = comportion.split('.');
let channel = dotted[0];
let state = dotted[1];
if (
channel === 'timestamp' ||
channel === 'latestTimeStamp' ||
state === null ||
channel === 'kit' ||
channel === 'bms_kitInfo' ||
//to avoid crashing when undefined datapoint is uncomming, better the way around to check the known and there is leftover
channel === 'bms_bmsInfo' ||
channel === 'bms_slave_bmsSlaveInfo_1' ||
channel === 'bms_slave_bmsSlaveInfo_2'
) {
//exceptions
} else {
// manipulation of delta2 and delta2max
if (channel === 'bms_bmsStatus' || channel === 'bms') {
channel = 'bmsMaster';
} else if (channel === 'bms_emsStatus') {
channel = 'ems';
} else if (channel === 'bms_slave_bmsSlaveStatus_1') {
channel = 'bmsSlave1';
} else if (channel === 'bms_slave_bmsSlaveStatus_2') {
channel = 'bmsSlave2';
} else if (channel === 'bms_slave') {
//delta2 has only one port issue #264
channel = 'bmsSlave1';
}
const channelsave = channel; // for handling the slaves when converting the value
if (channel === 'bmsSlave1' || channel === 'bmsSlave2') {
channel = 'bmsMaster';
}
if (stateDictObj[channel][state]) {
if (stateDictObj[channel][state]['entity'] !== 'icon') {
let value = convertData(stateDictObj, channel, state, payloadobj[comportion], stateObj);
//adapter.log.debug('converted value ' + value);
let old = await adapter.getStateAsync(`${topic}.${channelsave}.${state}`).catch(e => {
adapter.log.warn(`did not get old value ${topic}.${channelsave}.${state} ->${e}`);
});
//adapter.log.debug('old ' + JSON.stringify(old));
if (!old) {
await adapter.setStateAsync(`${topic}.${channelsave}.${state}`, {
val: value,
ack: true,
});
if (haenable && stateDictObj[channel][state]['entity'] !== 'array') {
let havalue;
const type = stateDictObj[channel][state]['entity'];
const enttype = stateObj[channel][type][state]['entity_type'];
switch (enttype) {
case 'sensor':
havalue = String(value);
break;
case 'switch':
if (value === true) {
havalue = stateObj[channel][type][state]['payload_on'];
} else if (value === false) {
havalue = stateObj[channel][type][state]['payload_off'];
}
break;
case 'select':
try {
havalue = stateObj[channel][type][state]['states'][value];
} catch (error) {
adapter.log.warn(
`select problem ${value} not in ${
stateObj[channel][type][state]['states']
} error -> ${error}`,
);
}
break;
case 'text':
havalue = value;
break;
default:
havalue = String(value);
break;
}
haUpdate.push({
topic: `${adapter.config.haTopic}/${topic}_${channelsave}/${state}`,
payload: havalue,
});
}
} else {
if (old.val !== value) {
if (logged) {
adapter.log.debug(
`station update ` + `.${channelsave}.${state} ${old.val} -> ${value}`,
);
}
await adapter.setStateAsync(`${topic}.${channelsave}.${state}`, {
val: value,
ack: true,
});
if (haenable && stateDictObj[channel][state]['entity'] !== 'array') {
let havalue;
const type = stateDictObj[channel][state]['entity'];
const enttype = stateObj[channel][type][state]['entity_type'];
switch (enttype) {
case 'sensor':
havalue = String(value);
break;
case 'switch':
if (value === true) {
havalue = stateObj[channel][type][state]['payload_on'];
} else if (value === false) {
havalue = stateObj[channel][type][state]['payload_off'];
}
break;
case 'select':
try {
havalue = stateObj[channel][type][state]['states'][value];
} catch (error) {
adapter.log.warn(
`select problem ${value} not in ${
stateObj[channel][type][state]['states']
} error -> ${error}`,
);
}
break;
case 'text':
havalue = value;
break;
default:
havalue = String(value);
break;
}
haUpdate.push({
topic: `${adapter.config.haTopic}/${topic}_${channelsave}/${state}`,
payload: havalue,
});
}
} else {
//even the same value switches get an update
//strange behaviour on HA side, where the switch returns to previous position
// achtung macht aber ordentlich last, da immer mitgeschickt
if (haenable && stateDictObj[channel][state]['entity'] === 'switch') {
let havalue;
const type = stateDictObj[channel][state]['entity'];
const enttype = stateObj[channel][type][state]['entity_type'];
switch (enttype) {
case 'switch':
if (value === true) {
havalue = stateObj[channel][type][state]['payload_on'];
} else if (value === false) {
havalue = stateObj[channel][type][state]['payload_off'];
}
break;
default:
break;
}
haUpdate.push({
topic: `${adapter.config.haTopic}/${topic}_${channel}/${state}`,
payload: havalue,
});
}
}
}
}
} else {
adapter.log.debug(
`[store station] ${topic} not processed ${channelsave} state: ${state} value ${
payloadobj[comportion]
}`,
);
}
}
}
}
} catch (error) {
adapter.log.debug(`store JSON payload ${error}`);
adapter.log.debug(`payload from ${topic} was: ${JSON.stringify(payload)}`);
}
} else {
adapter.log.debug('nothing to process');
}
return haUpdate;
}
/**
* @param adapter - THIS transfer
* @param {object} stateDictObj - dictionary of states
* @param {object} stateObj - datapoint definition
* @param {boolean} haenable - HA enabled for this device
* @param {string} topic - serial#
* @param {string} channel - upper struct
* @param {string} state - datapoint name
* @param {any} val - value of state
* @param {string} origchannel - the channel where references work
* @param {boolean} logged - log enable
*/
async function compareUpdate(
adapter,
stateDictObj,
stateObj,
haenable,
topic,
channel,
state,
val,
origchannel,
logged,
) {
let haUpdate = {};
try {
if (stateDictObj) {
if (stateDictObj[origchannel]) {
if (stateDictObj[origchannel][state]) {
if (stateDictObj[origchannel][state]['entity'] !== 'icon') {
let value = null;
try {
value = convertData(stateDictObj, origchannel, state, val, stateObj);
} catch (error) {
adapter.log.debug(
`CONVERT went wrong ${topic}.${channel}.${state} with ${val} error: ${error}`,
);
}
//adapter.log.debug('converted value ' + value);
let old = await adapter.getStateAsync(`${topic}.${channel}.${state}`).catch(e => {
adapter.log.warn(`[Compare] did not get old value ${topic}.${channel}.${state} ->${e}`);
});
// adapter.log.debug(`old ${JSON.stringify(old)}`);
if (!old) {
if (logged) {
adapter.log.debug(
`[Compare] force update ` + `${topic}.${channel}.${state} -> ${value}`,
);
}
await adapter.setStateAsync(`${topic}.${channel}.${state}`, {
val: value,
ack: true,
});
if (haenable && stateDictObj[origchannel][state]['entity'] !== 'array') {
let havalue;
const type = stateDictObj[origchannel][state]['entity'];
const enttype = stateObj[origchannel][type][state]['entity_type'];
switch (enttype) {
case 'sensor':
case 'number':
havalue = String(value);
break;
case 'switch':
//always a cmd
if (value === true) {
havalue = stateObj[origchannel][type][state]['payload_on'];
} else if (value === false) {
havalue = stateObj[origchannel][type][state]['payload_off'];
}
break;
case 'select':
try {
havalue = stateObj[origchannel][type][state]['states'][val];
} catch (error) {
adapter.log.warn(
`[Compare] select problem ${value} not in ${
stateObj[origchannel][type][state]['states']
} error -> ${error}`,
);
}
break;
case 'text':
havalue = value;
break;
default:
havalue = String(value);
break;
}
haUpdate = {
topic: `${adapter.config.haTopic}/${topic}_${channel}/${state}`,
payload: havalue,
};
}
} else {
if (Object.prototype.hasOwnProperty.call(old, 'val')) {
if (old.val !== value) {
if (logged) {
adapter.log.debug(
`[Compare] update ` + `${topic}.${channel}.${state} ${old.val} -> ${value}`,
);
}
await adapter.setStateAsync(`${topic}.${channel}.${state}`, {
val: value,
ack: true,
});
if (haenable && stateDictObj[origchannel][state]['entity'] !== 'array') {
let havalue;
const type = stateDictObj[origchannel][state]['entity'];
const enttype = stateObj[origchannel][type][state]['entity_type'];
switch (enttype) {
case 'sensor':
case 'number':
havalue = String(value);
break;
case 'switch':
//always a cmd, no need to check the specific type
if (value === true) {
havalue = stateObj[origchannel][type][state]['payload_on'];
} else if (value === false) {
havalue = stateObj[origchannel][type][state]['payload_off'];
} else {
adapter.log.warn(
`[Compare] switch problem ${value} not convertable `,
);
}
break;
case 'select':
//always a cmd, no need to check the specific type
try {
havalue = stateObj[origchannel][type][state]['states'][val];
} catch (error) {
adapter.log.warn(
`[Compare] select problem ${value} not in ${
stateObj[origchannel][type][state]['states']
} error -> ${error}`,
);
}
break;
case 'text':
havalue = value;
break;
default:
havalue = String(value);
break;
}
haUpdate = {
topic: `${adapter.config.haTopic}/${topic}_${channel}/${state}`,
payload: havalue,
};
}
} else {
// even the same value switches get an update
// strange behaviour on HA side, where the switch returns to previous position
// achtung macht aber ordentlich last, da immer mitgeschickt
// erweitert um die anderen CMD typen
if (haenable) {
let havalue;
const type = stateDictObj[origchannel][state]['entity'];
const enttype = stateObj[origchannel][type][state]['entity_type'];
switch (enttype) {
case 'switch':
if (value === true) {
havalue = stateObj[origchannel][type][state]['payload_on'];
haUpdate = {
topic: `${adapter.config.haTopic}/${topic}_${channel}/${state}`,
payload: havalue,
};
} else if (value === false) {
havalue = stateObj[origchannel][type][state]['payload_off'];
haUpdate = {
topic: `${adapter.config.haTopic}/${topic}_${channel}/${state}`,
payload: havalue,
};
} else {
adapter.log.warn(