Skip to content

Commit

Permalink
refactor and add decoder for cmi4130
Browse files Browse the repository at this point in the history
  • Loading branch information
rgarmyn committed Aug 17, 2023
1 parent 02f5234 commit f123260
Show file tree
Hide file tree
Showing 11 changed files with 938 additions and 181 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ bin/validate-image
bin/validate-image.exe
.envrc
.idea
yarn.lock
16 changes: 15 additions & 1 deletion vendor/elvaco/cmi4110-codec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,18 @@
# For documentation on writing encoders and decoders, see: https://www.thethingsindustries.com/docs/integrations/payload-formatters/javascript/
uplinkDecoder:
fileName: cmi4110.js
# Examples (optional)
examples:
- description: Landis UH temperature, energy, volume, power, flow , flow_temperature, return_temperature, serial and error flag.
input:
fPort: 2
bytes: [0, 12, 6, 82, 103, 97, 2, 12, 20, 151,137, 153, 0, 11, 45, 0, 0, 0, 11, 59,0, 0, 0, 10, 90, 51, 6, 10, 94, 65, 5, 12, 120, 41, 17, 3, 102, 2, 253, 23,0, 0]
output:
data:
energy: 2616752,
volume: 9989.97,
power: 0,
flow: 0,
flow_temperature: 63.3,
return_temperature: 54.1,
serial: 66031129,
error_flag: 0
268 changes: 137 additions & 131 deletions vendor/elvaco/cmi4110.js
Original file line number Diff line number Diff line change
@@ -1,152 +1,158 @@
const difVifMapping = {
'0c': {
'06': { measure: 'energy', unit: 'kWh', decimal: 0 },
'07': { measure: 'energy', unit: 'kWh', decimal: -1 },
// "FB00": {"measure": "energy", "unit": "kWh", "decimal": -3},
// "FB01": {"measure": "energy", "unit": "kWh", "decimal": -3},
14: { measure: 'volume', unit: 'm3', decimal: 2 },
15: { measure: 'volume', unit: 'm3', decimal: 1 },
16: { measure: 'volume', unit: 'm3', decimal: 0 },
78: { measure: 'serial', unit: '', decimal: 0 },
},
'0b': {
'2a': { measure: 'power', unit: 'kW', decimal: 4 },
'2b': { measure: 'power', unit: 'kW', decimal: 3 },
'2c': { measure: 'power', unit: 'kW', decimal: 2 },
'2d': { measure: 'power', unit: 'kW', decimal: 1 },
'2e': { measure: 'power', unit: 'kW', decimal: 0 },
'2f': { measure: 'power', unit: 'kW', decimal: -1 },
'3b': { measure: 'flow', unit: 'm3/h', decimal: 3 },
'3c': { measure: 'flow', unit: 'm3/h', decimal: 2 },
'3d': { measure: 'flow', unit: 'm3/h', decimal: 1 },
'3e': { measure: 'flow', unit: 'm3/h', decimal: 0 },
'3f': { measure: 'flow', unit: 'm3/h', decimal: -1 },
},
'0a': {
'5a': { measure: 'flow_temperature', unit: '°C', decimal: 1 },
'5b': { measure: 'flow_temperature', unit: '°C', decimal: 0 },
'5e': { measure: 'return_temperature', unit: '°C', decimal: 1 },
'5f': { measure: 'return_temperature', unit: '°C', decimal: 0 },
},
'02': { fd17: { measure: 'error_flag', unit: '', decimal: 0 } },
'04': { fd17: { measure: 'error_flag', unit: '', decimal: 0 } },
};

