diff --git a/lib/codecs.js b/lib/codecs.js new file mode 100644 index 0000000..3cec5c3 --- /dev/null +++ b/lib/codecs.js @@ -0,0 +1,643 @@ +let flag_rawmode = false; + +const enums = require('./enums'); + +function arr2Hex(arr) { + // Convert byte array to hex string + var hs = ''; + for (var v in arr) { hs += toHex(arr[v],2) } + return hs; +} + +function toHex(d, len) { + // Convert integer to hex string of length len + return (('00000000'+(Number(d).toString(16))).slice(-len)); +} + +function toByteArray(hs) { + // Convert hex string, e.g. '21A8' to byte array: [33,168] + var ba = []; + for (var i=0; i= Math.pow(2,15)) { v -= Math.pow(2,16); } + return v; +} + +function uint32toVal(j, ofs=0) { + return Math.pow(2,24)*j[ofs+3]+Math.pow(2,16)*j[ofs+2]+Math.pow(2,8)*j[ofs+1]+j[ofs]; +} + +function sint32toVal(j, ofs=0) { + var v = Math.pow(2,24)*j[ofs+3]+Math.pow(2,16)*j[ofs+2]+Math.pow(2,8)*j[ofs+1]+j[ofs]; + if (v >= Math.pow(2,31)) { v -= Math.pow(2,32); } + return v; +} + +function int2val(j, ofs = 0, signed = false) { + // Convert byte array to int08, int16, int32. Signed or unsigned. + switch (j.length) { + case 1 : + return (signed ? sint08toVal(j,ofs) : uint08toVal(j,ofs) ); + break; + case 2 : + return (signed ? sint16toVal(j,ofs) : uint16toVal(j,ofs) ); + break; + case 4 : + return (signed ? sint32toVal(j,ofs) : uint32toVal(j,ofs) ); + break; + default: + return null; + } +} + +function RawEncode(string_ascii, len) { + let string_bin = toByteArray(string_ascii); + if (string_bin.length !== len) { + throw new Error(`String must be ${len} long`); + } + return string_bin; +} + +function RawDecode(string_bin) { + return arr2Hex(string_bin); +} + +class O3ERawCodec { + constructor(string_len, idStr) { + this.string_len = string_len; + this.id = idStr; + } + encode(string_ascii) { + return RawEncode(string_ascii, this.string_len); + } + decode(string_bin) { + return RawDecode(string_bin); + } + __len__() { + return this.string_len; + } +} + +class O3EInt { + constructor(string_len, idStr, byte_width, scale=1.0, offset=0, signed=false) { + this.string_len = string_len; + this.id = idStr; + this.byte_width = byte_width; + this.scale = scale; + this.offset = offset; + this.signed = signed; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } else { + if (this.offset != 0) { + throw new Error("O3EInt.encode(): offset!=0 not implemented yet"); + } + let val = Math.round(eval(string_ascii) * this.scale); + if ( (this.signed) && (val < 0) ) { val += Math.pow(2,8*this.byte_width); } + let string_bin = toByteArray(toHex(val, this.byte_width*2)); + return string_bin.reverse(); + } + } + + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + return int2val(string_bin.slice(this.offset,this.offset+this.byte_width), 0, this.signed) / this.scale; + } + + __len__() { + return this.string_len; + } +} + +// Named parameters in Javascript: https://masteringjs.io/tutorials/fundamentals/parameters +// function (parg1, parg2, { narg1 = 1, narg2 = 2, narg3 = 3 } = {} ) {} + +class O3EInt8 extends O3EInt { + constructor(string_len, idStr, { scale=1.0, offset=0, signed=false } = {}) { + super(string_len, idStr, 1, scale, offset, signed); + } +} + +class O3EInt16 extends O3EInt { + constructor(string_len, idStr, { scale=10.0, offset = 0, signed = false } = {}) { + super(string_len, idStr, 2, scale, offset, signed); + } +} + +class O3EInt32 extends O3EInt { + constructor(string_len, idStr, { scale=1.0, offset = 0, signed = false } = {}) { + super(string_len, idStr, 4, scale, offset, signed); + } +} + +class O3EByteVal { + constructor(string_len, idStr, offset = 0) { + this.string_len = string_len; + this.id = idStr; + this.offset = offset; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } else { + if (this.offset != 0) { + throw new Error("O3EByteVal.encode(): offset!=0 not implemented yet"); + } + let val = Math.round(eval(string_ascii)); + let string_bin = toHex(val, this.string_len*2); + let bytes = toByteArray(string_bin); + return bytes.reverse(); + } + } + + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + let val = 0; + for (let i = 0; i < this.string_len; i++) { + val += string_bin[i + this.offset] << (i * 8); + } + return val; + } + + __len__() { + return this.string_len; + } +} + +class O3EBool { + constructor(string_len, idStr, offset = 0) { + this.string_len = string_len; + this.id = idStr; + this.offset = offset; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("O3EBool.encode(): not implemented yet"); + } + + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + let val = string_bin[this.offset]; + return (val == 0 ? "off" : "on" ); + } + + __len__() { + return this.string_len; + } +} + +class O3EUtf8 { + constructor(string_len, idStr, offset = 0) { + this.string_len = string_len; + this.id = idStr; + this.offset = offset; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("not implemented yet"); + } + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + var mystr = ''; + for (var i=this.offset; i= 2) ? string_bin[1] : 0), // minute + ((this.string_len >= 3) ? string_bin[2] : 0) // second + ); + return dt.toLocaleTimeString(); + } + __len__() { + return this.string_len; + } +} + +class O3EDateTime { + constructor(string_len, idStr, timeformat='VM') { + this.string_len = string_len; + this.id = idStr; + this.timeformat = timeformat; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("not implemented yet"); + } + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + var dt = new Date(); + if (this.timeformat == 'VM') { + 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 + ); + } + if (this.timeformat == 'ts') { + dt = new Date(uint32toVal(string_bin.slice(0,4),0)*1000); + } + return { "DateTime": dt.toLocaleString(), + "Timestamp": Math.round(dt.getTime()) + } + } + __len__() { + return this.string_len; + } +} + +class O3EUtc { + constructor(string_len, idStr) { + this.string_len = string_len; + this.id = idStr; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("not implemented yet"); + } + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + var dt = new Date(uint32toVal(string_bin.slice(0,4),0)*1000); + return dt.toUTCString(); + } + __len__() { + return this.string_len; + } +} + +class O3EEnum { + constructor(string_len, idStr, listStr) { + this.string_len = string_len; + this.id = idStr; + this.listStr = listStr; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("not implemented yet"); + } + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + var val = int2val(string_bin); + var 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 } + } + __len__() { + return this.string_len; + } +} + +class O3EList { + constructor(string_len, idStr, subTypes) { + this.string_len = string_len; + this.id = idStr; + this.subTypes = subTypes; + } + encode(string_ascii) { + if (flag_rawmode == true) { + return RawEncode(string_ascii, this.string_len); + } + throw new Error("not implemented yet"); + } + decode(string_bin) { + if (flag_rawmode == true) { + return RawDecode(string_bin); + } + var result = {}; + var index = 0; + var count = 0; + for (var subType in this.subTypes) { + var subT = this.subTypes[subType]; + 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] = []; + for (var i=0; i= 16" }, "dependencies": { - "@iobroker/adapter-core": "^3.0.4" + "@iobroker/adapter-core": "^3.0.4", + "socketcan": "^3.0.0", + "Buffer": "^0.0.0" }, "devDependencies": { "@alcalzone/release-script": "^3.6.0",