watteco / codec-report-standard-javascript Goto Github PK
View Code? Open in Web Editor NEWA command line script to decode a ZCL payload at a given port.
License: Other
A command line script to decode a ZCL payload at a given port.
License: Other
Using the js function or the online decoder (https://lora.watteco.fr/Lora/?trame=110A00560100411F27000102030405426f6e6a6f757220210080450102030405060F0880000000&MySelectMenu=0&+submit=Submit) I do not have the same result for the PTCOUR1.
It seems that the online decoder is correct.
hey, i'm having error codes in chirpstack trying to get this decoder to work. Can you help me?
code:
/*
// {{{ Constants
var ST_UNDEF = 0
var ST_BL = 1
var ST_U4 = 2
var ST_I4 = 3
var ST_U8 = 4
var ST_I8 = 5
var ST_U16 = 6
var ST_I16 = 7
var ST_U24 = 8
var ST_I24 = 9
var ST_U32 = 10
var ST_I32 = 11
var ST_FL = 12
var ST = {}
ST[ST_UNDEF] = 0
ST[ST_BL] = 1
ST[ST_U4] = 4
ST[ST_I4] = 4
ST[ST_U8] = 8
ST[ST_I8] = 8
ST[ST_U16] = 16
ST[ST_I16] = 16
ST[ST_U24] = 24
ST[ST_I24] = 24
ST[ST_U32] = 32
ST[ST_I32] = 32
ST[ST_FL] = 32
var BR_HUFF_MAX_INDEX_TABLE = 14
var NUMBER_OF_SERIES = 16
var HUFF = [
[
{ sz: 2, lbl: 0x000 },
{ sz: 2, lbl: 0x001 },
{ sz: 2, lbl: 0x003 },
{ sz: 3, lbl: 0x005 },
{ sz: 4, lbl: 0x009 },
{ sz: 5, lbl: 0x011 },
{ sz: 6, lbl: 0x021 },
{ sz: 7, lbl: 0x041 },
{ sz: 8, lbl: 0x081 },
{ sz: 10, lbl: 0x200 },
{ sz: 11, lbl: 0x402 },
{ sz: 11, lbl: 0x403 },
{ sz: 11, lbl: 0x404 },
{ sz: 11, lbl: 0x405 },
{ sz: 11, lbl: 0x406 },
{ sz: 11, lbl: 0x407 }
],
[
{ sz: 7, lbl: 0x06f },
{ sz: 5, lbl: 0x01a },
{ sz: 4, lbl: 0x00c },
{ sz: 3, lbl: 0x003 },
{ sz: 3, lbl: 0x007 },
{ sz: 2, lbl: 0x002 },
{ sz: 2, lbl: 0x000 },
{ sz: 3, lbl: 0x002 },
{ sz: 6, lbl: 0x036 },
{ sz: 9, lbl: 0x1bb },
{ sz: 9, lbl: 0x1b9 },
{ sz: 10, lbl: 0x375 },
{ sz: 10, lbl: 0x374 },
{ sz: 10, lbl: 0x370 },
{ sz: 11, lbl: 0x6e3 },
{ sz: 11, lbl: 0x6e2 }
],
[
{ sz: 4, lbl: 0x009 },
{ sz: 3, lbl: 0x005 },
{ sz: 2, lbl: 0x000 },
{ sz: 2, lbl: 0x001 },
{ sz: 2, lbl: 0x003 },
{ sz: 5, lbl: 0x011 },
{ sz: 6, lbl: 0x021 },
{ sz: 7, lbl: 0x041 },
{ sz: 8, lbl: 0x081 },
{ sz: 10, lbl: 0x200 },
{ sz: 11, lbl: 0x402 },
{ sz: 11, lbl: 0x403 },
{ sz: 11, lbl: 0x404 },
{ sz: 11, lbl: 0x405 },
{ sz: 11, lbl: 0x406 },
{ sz: 11, lbl: 0x407 }
]
]
// }}}
// {{{ Polyfills
Math.trunc =
Math.trunc ||
function(x) {
if (isNaN(x)) {
return NaN
}
if (x > 0) {
return Math.floor(x)
}
return Math.ceil(x)
}
// }}}
/**
out.batch_counter = buffer.getNextSample(ST_U8, 3)
buffer.getNextSample(ST_U8, 1)
var temp = prePopulateOutput(out, buffer, argList, flag, tagsz)
var last_timestamp = temp.last_timestamp
var index_of_the_first_sample = temp.index_of_the_first_sample
if (flag.hasSample) {
last_timestamp = uncompressSamplesData(
out,
buffer,
index_of_the_first_sample,
argList,
last_timestamp,
flag,
tagsz
)
}
out.batch_relative_timestamp = extractTimestampFromBuffer(
buffer,
last_timestamp
)
return adaptToExpectedFormat(out, argList, batch_absolute_timestamp)
}
/////////////// Sub functions ///////////////
/**
/**
return {
index: 0,
byteArray: byteArray,
getNextSample: function(sampleType, nbBitsInput) {
var nbBits = nbBitsInput || ST[sampleType]
var sourceBitStart = this.index
this.index += nbBits
if (sampleType === ST_FL && nbBits !== 32) {
throw "Mauvais sampletype"
}
var u32 = 0
var nbytes = Math.trunc((nbBits - 1) / 8) + 1
var nbitsfrombyte = nbBits % 8
if (nbitsfrombyte === 0 && nbytes > 0) {
nbitsfrombyte = 8
}
while (nbytes > 0) {
var bittoread = 0
while (nbitsfrombyte > 0) {
var idx = sourceBitStart >> 3
if (this.byteArray[idx] & (1 << (sourceBitStart & 0x07))) {
u32 |= 1 << ((nbytes - 1) * 8 + bittoread)
}
nbitsfrombyte--
bittoread++
sourceBitStart += 1
}
nbytes--
nbitsfrombyte = 8
}
// Propagate the sign bit if 1
if (
(sampleType == ST_I4 || sampleType == ST_I8 ||sampleType == ST_I16 || sampleType == ST_I24) &&
u32 & (1 << (nbBits - 1))
) {
for (var i = nbBits; i < 32; i++) {
u32 |= 1 << i
nbBits++
}
}
return u32
},
/**
* Extract sz and bi from Huff table
*/
getNextBifromHi: function(huff_coding) {
for (var i = 2; i < 12; i++) {
var lhuff = bitsBuf2HuffPattern(this.byteArray, this.index, i)
for (var j = 0; j < HUFF[huff_coding].length; j++) {
if (
HUFF[huff_coding][j].sz == i &&
lhuff == HUFF[huff_coding][j].lbl
) {
this.index += i
return j
}
}
}
throw "Bi not found in HUFF table"
}
}
}
/**
/**
// leftpad
while (binbase.length < 8) {
binbase = "0" + binbase
}
return {
isCommonTimestamp: parseInt(binbase[binbase.length - 2], 2),
hasSample: !parseInt(binbase[binbase.length - 3], 2),
batch_req: parseInt(binbase[binbase.length - 4], 2),
nb_of_type_measure: parseInt(binbase.substring(0, 4), 2)
}
}
/**
Prepopulate output with relative timestamp and measure of the first sample
for each series.
*/
function prePopulateOutput(out, buffer, argList, flag, tagsz) {
var currentTimestamp = 0
var index_of_the_first_sample = 0
for (var i = 0; i < flag.nb_of_type_measure; i++) {
var tag = {
size: tagsz,
lbl: buffer.getNextSample(ST_U8, tagsz)
}
var sampleIndex = findIndexFromArgList(argList, tag)
if (i == 0) {
index_of_the_first_sample = sampleIndex
}
currentTimestamp = extractTimestampFromBuffer(buffer, currentTimestamp)
out.series[sampleIndex] = computeSeries(
buffer,
argList[sampleIndex].sampletype,
tag.lbl,
currentTimestamp
)
if (flag.hasSample) {
out.series[sampleIndex].codingType = buffer.getNextSample(ST_U8, 2)
out.series[sampleIndex].codingTable = buffer.getNextSample(ST_U8, 2)
}
}
return {
last_timestamp: currentTimestamp,
index_of_the_first_sample: index_of_the_first_sample
}
}
/**
/**
/**
/**
/**
/**
function getMeasure(buffer, sampletype) {
var v = buffer.getNextSample(sampletype)
return sampletype === ST_FL ? bytes2Float32(v) : v
}
/**
if (exponent == 128) {
return sign * (significand ? Number.NaN : Number.POSITIVE_INFINITY)
}
if (exponent == -127) {
if (significand == 0) {
return sign * 0.0
}
exponent = -126
significand /= 1 << 22
} else {
significand = (significand | (1 << 23)) / (1 << 23)
}
return sign * significand * Math.pow(2, exponent)
}
/**
/**
var temp = initTimestampCommonTable(
out,
buffer,
nb_sample_to_parse,
index_of_the_first_sample
)
var timestampCommon = temp.timestampCommon
var lastTimestamp = temp.lastTimestamp
for (var j = 0; j < flag.nb_of_type_measure; j++) {
var first_null_delta_value = 1
tag.lbl = buffer.getNextSample(ST_U8, tagsz)
var sampleIndex = findIndexFromArgList(argList, tag)
for (var i = 0; i < nb_sample_to_parse; i++) {
//Available bit
var available = buffer.getNextSample(ST_U8, 1)
if (available) {
//Delta value
var bi = buffer.getNextBifromHi(out.series[sampleIndex].codingTable)
var currentMeasure = {
data_relative_timestamp: 0,
data: {}
}
if (bi <= BR_HUFF_MAX_INDEX_TABLE) {
var precedingValue =
out.series[sampleIndex].uncompressSamples[
out.series[sampleIndex].uncompressSamples.length - 1
].data.value
if (bi > 0) {
currentMeasure.data.value = completeCurrentMeasure(
buffer,
precedingValue,
out.series[sampleIndex].codingType,
argList[sampleIndex].resol,
bi
)
} else {
// (bi <= 0)
if (first_null_delta_value) {
// First value is yet recorded starting from the header
first_null_delta_value = 0
continue
} else {
currentMeasure.data.value = precedingValue
}
}
} else {
// bi > BR_HUFF_MAX_INDEX_TABLE
currentMeasure.data.value = buffer.getNextSample(
argList[sampleIndex].sampletype
)
}
currentMeasure.data_relative_timestamp = timestampCommon[i]
out.series[sampleIndex].uncompressSamples.push(currentMeasure)
}
}
}
return lastTimestamp
}
/**
/**
/**
/**
/**
Translate brUncompress output data to expected structure
*/
function adaptToExpectedFormat(out, argList, batchAbsoluteTimestamp) {
var returnedGlobalObject = {
//batch_counter: out.batch_counter,
//batch_relative_timestamp: out.batch_relative_timestamp
}
if (batchAbsoluteTimestamp) {
returnedGlobalObject.b_ts = batchAbsoluteTimestamp
}
returnedGlobalObject.datas = out.series.reduce(function(
acc,
current,
index
) {
return acc.concat(
current.uncompressSamples.map(function(item) {
var returned = {
//data_relative_timestamp: item.data_relative_timestamp,
data: {
value: argList[index].divide
? item.data.value / argList[index].divide
: item.data.value,
}
}
if (argList[index].lblname) {
returned.data.label = argList[index].lblname
}
if (batchAbsoluteTimestamp) {
returned.date = computeDataAbsoluteTimestamp(
batchAbsoluteTimestamp,
out.batch_relative_timestamp,
item.data_relative_timestamp
)
}
return returned
})
)
},
[])
return returnedGlobalObject
}
/**
try {
module.exports = brUncompress
} catch (e) {
// when called from nashorn, module.exports is unavailable…
}
function UintToInt(Uint, Size) {
if (Size === 2) {
if ((Uint & 0x8000) > 0) {
Uint = Uint - 0x10000;
}
}
if (Size === 3) {
if ((Uint & 0x800000) > 0) {
Uint = Uint - 0x1000000;
}
}
if (Size === 4) {
if ((Uint & 0x80000000) > 0) {
Uint = Uint - 0x100000000;
}
}
return Uint;
}
function decimalToHex(d, padding) {
var hex = Number(d).toString(16).toUpperCase();
padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
while (hex.length < padding) {
hex = "0" + hex;
}
return "0x" + hex;
}
function Bytes2Float32(bytes) {
var sign = (bytes & 0x80000000) ? -1 : 1;
var exponent = ((bytes >> 23) & 0xFF) - 127;
var significand = (bytes & ~(-1 << 23));
if (exponent == 128)
return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);
if (exponent == -127) {
if (significand == 0) return sign * 0.0;
exponent = -126;
significand /= (1 << 22);
}
else significand = (significand | (1 << 23)) / (1 << 23);
return sign * significand * Math.pow(2, exponent);
}
function Decode(fport, bytes) {
// Decode an uplink message from a buffer
// (array) of bytes to an object of fields.
var decoded = {};
var decodedBatch = {};
var lora = {};
// decoded.lora.port = port;
// Get raw payload
var bytes_len_ = bytes.length;
var temp_hex_str = ""
lora.payload = "";
for( var j = 0; j < bytes_len_; j++ ){
temp_hex_str = bytes[j].toString( 16 ).toUpperCase( );
if( temp_hex_str.length == 1 ){
temp_hex_str = "0" + temp_hex_str;
}
lora.payload += temp_hex_str;
}
var date = new Date();
var lDate = date.toISOString();
if (port === 125){
//batch
decodedBatch = !(bytes[0] & 0x01);
//trame standard
if (decodedBatch === false){
decoded.zclheader = {};
decoded.zclheader.report = "standard";
attributID = -1;
cmdID = -1;
clusterdID = -1;
//endpoint
decoded.zclheader.endpoint = ((bytes[0]&0xE0)>>5) | ((bytes[0]&0x06)<<2);
//command ID
cmdID = bytes[1]; decoded.zclheader.cmdID = decimalToHex(cmdID,2);
//Cluster ID
clusterdID = bytes[2]*256 + bytes[3]; decoded.zclheader.clusterdID = decimalToHex(clusterdID,4);
// decode report and read atrtribut response
if((cmdID === 0x0a)|(cmdID === 0x8a)|(cmdID === 0x01)){
stdData = {};
var tab=[];
//Attribut ID
attributID = bytes[4]*256 + bytes[5]; decoded.zclheader.attributID = decimalToHex(attributID,4);
if (cmdID === 0x8a) {
decoded.zclheader.alarm = 1;
}
else {
decoded.zclheader.alarm = 0;
}
//data index start
if ((cmdID === 0x0a) | (cmdID === 0x8a)) index = 7;
// if (cmdID === 0x01) {index = 8; decoded.zclheader.status = bytes[6];}
//simple metering
if ( (clusterdID === 0x0052 ) & (attributID === 0x0000)) {
tab.push({label: "ActiveEnergyWh", value: UintToInt(bytes[index+1]*256*256+bytes[index+2]*256+bytes[index+3],3)/1000, date: lDate});
tab.push({label: "ReActiveEnergyVARh", value: UintToInt(bytes[index+4]*256*256+bytes[index+5]*256+bytes[index+6],3)/1000, date: lDate});
tab.push({label: "NumberOfSample", value: (bytes[index+7]*256+bytes[index+8])/1000, date: lDate});
tab.push({label: "ActivePowerW", value: UintToInt(bytes[index+9]*256+bytes[index+10],2)/1000, date: lDate});
tab.push({label: "ReActivePowerVAR", value: UintToInt(bytes[index+11]*256+bytes[index+12],2)/1000, date: lDate});
}
// on/off present value
if ( (clusterdID === 0x0006 ) & (attributID === 0x0000)) {
state = bytes[index];
if(state === 0) {
tab.push({label: "Output"+(decoded.zclheader.endpoint+1), value: "OFF", date: lDate});
}
if(state === 1) {
tab.push({label: "Output"+(decoded.zclheader.endpoint+1), value: "ON", date: lDate});
}
}
// power quality
if ((clusterdID === 0x8052) & (attributID === 0x0000)) {
tab.push({label: "Freq", value: (bytes[index+1]*256+bytes[index+2]+22232)/1000, date: lDate});
tab.push({label: "FreqMin", value: (bytes[index+3]*256+bytes[index+4]+22232)/1000, date: lDate});
tab.push({label: "FreqMax", value: (bytes[index+5]*256+bytes[index+6]+22232)/1000, date: lDate});
tab.push({label: "Vrms", value: (bytes[index+7]*256+bytes[index+8])/10, date: lDate});
tab.push({label: "VrmsMin", value: (bytes[index+9]*256+bytes[index+10])/10, date: lDate});
tab.push({label: "VrmsMax", value: (bytes[index+11]*256+bytes[index+12])/10, date: lDate});
tab.push({label: "Vpeak", value: (bytes[index+13]*256+bytes[index+14])/10, date: lDate});
tab.push({label: "VpeakMin", value: (bytes[index+15]*256+bytes[index+16])/10, date: lDate});
tab.push({label: "VpeakMax", value: (bytes[index+17]*256+bytes[index+18])/10, date: lDate});
tab.push({label: "OverVoltageNumber", value: bytes[index+19]*256+bytes[index+20], date: lDate});
tab.push({label: "SagNumber", value: bytes[index+21]*256+bytes[index+22], date: lDate});
tab.push({label: "BrownoutNumber", value: bytes[index+23]*256+bytes[index+24], date: lDate});
}
// lorawan message type
if ( (clusterdID === 0x8004 ) & (attributID === 0x0000)) {
if (bytes[index] === 1)
stdData.message_type = "confirmed";
if (bytes[index] === 0)
stdData.message_type = "unconfirmed";
}
// lorawan retry
if ( (clusterdID === 0x8004 ) & (attributID === 0x0001)) {
stdData.nb_retry= bytes[index] ;
}
// lorawan reassociation
if ( (clusterdID === 0x8004 ) & (attributID === 0x0002)) {
stdData.period_in_minutes = bytes[index+1] *256+bytes[index+2];
stdData.nb_err_frames = bytes[index+3] *256+bytes[index+4];
}
decoded.data = tab;
}
// decode configuration response
if(cmdID === 0x07){
//AttributID
attributID = bytes[6]*256 + bytes[7];decoded.zclheader.attributID = decimalToHex(attributID,4);
//status
decoded.zclheader.status = bytes[4];
//batch
decoded.zclheader.decodedBatch = bytes[5];
}
//decode read configuration response
if(cmdID === 0x09){
//AttributID
attributID = bytes[6]*256 + bytes[7];decoded.zclheader.attributID = decimalToHex(attributID,4);
//status
decoded.zclheader.status = bytes[4];
//batch
decoded.zclheader.decodedBatch = bytes[5];
//AttributType
decoded.zclheader.attribut_type = bytes[8];
//min
decoded.zclheader.min = {}
if ((bytes[9] & 0x80) === 0x80) {
decoded.zclheader.min.value = (bytes[9]-0x80)*256+bytes[10];
decoded.zclheader.min.unity = "minutes";
}
else {
decoded.zclheader.min.value = bytes[9]*256+bytes[10];
decoded.zclheader.min.unity = "seconds";
}
//max
decoded.zclheader.max = {}
if ((bytes[9] & 0x80) === 0x80) {
decoded.zclheader.max.value = (bytes[9]-0x80)*256+bytes[10];
decoded.zclheader.max.unity = "minutes";
}
else {
decoded.zclheader.max.value = bytes[9]*256+bytes[10];
decoded.zclheader.max.unity = "seconds";
}
}
}
else
{
var decoded = {};
brData = (brUncompress(1,[{taglbl: 0,resol: 1, sampletype: 9,lblname: "ActiveEnergyWh"}], lora.payload, lDate))
var data_length = brData["datas"].length;
var tab=[];
for (var i = 0; i < data_length; i++) {
tab.push({label:brData["datas"][i]["data"]["label"] ,value:brData["datas"][i]["data"]["value"], date:brData["datas"][i]["date"]}) ;
}
decoded.data = tab;
decoded.zclheader = {};
decoded.zclheader.report = "batch";
}
}
return decoded;
}
function decodeUplink(input) {
return {
data : Decoder(input.bytes, input.fPort),
warnings: [],
errors: []
};
}
function encodeDownlink(input) {
if (input.data.value == "OFF"){
value = 0x00;
}
if (input.data.value == "ON"){
value = 0x01;
}
if (input.data.value == "TOGGLE"){
value = 0x02;
}
bytes = [0x11, 0x50, 0x00, 0x06, value];
return {
bytes: bytes,
fPort: 125,
warnings: [],
errors: []
};
}
function decodeDownlink(input) {
return {
data: {
bytes: input.bytes
},
warnings: [],
errors: []
};
}
Hello,
I work for a French company that uses your devices and I am in charge of developing an application based on your code to interpret the information received.
I spotted an error in your code when I receive frames from your Modbus product. Indeed, the last byte of the payload is not interpreted by the program. The problem is at line 163 where the loop does not run until the last byte but ends just before.
There are 2 ways to correct the problem :
I am available for more information.
I think there's an issue on line 546 (to line 564). Shouldn't it be EP9 instead of EP6?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.