UNPKG

iobroker.e3oncan

Version:

Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meters E380CA and E3100CB

1,271 lines (1,214 loc) 35.9 kB
const enums = require('./enums'); const C2POW08 = 0x100; const C2POW15 = 0x08000; const C2POW16 = 0x10000; const C2POW24 = 0x1000000; const C2POW31 = 0x080000000; const C2POW32 = 0x100000000; const C2POW40 = 0x10000000000; const C2POW48 = 0x1000000000000; const C2POW52n = BigInt(0x10000000000000); const C2POW56n = BigInt(0x100000000000000); const C2POW63n = BigInt(0x08000000000000000); const C2POW64n = BigInt(0x10000000000000000); /** * Convert byte array to hex string * * @param {Array} arr byte array */ function arr2Hex(arr) { let hs = ''; for (const v in arr) { hs += toHex(arr[v], 2); } return hs; } /** * Convert integer to hex string of length len * * @param {number} d integer * @param {number} len Lenght of result */ function toHex(d, len) { return `00000000${Number(d).toString(16)}`.slice(-len); } /** * Convert int64 to hex string of length len * * @param {bigint} d 64-bit integer * @param {number} len Lenght of result */ function toHex64(d, len) { return `0000000000000000${d.toString(16)}`.slice(-len); } /** * Convert hex string, e.g. '21A8' to byte array: [33,168] * * @param {string} hs hex string */ function toByteArray(hs) { const ba = []; for (let i = 0; i < hs.length / 2; i++) { ba.push(parseInt(hs.slice(2 * i, 2 * i + 2), 16)); } return ba; } /** * Convert unsigned 8-bit integer to hex string * * @param {Array} j Array of byte */ function uint08toVal(j) { return j[0]; } /** * Convert signed 8-bit integer to hex string * * @param {Array} j Array of byte */ function sint08toVal(j) { return j[0] < 128 ? j[0] : j[0] - 256; } /** * Convert unsigned 16-bit integer to hex string * * @param {Array} j Array of byte */ function uint16toVal(j) { return C2POW08 * j[1] + j[0]; } /** * Convert signed 16-bit integer to hex string * * @param {Array} j Array of byte */ function sint16toVal(j) { let v = C2POW08 * j[1] + j[0]; if (v >= C2POW15) { v -= C2POW16; } return v; } /** * Convert unsigned 32-bit integer to hex string * * @param {Array} j Array of byte */ function uint32toVal(j) { return C2POW24 * j[3] + C2POW16 * j[2] + C2POW08 * j[1] + j[0]; } /** * Convert signed 32-bit integer to hex string * * @param {Array} j Array of byte */ function sint32toVal(j) { let v = C2POW24 * j[3] + C2POW16 * j[2] + C2POW08 * j[1] + j[0]; if (v >= C2POW31) { v -= C2POW32; } return v; } /** * Convert unsigned 64-bit integer to hex string * * @param {Array} j Array of byte */ function uint64toVal(j) { return Number( C2POW56n * BigInt(j[7]) + BigInt(C2POW48) * BigInt(j[6]) + BigInt(C2POW40) * BigInt(j[5]) + BigInt(C2POW32) * BigInt(j[4]) + BigInt(C2POW24) * BigInt(j[3]) + BigInt(C2POW16) * BigInt(j[2]) + BigInt(C2POW08) * BigInt(j[1]) + BigInt(j[0]), ); } /** * Convert signed 64-bit integer to hex string * * @param {Array} j Array of byte */ function sint64toVal(j) { let v = C2POW56n * BigInt(j[7]) + BigInt(C2POW48) * BigInt(j[6]) + BigInt(C2POW40) * BigInt(j[5]) + BigInt(C2POW32) * BigInt(j[4]) + BigInt(C2POW24) * BigInt(j[3]) + BigInt(C2POW16) * BigInt(j[2]) + BigInt(C2POW08) * BigInt(j[1]) + BigInt(j[0]); if (v >= C2POW63n) { v -= C2POW64n; } return Number(v); } /** * Convert byte array to int08, int16, int32. Signed or unsigned. * * @param {Array} j Array of byte * @param {boolean} signed Value is signed */ function int2val(j, signed = false) { switch (j.length) { case 1: return signed ? sint08toVal(j) : uint08toVal(j); case 2: return signed ? sint16toVal(j) : uint16toVal(j); case 4: return signed ? sint32toVal(j) : uint32toVal(j); case 8: return signed ? sint64toVal(j) : uint64toVal(j); default: return null; } } /** * Convert int08, int16, int32 to byte array. Signed or unsigned. * * @param {string} v Value * @param {number} byte_width Byte length of value * @param {number} scale Scaling factor * @param {boolean} signed Signed value yes/no */ function val2byteArr(v, byte_width, scale = 1, signed = false) { let val = Math.round(Number(v) * scale); if (signed && val < 0) { val += 2 ** (8 * byte_width); } const string_bin = toByteArray(toHex(val, byte_width * 2)); return string_bin.reverse(); } function val2byteArr64(v, byte_width, scale = 1, signed = false) { // Convert int64 to byte array. Signed or unsigned. // Due to internal limitations of Javascript this only works correctly for values < 2^52 (4.503.599.627.370.496) let val = BigInt(Math.round(v * scale)); if (val >= C2POW52n || val <= -C2POW52n) { throw new Error( 'O3EInt64.encode(): Value out of range. For encoding only values in range -2**52 < value < 2**52 (4.503.599.627.370.496) are allowed!', ); } if (signed && val < 0) { val += C2POW64n; } const string_bin = toByteArray(toHex64(val, byte_width * 2)); return string_bin.reverse(); } function RawEncode(data, len) { const string_bin = toByteArray(data); if (string_bin.length !== len) { throw new Error(`String must be ${len} long`); } return string_bin; } function RawDecode(string_bin) { return arr2Hex(string_bin); } /** * Codec for UDSonCAN: Raw Codec: Just pass raw data */ class O3ERawCodec { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {Array} data Raw data */ encode(data) { return RawEncode(data, this.string_len); } /** * @param {string} string_bin Raw data (string of hex bytes) */ decode(string_bin) { return RawDecode(string_bin); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Integer numbers */ class O3EInt { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {number} byte_width Byte width of passed integer * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, byte_width, _args, _devUnits) { this.string_len = string_len; this.id = idStr; this.byte_width = byte_width; this.scale = _args.scale; this.signed = _args.signed; } /** * @param {string} data Raw data */ encode(data) { if (this.byte_width == 8) { return val2byteArr64(data, this.byte_width, this.scale, this.signed); } return val2byteArr(data, this.byte_width, this.scale, this.signed); } /** * @param {Array} data Array of bytes */ decode(data) { return int2val(data.slice(0, this.byte_width), this.signed) / this.scale; } /** * Returns length of raw data */ __len__() { return this.string_len; } } // Named parameters in Javascript: https://masteringjs.io/tutorials/fundamentals/parameters // function (parg1, parg2, { narg1 = 1, narg2 = 2, narg3 = 3 } = {} ) {} /** * Codec for UDSonCAN: 8-Bit integer */ class O3EInt8 extends O3EInt { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { super(string_len, idStr, 1, _args, _devUnits); } } /** * Codec for UDSonCAN: 16-Bit integer */ class O3EInt16 extends O3EInt { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { super(string_len, idStr, 2, _args, _devUnits); } } /** * Codec for UDSonCAN: 32-Bit integer */ class O3EInt32 extends O3EInt { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { super(string_len, idStr, 4, _args, _devUnits); } } /** * Codec for UDSonCAN: 64-Bit integer */ class O3EInt64 extends O3EInt { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { super(string_len, idStr, 8, _args, _devUnits); } } /** * Codec for UDSonCAN: Single byte */ class O3EByteVal { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { return val2byteArr(data, this.string_len, 1, false); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { let val = 0; for (let i = 0; i < this.string_len; i++) { val += string_bin[i] << (i * 8); } return val; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Float32 number */ class O3EFloat32 { // IEEE-754 Converter for float32: https://www.h-schmidt.net/FloatConverter/IEEE754de.html // Convert byte values to float: https://stackoverflow.com/questions/4414077/read-write-bytes-of-float-in-js /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; this.scale = _args.scale; } /** * @param {number} _data Float32 value */ encode(_data) { throw new Error('O3EFloat32.encode(): not implemented yet'); } /** * @param {Array} string_bin Raw data (arry of bytes) */ decode(string_bin) { const buffer = new ArrayBuffer(4); const intView = new Int32Array(buffer); const floatView = new Float32Array(buffer); intView[0] = int2val(string_bin, false); return floatView[0] / this.scale; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Boolean value */ class O3EBool { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { return data == 'off' ? 0 : 1; } /** * @param {number} string_bin Raw data (number) */ decode(string_bin) { const val = string_bin[0]; return val == 0 ? 'off' : 'on'; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Utf8 string */ class O3EUtf8 { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { let result; try { result = Object.values(new TextEncoder().encode(data)); } catch (e) { throw new Error(`O3EUtf8.encode(): Conversion from Utf8 failed ${this.id}; err=${JSON.stringify(e)}`); } if (result.length > this.string_len) { throw new Error( `O3EUtf8.encode(): Result too long: ${this.id} - ${String(result.length)} > ${String(this.string_len)}`, ); } return result.concat(Array.from(Array(this.string_len - result.length), () => 0)); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { let i = string_bin.slice(0).indexOf(0); if (i == -1) { i = string_bin.length; } let result; try { result = new TextDecoder().decode(new Uint8Array(string_bin.slice(0, i))); } catch (e) { throw new Error(`O3EUtf8.decode(): Conversion to Utf8 failed ${this.id}; err=${JSON.stringify(e)}`); } return result; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Software version */ class O3ESoftVers { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { let result = []; const arr = data.split('.'); for (const v of Object.values(arr)) { result = result.concat(val2byteArr(v, 2, 1, false)); } if (result.length > this.string_len) { throw new Error( `O3ESoftVers.encode() result too long: ${this.id} - ${String(result.length)} > ${String( this.string_len, )}`, ); } return result.concat(Array.from(Array(this.string_len - result.length), () => 0)); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { const lstv = []; for (let i = 0; i < this.string_len; i += 2) { lstv.push(string_bin[i] + (string_bin[i + 1] << 8)); } return lstv.join('.'); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: MAC-Address */ class O3EMacAddr { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { return data.split('-').map(function (str) { return parseInt(`0x${str}`); }); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { const lstv = []; for (let i = 0; i < this.string_len; i++) { lstv.push(toHex(string_bin[i], 2).toUpperCase()); } return lstv.join('-'); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: IPv4-address */ class O3EIp4Addr { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; } /** * @param {string} data Raw data */ encode(data) { return data.split('.').map(function (str) { return parseInt(str); }); } /** * @param {string} string_bin Raw data (string of hex bytes) */ decode(string_bin) { const lstv = []; for (let i = 0; i < this.string_len; i++) { lstv.push(string_bin[i].toString().padStart(3, '0')); } return lstv.join('.'); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Date */ class O3ESdate { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.devUnits = _devUnits; } /** * @param {number[]} data Raw data */ encode(data) { const dt = new Date(data); if (!dt.valueOf()) { throw new Error('could not convert date value'); } let sbin; const du = this.devUnits.split(' / '); const devDateFomrat = du.length == 3 ? du[1] : 'DayMonthYear'; switch (devDateFomrat) { case 'YearMonthDay': sbin = [dt.getFullYear() % 100, dt.getMonth() + 1, dt.getDate()]; break; case 'MonthDayYear': sbin = [dt.getMonth() + 1, dt.getDate(), dt.getFullYear() % 100]; break; case 'DayMonthYear': default: sbin = [dt.getDate(), dt.getMonth() + 1, dt.getFullYear() % 100]; break; } return sbin; } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { let dt; const du = this.devUnits.split(' / '); const devDateFomrat = du.length == 3 ? du[1] : 'DayMonthYear'; switch (devDateFomrat) { case 'YearMonthDay': dt = new Date( string_bin[0] + 2000, // year string_bin[1] - 1, // month string_bin[2], // day ); break; case 'MonthDayYear': dt = new Date( string_bin[2] + 2000, // year string_bin[0] - 1, // month string_bin[1], // day ); break; case 'DayMonthYear': default: dt = new Date( string_bin[2] + 2000, // year string_bin[1] - 1, // month string_bin[0], // day ); break; } return dt.toDateString(); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Time */ class O3EStime { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.devUnits = _devUnits; } /** * @param {string} data Raw data */ encode(data) { if (this.string_len == 1) { data += ':00'; } // Date() needs at least hour:minute const now = new Date(); const dt = new Date(`${now.toDateString()} ${data}`); if (!dt.valueOf()) { throw new Error('could not convert time value'); } const retVal = [dt.getHours(), dt.getMinutes(), dt.getSeconds()]; return retVal.slice(0, this.string_len); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { const now = new Date(); const dt = new Date( now.getFullYear(), now.getMonth(), now.getDate(), string_bin[0], // hour this.string_len >= 2 ? string_bin[1] : 0, // minute this.string_len >= 3 ? string_bin[2] : 0, // second ); return dt.toLocaleTimeString(); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Date and Time */ class O3EDateTime { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.timeformat = _args.timeformat; this.devUnits = _devUnits; } /** * @param {object} data Raw data */ encode(data) { if (this.timeformat == 'VM') { const dt = new Date(data.Timestamp); if (!dt.valueOf()) { throw new Error('could not convert datetime value'); } const fill = 0x05; // Unknown byte between date and time. Known values are 0x05 and 0x06 const du = this.devUnits.split(' / '); const devDateFomrat = du.length == 3 ? du[1] : 'DayMonthYear'; let res = []; switch (devDateFomrat) { case 'YearMonthDay': res = [ dt.getDate(), dt.getMonth() + 1, Math.floor(dt.getFullYear() / 100), dt.getFullYear() % 100, fill, dt.getHours(), dt.getMinutes(), dt.getSeconds(), ]; break; case 'MonthDayYear': res = [ Math.floor(dt.getFullYear() / 100), dt.getFullYear() % 100, dt.getDate(), dt.getMonth() + 1, fill, dt.getHours(), dt.getMinutes(), dt.getSeconds(), ]; break; case 'DayMonthYear': default: res = [ Math.floor(dt.getFullYear() / 100), dt.getFullYear() % 100, dt.getMonth() + 1, dt.getDate(), fill, dt.getHours(), dt.getMinutes(), dt.getSeconds(), ]; break; } return res; } return val2byteArr(data.Timestamp, this.string_len, 0.001, false); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { let dt = new Date(); if (this.timeformat == 'VM') { const du = this.devUnits.split(' / '); const devDateFomrat = du.length == 3 ? du[1] : 'DayMonthYear'; switch (devDateFomrat) { case 'YearMonthDay': dt = new Date( string_bin[2] * 100 + string_bin[3], // year string_bin[1] - 1, // month string_bin[0], // day string_bin[5], // hour string_bin[6], // minute string_bin[7], // second ); break; case 'MonthDayYear': dt = new Date( string_bin[0] * 100 + string_bin[1], // year string_bin[3] - 1, // month string_bin[2], // day string_bin[5], // hour string_bin[6], // minute string_bin[7], // second ); break; case 'DayMonthYear': default: dt = new Date( string_bin[0] * 100 + string_bin[1], // year string_bin[2] - 1, // month string_bin[3], // day string_bin[5], // hour string_bin[6], // minute string_bin[7], // second ); break; } } if (this.timeformat == 'ts') { dt = new Date(uint32toVal(string_bin.slice(0, 4)) * 1000); } return { DateTime: dt.toLocaleString(), Timestamp: Math.round(dt.getTime()) }; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Utc coded Date and Time */ class O3EUtc { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.devUnits = _devUnits; } /** * @param {number[]} data Raw data */ encode(data) { const dt = new Date(data); if (!dt) { throw new Error('could not convert Utc date value'); } return val2byteArr(String(dt.getTime()), this.string_len, 0.001, false); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { const dt = new Date(uint32toVal(string_bin.slice(0, 4)) * 1000); return dt.toUTCString(); } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Enumeration */ class O3EEnum { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; this.listStr = _args.listStr; } /** * @param {object} data Raw data */ encode(data) { return val2byteArr(data.ID, this.string_len, 1, false); } /** * @param {Array} string_bin Raw data (string of bytes) */ decode(string_bin) { const val = int2val(string_bin); let txt = ''; if (this.listStr in enums.enums && String(val) in enums.enums[this.listStr]) { txt = enums.enums[this.listStr][String(val)]; } else { txt = `Enum not found in ${this.listStr}`; } return { ID: val, Text: txt }; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: List of subs */ class O3EList { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.subTypes = _args.subTypes; this.devUnits = _devUnits; } /** * @param {Array} data Raw data */ encode(data) { let result = []; for (const cdi of Object.values(this.subTypes)) { const subT = new O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args, this.devUnits); if (cdi.args.subTypes) { for (const dataElement of Object.values(data[cdi.id])) { result = result.concat(subT.encode(dataElement)); } } else { result = result.concat(subT.encode(data[cdi.id])); } } if (result.length > this.string_len) { throw new Error(`O3EList.encode() result too long: ${String(result.length)} > ${String(this.string_len)}`); } return result.concat(Array.from(Array(this.string_len - result.length), () => 0)); } /** * @param {string} string_bin Raw data (string of hex bytes) */ decode(string_bin) { const result = {}; let index = 0; let count = 0; for (const cdi of Object.values(this.subTypes)) { const subT = new O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args, this.devUnits); if (subT.id.toLowerCase() == 'count') { count = subT.decode(string_bin.slice(index, index + subT.string_len)); result[subT.id] = count; index += subT.string_len; } else { if ('subTypes' in subT) { // O3EComplexType result[subT.id] = []; if (count <= 100) { for (let i = 0; i < count; i++) { result[subT.id].push(subT.decode(string_bin.slice(index, index + subT.string_len))); index += subT.string_len; } } else { result[subT.id] = ['Implausible number of elements. Decoding aborted.']; } } else { result[subT.id] = subT.decode(string_bin.slice(index, index + subT.string_len)); index += subT.string_len; } } } return result; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Array */ class O3EArray { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.subTypes = _args.subTypes; this.len = _args.arrayLength; this.devUnits = _devUnits; } /** * @param {number} _data Float32 value */ encode(_data) { throw new Error('not implemented yet'); } /** * @param {string} string_bin Raw data (string of hex bytes) */ decode(string_bin) { const result = {}; let index = 0; const count = this.len; for (const cdi of Object.values(this.subTypes)) { const subT = new O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args, this.devUnits); result[subT.id] = []; for (let i = 0; i < count; i++) { result[subT.id].push(subT.decode(string_bin.slice(index, index + subT.string_len))); index += subT.string_len; } } return result; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Complex DID structure */ class O3EComplexType { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits = '') { this.string_len = string_len; this.id = idStr; this.subTypes = _args.subTypes; this.devUnits = _devUnits; } /** * @param {Array} data Raw data */ encode(data) { let result = []; for (const cdi of Object.values(this.subTypes)) { const subT = new O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args, this.devUnits); result = result.concat(subT.encode(data[cdi.id])); } if (result.length > this.string_len) { throw new Error( `O3EComplexType.encode() result too long: ${this.id} - ${String(result.length)} > ${String( this.string_len, )}`, ); } return result.concat(Array.from(Array(this.string_len - result.length), () => 0)); } /** * @param {string} string_bin Raw data (string of hex bytes) */ decode(string_bin) { const result = {}; let index = 0; for (const cdi of Object.values(this.subTypes)) { const subT = new O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args, this.devUnits); result[subT.id] = subT.decode(string_bin.slice(index, index + subT.string_len)); index += subT.string_len; } return result; } /** * Returns length of raw data */ __len__() { return this.string_len; } } /** * Codec for UDSonCAN: Energy Meter: Value of Cosinus Phi */ class O3EcosPhi { /** * @param {number} string_len Length of raw data * @param {string} idStr Data ID (DID) * @param {object} _args Additional parameters * @param {string} _devUnits Units and formats of device */ constructor(string_len, idStr, _args, _devUnits) { this.string_len = string_len; this.id = idStr; this.scale = _args.scale; } /** * @param {number} _data Float32 value */ encode(_data) { throw new Error('not implemented yet'); } /** * @param {Array} string_bin Raw data (array of bytes) */ decode(string_bin) { let val = string_bin[1]; if (string_bin[0] == 0x04) { val = -1.0 * val; } return val / this.scale; } /** * Returns length of raw data */ __len__() { return this.string_len; } } const O3Ecodecs = { RawCodec: O3ERawCodec, O3EInt8: O3EInt8, O3EInt16: O3EInt16, O3EInt32: O3EInt32, O3EInt64: O3EInt64, O3EByteVal: O3EByteVal, O3EFloat32: O3EFloat32, O3EBool: O3EBool, O3EUtf8: O3EUtf8, O3ESoftVers: O3ESoftVers, O3EMacAddr: O3EMacAddr, O3EIp4Addr: O3EIp4Addr, O3ESdate: O3ESdate, O3EStime: O3EStime, O3EDateTime: O3EDateTime, O3EUtc: O3EUtc, O3EEnum: O3EEnum, O3EList: O3EList, O3EArray: O3EArray, O3EComplexType: O3EComplexType, O3EcosPhi: O3EcosPhi, }; module.exports = { O3Ecodecs, arr2Hex, toByteArray, val2byteArr, };