const DIF_VIF_MAPPING = {
"0c": {
"06": {"measure": "energy", "unit": "kWh", "decimal": 0},
"07": {"measure": "energy", "unit": "kWh", "decimal": -1},
// "FB00": {"measure": "energy", "unit": "kWh", "decimal": -3},
// "FB01": {"measure": "energy", "unit": "kWh", "decimal": -3},
"14": {"measure": "volume", "unit": "m3", "decimal": 2},
"15": {"measure": "volume", "unit": "m3", "decimal": 1},
"16": {"measure": "volume", "unit": "m3", "decimal": 0},
"78": {"measure": "serial_from_message", "unit": "", "decimal": 0},
},
"0b": {
"2a": {"measure": "power", "unit": "kW", "decimal": 4},
"2b": {"measure": "power", "unit": "kW", "decimal": 3},
"2c": {"measure": "power", "unit": "kW", "decimal": 2},
"2d": {"measure": "power", "unit": "kW", "decimal": 1},
"2e": {"measure": "power", "unit": "kW", "decimal": 0},
"2f": {"measure": "power", "unit": "kW", "decimal": -1},
"3b": {"measure": "flow", "unit": "m3/h", "decimal": 3},
"3c": {"measure": "flow", "unit": "m3/h", "decimal": 2},
"3d": {"measure": "flow", "unit": "m3/h", "decimal": 1},
"3e": {"measure": "flow", "unit": "m3/h", "decimal": 0},
"3f": {"measure": "flow", "unit": "m3/h", "decimal": -1},
},
"0a": {
"5a": {"measure": "flow_temperature", "unit": "°C", "decimal": 1},
"5b": {"measure": "flow_temperature", "unit": "°C", "decimal": 0},
"5e": {"measure": "return_temperature", "unit": "°C", "decimal": 1},
"5f": {"measure": "return_temperature", "unit": "°C", "decimal": 0},
},
"02": {"fd17": {"measure": "error_flag", "unit": "", "decimal": 0}},
"04": {"fd17": {"measure": "error_flag", "unit": "", "decimal": 0}},
function getVif(payloadArr, index) {
const dif = payloadArr[index].toLowerCase();
const dif_int = parseInt(dif, 16);

if (dif_int === 132) {
const vif = payloadArr
.slice(index + 1, index + 3)
.join('')
.toLowerCase();
return [vif, index + 3];
} else {
const vif = payloadArr[index + 1].toLowerCase();
return [vif, index + 2];
}
}

function decode_base64(payload) {
const decoded = Buffer.from(payload, 'base64').toString('hex');
const array = decoded.match(/.{2}/g);
return array;
function checkNegativeValue(reversed_values) {
if (reversed_values.includes('f0')) {
return -parseInt(reversed_values.replace('f0', ''), 16);
} else {
return parseInt(reversed_values, 16);
}
}

function get_vif(payload_arr, index) {
const dif = payload_arr[index].toLowerCase();
function decodeCMI4110Standard(payloadArr) {
const decoded_dictionary = {};
let i = 1;
energy_count = 0;
while (i < payloadArr.length) {
const dif = payloadArr[i].toLowerCase();
const dif_int = parseInt(dif, 16);

if (dif_int === 132) {
const vif = payload_arr.slice(index + 1, index + 3).join("").toLowerCase();
return [vif, index + 3];
} else {
const vif = payload_arr[index + 1].toLowerCase();
return [vif, index + 2];
let [vif, newIndex] = getVif(payloadArr, i);
i = newIndex;
let bcd_len = dif_int;
if (!(dif_int >= 2 && dif_int <= 4)) {
bcd_len = dif_int - 8;
}
if (payloadArr.length - i <= 3) {
vif += payloadArr[i];
i += 1;
}
}

if (dif in difVifMapping && vif in difVifMapping[dif]) {
const reversed_values = payloadArr
.slice(i, i + bcd_len)
.reverse()
.join('');
const value_int = checkNegativeValue(reversed_values);
i += bcd_len;

function check_negative_value(reversed_values) {
if (reversed_values.includes("f0")) {
return -parseInt(reversed_values.replace("f0", ""), 16);
const unit_info = difVifMapping[dif][vif];
let register = unit_info['measure'];
switch (register) {
case 'energy':
if (energy_count == 0) {
} else if (energy_count < 4) {
register = 'energy_tariff_' + energy_count.toString();
} else {
throw 'more than 4 energy registers';
}
energy_count += 1;
value = parseInt(reversed_values) / Math.pow(10, unit_info['decimal']);
break;
case 'flow' || 'power':
value = value_int / Math.pow(10, unit_info['decimal']);
break;
default:
value = parseInt(reversed_values) / Math.pow(10, unit_info['decimal']);
if (!unit_info['unit']) {
value = parseInt(reversed_values);
}
}
decoded_dictionary[register] = value;
} else {
return parseInt(reversed_values, 16);
throw 'Unknown dif ' + dif + ' and vif ' + vif;
}
}
return decoded_dictionary;
}

function bytesToHexArray(bytes) {
return bytes.map(function (byte) {
return ('0' + (byte & 0xff).toString(16)).slice(-2);
});
}

function decode_cmi4110_standard(payload_arr) {
const decoded_dictionary = {}
let i = 1
energy_count = 0
while (i < payload_arr.length) {
const dif = payload_arr[i].toLowerCase();
const dif_int = parseInt(dif, 16);
let [vif, newIndex] = get_vif(payload_arr, i);
i = newIndex;
let bcd_len = dif_int;
if (!(2 <= dif_int && dif_int <= 4)) {
bcd_len = dif_int - 8
}
if (payload_arr.length - i <= 3) {
vif += payload_arr[i];
i += 1;
}

if (dif in DIF_VIF_MAPPING && vif in DIF_VIF_MAPPING[dif]) {
const reversed_values = payload_arr.slice(i, i + bcd_len).reverse().join("");
const value_int = check_negative_value(reversed_values);
i += bcd_len;

const unit_info = DIF_VIF_MAPPING[dif][vif];
let register = unit_info["measure"];
switch (register) {
case "energy":
if (energy_count == 0) {
} else if (energy_count < 4) {
register = "energy_tariff_" + energy_count.toString();
} else {
throw "more than 4 energy registers";
}
energy_count += 1;
value = parseInt(reversed_values) / Math.pow(10, unit_info["decimal"]);
break;
case "flow" || "power":
value = value_int / Math.pow(10, unit_info["decimal"]);
break;
default:
value = parseInt(reversed_values) / Math.pow(10, unit_info["decimal"]);
if (!unit_info["unit"]) {
value = parseInt(reversed_values);
}
}
decoded_dictionary[register] = value;
} else {
throw "Unknown dif " + dif + " and vif " + vif;
}
}
return decoded_dictionary;
function hexToBytes(hex) {
return hex.map(function (byte) {
return parseInt(byte, 16);
});
}

function bytes_to_hex_array(bytes) {
return bytes.map(function (byte) {
return ('0' + (byte & 0xff).toString(16)).slice(-2);
});
}

function hex_to_bytes(hex) {
return hex.match(/.{1,2}/g).map(function (byte) {
return parseInt(byte, 16);
});
}

function decodeUplink(input) {
switch (input.fPort) {
case 2:
const hex_array = bytes_to_hex_array(input.bytes);
if (hex_array.length < 40) {
return {
data: {},
errors: ['payload length < 40 '],
};
}
function decodeUplink(input) {
switch (input.fPort) {
case 2:
const hex_array = bytesToHexArray(input.bytes);
if (hex_array.length < 40) {
return {
data: decode_cmi4110_standard(hex_array),
data: {},
errors: ['payload length < 40 '],
};
default:
}
if (hex_array[0] != '00') {
return {
data: {},
errors: ['unknown FPort'],
data: {},
errors: ['Payload type unknown, currently standard format supported'],
};
}
}
return {
data: decodeCMI4110Standard(hex_array),
};
default:
return {
data: {},
errors: ['unknown FPort'],
};
}

// array = ["00", "0c", "06", "52", "67", "61", "02", "0c", "14", "97", "89", "99", "00", "0b", "2d", "00", "00", "00", "0b", "3b", "00", "00", "00", "0a", "5a", "33", "06", "0a", "5e", "41", "05", "0c", "78", "29", "11", "03", "66", "02", "fd", "17", "00", "00"]
// console.log(decode_cmi4110_standard(array))
}

// array= 000c06526761020c14978999000b2d0000000b3b0000000a5a33060a5e41050c782911036602fd170000
// hex = ["00","0c","06","52","67","61","02","0c","14","97","89","99","00","0b","2d","00","00","00","0b","3b","00","00","00","0a","5a","33","06","0a","5e","41","05","0c","78","29","11","03","66","02","fd","17","00","00"]
bytes = [0, 12, 6, 82, 103, 97, 2, 12, 20, 151, 137, 153, 0, 11, 45, 0, 0, 0, 11, 59, 0, 0, 0, 10, 90, 51, 6, 10, 94, 65, 5, 12, 120, 41, 17, 3, 102, 2, 253, 23, 0, 0];
input = { fPort: 2, bytes: bytes };
console.log(decodeUplink(input));
Loading

0 comments on commit f123260

Please sign in to comment.