From f123260ab9b052512010e8e0f03cd6a206b35b5f Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 17 Aug 2023 11:00:29 +0200 Subject: [PATCH] refactor and add decoder for cmi4130 --- .gitignore | 1 + vendor/elvaco/cmi4110-codec.yaml | 16 +- vendor/elvaco/cmi4110.js | 268 ++++++++++++----------- vendor/elvaco/cmi4111.js | 161 ++++++++++++++ vendor/elvaco/cmi4130-codec.yaml | 19 ++ vendor/elvaco/cmi4130-profile.yaml | 49 +++++ vendor/elvaco/cmi4130.js | 148 +++++++++++++ vendor/elvaco/cmi4140-codec.yaml | 16 +- vendor/elvaco/cmi4140.js | 102 +++++---- vendor/elvaco/index.yaml | 2 +- yarn.lock | 337 +++++++++++++++++++++++++++++ 11 files changed, 938 insertions(+), 181 deletions(-) create mode 100644 vendor/elvaco/cmi4111.js create mode 100644 vendor/elvaco/cmi4130-codec.yaml create mode 100644 vendor/elvaco/cmi4130-profile.yaml create mode 100644 vendor/elvaco/cmi4130.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 930d7448d4..d8ad8ba7ac 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ bin/validate-image bin/validate-image.exe .envrc .idea +yarn.lock \ No newline at end of file diff --git a/vendor/elvaco/cmi4110-codec.yaml b/vendor/elvaco/cmi4110-codec.yaml index 99f04cc7ad..8c2bb7d71e 100644 --- a/vendor/elvaco/cmi4110-codec.yaml +++ b/vendor/elvaco/cmi4110-codec.yaml @@ -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 \ No newline at end of file diff --git a/vendor/elvaco/cmi4110.js b/vendor/elvaco/cmi4110.js index 61fc6cd476..9d4ea32877 100644 --- a/vendor/elvaco/cmi4110.js +++ b/vendor/elvaco/cmi4110.js @@ -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)); diff --git a/vendor/elvaco/cmi4111.js b/vendor/elvaco/cmi4111.js new file mode 100644 index 0000000000..c17a6031c7 --- /dev/null +++ b/vendor/elvaco/cmi4111.js @@ -0,0 +1,161 @@ +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 } }, +}; + +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 checkNegativeValue(reversed_values) { + if (reversed_values.includes('f0')) { + return -parseInt(reversed_values.replace('f0', ''), 16); + } else { + return parseInt(reversed_values, 16); + } +} +function decodeCMI4111Standard(payloadArr) { + const decodedDictionary = {}; + let i = 1; + + while (i < payloadArr.length) { + const dif = payloadArr[i]; + const vif = payloadArr[i + 1]; + const difInt = parseInt(dif, 16); + i += 2; + const bcdLen = difInt >= 2 && difInt <= 4 ? difInt : 4; + + if (payloadArr.slice(i).length <= 5 && vif === 'fd') { + vif += payloadArr[i]; + i += 1; + } + + if (!(dif in difVifMapping_CMI4111) || !(vif in difVifMapping_CMI4111[dif])) { + throw new Error(`Unknown dif ${dif} and vif ${vif}`); + } + + if (dif === '34') { + if (payloadArr.filter((val) => val === '00').length > 20) { + throw new Error('Empty payload, value during error state'); + } else { + throw new Error(`Unknown dif ${dif} and vif ${vif}`); + } + } + + const reversedValues = payloadArr + .slice(i, i + bcdLen) + .reverse() + .join(''); // Little-endian (LSB) + const unitInfo = difVifMapping_CMI4111[dif][vif]; + + let valueInt; + if (reversedValues.startsWith('fff') && ['power', 'flow'].includes(unitInfo.measure)) { + valueInt = parseInt(reversedValues.replace('fff', '-'), 16); + } else { + valueInt = parseInt(reversedValues, 16); + } + + i += bcdLen; + let value; + + if (unitInfo.measure === 'date_heat_meter') { + throw new Error('date_heat_meter is not supported yet'); + } else if (unitInfo.measure === 'serial_from_message') { + value = parseInt(reversedValues); + } else if (unitInfo.unit) { + value = valueInt / Math.pow(10, unitInfo.decimal); + } else { + value = parseInt(reversedValues, 16); + } + + decodedDictionary[unitInfo.measure] = value; + } + + return standardPayload(decodedDictionary); +} + +function bytesToHexArray(bytes) { + return bytes.map(function (byte) { + return ('0' + (byte & 0xff).toString(16)).slice(-2); + }); +} + +function hexToBytes(hex) { + return hex.map(function (byte) { + return parseInt(byte, 16); + }); +} + +function decodeUplink(input) { + switch (input.fPort) { + case 2: + const hex_array = bytesToHexArray(input.bytes); + if (hex_array.length < 40) { + return { + data: {}, + errors: ['payload length < 40 '], + }; + } + if (hex_array[0] != '00') { + return { + data: {}, + errors: ['Payload type unknown, currently standard format supported'], + }; + } + return { + data: decodeCMI4111Standard(hex_array), + }; + default: + return { + data: {}, + errors: ['unknown FPort'], + }; + } +} + +// 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)); diff --git a/vendor/elvaco/cmi4130-codec.yaml b/vendor/elvaco/cmi4130-codec.yaml new file mode 100644 index 0000000000..51b2a1bca7 --- /dev/null +++ b/vendor/elvaco/cmi4130-codec.yaml @@ -0,0 +1,19 @@ +# Uplink decoder decodes binary data uplink into a JSON object (optional) +# For documentation on writing encoders and decoders, see: https://www.thethingsindustries.com/docs/integrations/payload-formatters/javascript/ +uplinkDecoder: + fileName: cmi4130.js + examples: + - description: Itron Meter temperature, energy, volume, power, flow , flow_temperature, return_temperature, serial and error flag. + input: + fPort: 2 + bytes : [15, 4, 7, 225, 4, 2, 0, 4, 21, 17, 162, 76, 0, 2, 45, 62, 0, 2, 59, 12, 3, 2, 90, 166, 2, 2, 94, 96, 2, 12, 120, 25, 103, 144, 16, 2, 253, 23, 0, 0] + output: + data: + energy: 1323210, + volume: 502222.5, + power: 6.2, + flow: 0.78, + flow_temperature: 67.8, + return_temperature: 60.8, + serial: 277899033, + error_flag: 0 \ No newline at end of file diff --git a/vendor/elvaco/cmi4130-profile.yaml b/vendor/elvaco/cmi4130-profile.yaml new file mode 100644 index 0000000000..2b4a7e2241 --- /dev/null +++ b/vendor/elvaco/cmi4130-profile.yaml @@ -0,0 +1,49 @@ +vendorProfileID: 574 + +# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1 +macVersion: '1.0.2' +# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version: +# 1.0: TS001-1.0 +# 1.0.1: TS001-1.0.1 +# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB +# 1.0.3: RP001-1.0.3-RevA +# 1.0.4: RP002-1.0.0 or RP002-1.0.1 +# 1.1: RP001-1.1-RevA or RP001-1.1-RevB +regionalParametersVersion: 'RP001-1.0.2' + +# Whether the end device supports join (OTAA) or not (ABP) +supportsJoin: true +# If your device is an ABP device (supportsJoin is false), uncomment the following fields: +# RX1 delay +#rx1Delay: 5 +# RX1 data rate offset +#rx1DataRateOffset: 0 +# RX2 data rate index +#rx2DataRateIndex: 0 +# RX2 frequency (MHz) +#rx2Frequency: 869.525 +# Factory preset frequencies (MHz) +#factoryPresetFrequencies: [868.1, 868.3, 868.5, 867.1, 867.3, 867.5, 867.7, 867.9] + +# Maximum EIRP +maxEIRP: 16 +# Whether the end device supports 32-bit frame counters +supports32bitFCnt: true + +# Whether the end device supports class B +supportsClassB: false +# If your device supports class B, uncomment the following fields: +# Maximum delay for the end device to answer a MAC request or confirmed downlink frame (seconds) +#classBTimeout: 60 +# Ping slot period (seconds) +#pingSlotPeriod: 128 +# Ping slot data rate index +#pingSlotDataRateIndex: 0 +# Ping slot frequency (MHz). Set to 0 if the band supports ping slot frequency hopping. +#pingSlotFrequency: 869.525 + +# Whether the end device supports class C +supportsClassC: false +# If your device supports class C, uncomment the following fields: +# Maximum delay for the end device to answer a MAC request or confirmed downlink frame (seconds) +#classCTimeout: 60 diff --git a/vendor/elvaco/cmi4130.js b/vendor/elvaco/cmi4130.js new file mode 100644 index 0000000000..765e4eead3 --- /dev/null +++ b/vendor/elvaco/cmi4130.js @@ -0,0 +1,148 @@ +const difVifMapping = { + '04': { + '00': { measure: 'energy', unit: 'kWh', decimal: 6 }, + '01': { measure: 'energy', unit: 'kWh', decimal: 5 }, + '02': { measure: 'energy', unit: 'kWh', decimal: 4 }, + '03': { measure: 'energy', unit: 'kWh', decimal: 3 }, + '04': { measure: 'energy', unit: 'kWh', decimal: 2 }, + '05': { measure: 'energy', unit: 'kWh', decimal: 1 }, + '06': { measure: 'energy', unit: 'kWh', decimal: 0 }, + '07': { measure: 'energy', unit: 'kWh', decimal: -1 }, + 11: { measure: 'volume', unit: 'm3', decimal: 5 }, + 12: { measure: 'volume', unit: 'm3', decimal: 4 }, + 13: { measure: 'volume', unit: 'm3', decimal: 3 }, + 14: { measure: 'volume', unit: 'm3', decimal: 2 }, + 15: { measure: 'volume', unit: 'm3', decimal: 1 }, + 16: { measure: 'volume', unit: 'm3', decimal: 0 }, + 17: { measure: 'volume', unit: 'm3', decimal: -1 }, + fd17: { measure: 'error_flag', unit: '', decimal: 0 }, + '6d': { measure: 'datetime_heat_meter', unit: '', decimal: 0 }, + }, + '02': { + '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 }, + 58: { measure: 'flow_temperature', unit: '°C', decimal: 3 }, + 59: { measure: 'flow_temperature', unit: '°C', decimal: 2 }, + '5a': { measure: 'flow_temperature', unit: '°C', decimal: 1 }, + '5b': { measure: 'flow_temperature', unit: '°C', decimal: 0 }, + '5c': { measure: 'return_temperature', unit: '°C', decimal: 3 }, + '5d': { measure: 'return_temperature', unit: '°C', decimal: 2 }, + '5e': { measure: 'return_temperature', unit: '°C', decimal: 1 }, + '5f': { measure: 'return_temperature', unit: '°C', decimal: 0 }, + fd17: { measure: 'error_flag', unit: '', decimal: 0 }, + }, + 32: { + '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 }, + }, + '0c': { 78: { measure: 'serial', unit: '', decimal: 0 } }, +}; + +function decode_cmi4130_standard(payloadArr) { + const decoded_dictionary = {}; + let i = 1; + while (i < payloadArr.length) { + const dif = payloadArr[i].toLowerCase(); + const dif_int = parseInt(dif, 16); + let vif = payloadArr[i + 1].toLowerCase(); + i += 2; + + if (payloadArr.length - i <= 3 && vif == 'fd') { + // end of payload: error flag + vif += payloadArr[i]; + i += 1; + } + let bcd_len = 4; + if (dif_int >= 2 && dif_int <= 4) { + bcd_len = dif_int; + } else if (dif_int == 50) { + // 32 reversed flow + bcd_len = 2; + } + + if (!(dif in difVifMapping && vif in difVifMapping[dif])) { + throw 'Unknown dif ' + dif + ' and vif dif in difVifMapping' + vif; + } + const reversed_values = payloadArr + .slice(i, i + bcd_len) + .reverse() + .join(''); //Little-endian (LSB) + i += bcd_len; + unit_info = difVifMapping[dif][vif]; + if (unit_info) { + value = parseInt(reversed_values, 16) / Math.pow(10, unit_info['decimal']); + } else { + value = parseInt(reversed_values, 16); + } + if (dif == '32') { + //reverse flow + decoded_dictionary[unit_info['measure']] = 0; + } else { + decoded_dictionary[unit_info['measure']] = value; + } + } + return decoded_dictionary; +} + +function bytesToHexArray(bytes) { + return bytes.map(function (byte) { + return ('0' + (byte & 0xff).toString(16)).slice(-2); + }); +} + +function hexToBytes(hex) { + return hex.map(function (byte) { + return parseInt(byte, 16); + }); +} + +function decodeUplink(input) { + switch (input.fPort) { + case 2: + const hex_array = bytesToHexArray(input.bytes); + if (hex_array.length < 40) { + return { + data: {}, + errors: ['payload length < 40 '], + }; + } + if (hex_array[0] != '0f') { + return { + data: {}, + errors: ['Payload type unknown, currently standard format supported'], + }; + } + return { + data: decode_cmi4130_standard(hex_array), + }; + default: + return { + data: {}, + errors: ['unknown FPort'], + }; + } +} +// int(reversed_values, 16) +// array= 150405b827bd3a04142cddb10d02290000023a000002598f26025d61160c784405817904fd1700000000 +// hex = ['0f', '04', '07', 'e1', '04', '02', '00', '04', '15', '11', 'a2', '4c', '00', '02', '2d', '3e', '00', '02', '3b', '0c', '03', '02', '5a', 'a6', '02', '02', '5e', '60', '02', '0c', '78', '19', '67', '90', '10', '02', 'fd', '17', '00', '00'] +bytes = [15, 4, 7, 225, 4, 2, 0, 4, 21, 17, 162, 76, 0, 2, 45, 62, 0, 2, 59, 12, 3, 2, 90, 166, 2, 2, 94, 96, 2, 12, 120, 25, 103, 144, 16, 2, 253, 23, 0, 0]; +input = { fPort: 2, bytes: bytes }; +console.log(decodeUplink(input)); diff --git a/vendor/elvaco/cmi4140-codec.yaml b/vendor/elvaco/cmi4140-codec.yaml index 6aed7438ce..62e5586c1c 100644 --- a/vendor/elvaco/cmi4140-codec.yaml +++ b/vendor/elvaco/cmi4140-codec.yaml @@ -2,4 +2,18 @@ # For documentation on writing encoders and decoders, see: https://www.thethingsindustries.com/docs/integrations/payload-formatters/javascript/ uplinkDecoder: fileName: cmi4140.js - # Examples (optional) + examples: + - description: Multical 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: 98547500, + volume: 2297603, + power: 0, + flow: 0, + flow_temperature: 98.71, + return_temperature: 57.29, + serial: 79810544, + error_flag: 0 diff --git a/vendor/elvaco/cmi4140.js b/vendor/elvaco/cmi4140.js index 3711015cad..1436a7cbb4 100644 --- a/vendor/elvaco/cmi4140.js +++ b/vendor/elvaco/cmi4140.js @@ -1,5 +1,5 @@ //Based on user's Manual V1.3 -const DIF_VIF_MAPPING = { +const difVifMapping = { '04': { '00': { measure: 'energy', unit: 'kWh', decimal: 6 }, '01': { measure: 'energy', unit: 'kWh', decimal: 5 }, @@ -9,87 +9,85 @@ const DIF_VIF_MAPPING = { '05': { measure: 'energy', unit: 'kWh', decimal: 1 }, '06': { measure: 'energy', unit: 'kWh', decimal: 0 }, '07': { measure: 'energy', unit: 'kWh', decimal: -1 }, - '10': { measure: 'volume', unit: 'm3', decimal: 6 }, - '11': { measure: 'volume', unit: 'm3', decimal: 5 }, - '12': { measure: 'volume', unit: 'm3', decimal: 4 }, - '13': { measure: 'volume', unit: 'm3', decimal: 3 }, - '14': { measure: 'volume', unit: 'm3', decimal: 2 }, - '15': { measure: 'volume', unit: 'm3', decimal: 1 }, - '16': { measure: 'volume', unit: 'm3', decimal: 0 }, - '17': { measure: 'volume', unit: 'm3', decimal: -1 }, - 'fd17': { measure: 'error_flag', unit: '', decimal: 0 }, + 10: { measure: 'volume', unit: 'm3', decimal: 6 }, + 11: { measure: 'volume', unit: 'm3', decimal: 5 }, + 12: { measure: 'volume', unit: 'm3', decimal: 4 }, + 13: { measure: 'volume', unit: 'm3', decimal: 3 }, + 14: { measure: 'volume', unit: 'm3', decimal: 2 }, + 15: { measure: 'volume', unit: 'm3', decimal: 1 }, + 16: { measure: 'volume', unit: 'm3', decimal: 0 }, + 17: { measure: 'volume', unit: 'm3', decimal: -1 }, + fd17: { measure: 'error_flag', unit: '', decimal: 0 }, '6d': { measure: 'datetime_heat_meter', unit: '', decimal: 0 }, }, '02': { - '29': { measure: 'power', unit: 'kW', decimal: 5 }, // to verify + 29: { measure: 'power', unit: 'kW', decimal: 5 }, // to verify '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 }, - '39': { measure: 'flow', unit: 'm3/h', decimal: 5 }, // assumption + 39: { measure: 'flow', unit: 'm3/h', decimal: 5 }, // assumption '3a': { measure: 'flow', unit: 'm3/h', decimal: 4 }, '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 }, - '58': { measure: 'forward_temperature', unit: '°C', decimal: 3 }, - '59': { measure: 'forward_temperature', unit: '°C', decimal: 2 }, - '5a': { measure: 'forward_temperature', unit: '°C', decimal: 1 }, - '5b': { measure: 'forward_temperature', unit: '°C', decimal: 0 }, + 58: { measure: 'flow_temperature', unit: '°C', decimal: 3 }, + 59: { measure: 'flow_temperature', unit: '°C', decimal: 2 }, + '5a': { measure: 'flow_temperature', unit: '°C', decimal: 1 }, + '5b': { measure: 'flow_temperature', unit: '°C', decimal: 0 }, '5c': { measure: 'return_temperature', unit: '°C', decimal: 3 }, '5d': { measure: 'return_temperature', unit: '°C', decimal: 2 }, '5e': { measure: 'return_temperature', unit: '°C', decimal: 1 }, '5f': { measure: 'return_temperature', unit: '°C', decimal: 0 }, - 'fd17': { measure: 'error_flag', unit: '', decimal: 0 }, + fd17: { measure: 'error_flag', unit: '', decimal: 0 }, }, '0c': { - 78: { measure: 'serial_from_message', unit: '', decimal: 0 }, + 78: { measure: 'serial', unit: '', decimal: 0 }, }, }; -function get_vif(payload_arr, index) { - const dif = payload_arr[index].toLowerCase(); +function getVif(payloadArr, index) { + const dif = payloadArr[index].toLowerCase(); const dif_int = parseInt(dif, 16); if (dif_int === 132) { - const vif = payload_arr + const vif = payloadArr .slice(index + 1, index + 3) .join('') .toLowerCase(); return [vif, index + 3]; } else { - const vif = payload_arr[index + 1].toLowerCase(); + const vif = payloadArr[index + 1].toLowerCase(); return [vif, index + 2]; } } - -//javascript -function decode_cmi4140_standard(payload_arr) { +function decode_cmi4140_standard(payloadArr) { let decoded_dictionary = {}; let i = 1; - while (i < payload_arr.length) { - let dif = payload_arr[i].toLowerCase(); + while (i < payloadArr.length) { + let dif = payloadArr[i].toLowerCase(); let dif_int = parseInt(dif, 16); - let [vif, newIndex] = get_vif(payload_arr, i); + let [vif, newIndex] = getVif(payloadArr, i); i = newIndex; let bcd_len = dif_int >= 2 && dif_int <= 4 ? dif_int : 4; - if (payload_arr.slice(i).length <= 5 && vif == 'fd') { - vif = vif + payload_arr[i]; + if (payloadArr.slice(i).length <= 5 && vif == 'fd') { + vif += payloadArr[i]; i += 1; } - if (!(dif in DIF_VIF_MAPPING) || !(vif in DIF_VIF_MAPPING[dif])) { + if (!(dif in difVifMapping) || !(vif in difVifMapping[dif])) { throw new Error('Unknown dif ' + dif + ' and vif ' + vif); } - let reversed_values = payload_arr + let reversed_values = payloadArr .slice(i, i + bcd_len) .reverse() .join(''); // Little-endian (LSB) i += bcd_len; - let unit_info = DIF_VIF_MAPPING[dif][vif]; + let unit_info = difVifMapping[dif][vif]; let value; value = unit_info['unit'] ? parseInt(reversed_values, 16) / Math.pow(10, unit_info['decimal']) : parseInt(reversed_values); @@ -99,14 +97,14 @@ function decode_cmi4140_standard(payload_arr) { return decoded_dictionary; } -function bytes_to_hex_array(bytes) { +function bytesToHexArray(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) { +function hexToBytes(hex) { + return hex.map(function (byte) { return parseInt(byte, 16); }); } @@ -114,24 +112,34 @@ function hex_to_bytes(hex) { function decodeUplink(input) { switch (input.fPort) { case 2: - const hex_array = bytes_to_hex_array(input.bytes); + const hex_array = bytesToHexArray(input.bytes); if (hex_array.length < 40) { + return { + data: {}, + errors: ['payload length < 40 '], + }; + } + switch (hex_array[0]) { + case '15': return { - data: {}, - errors: ['payload length < 40 '], + data: decode_cmi4140_standard(hex_array), }; - } - return { - data: decode_cmi4140_standard(hex_array), - }; + default: + return { + data: {}, + errors: ['Payload type unknown, currently standard format supported'], + }; + } + default: return { - data: {}, + data: {}, errors: ['unknown FPort'], }; } } - - -// array= 000c06526761020c14978999000b2d0000000b3b0000000a5a33060a5e41050c782911036602fd170000 +// payload = "150405b827bd3a04142cddb10d02290000023a000002598f26025d61160c784405817904fd1700000000" +bytes = [21, 4, 5, 184, 39, 189, 58, 4, 20, 44, 221, 177, 13, 2, 41, 0, 0, 2, 58, 0, 0, 2, 89, 143, 38, 2, 93, 97, 22, 12, 120, 68, 5, 129, 121, 4, 253, 23, 0, 0, 0, 0]; +input = { fPort: 2, bytes: bytes }; +console.log(decodeUplink(input)); diff --git a/vendor/elvaco/index.yaml b/vendor/elvaco/index.yaml index 45a5b65f56..5eef18b12a 100644 --- a/vendor/elvaco/index.yaml +++ b/vendor/elvaco/index.yaml @@ -2,7 +2,7 @@ endDevices: # Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters) - cmi4110 # - cmi4111 - # - cmi4130 + - cmi4130 - cmi4140 # - cmi4160 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..f05227bd1f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,337 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ajv-cli@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/ajv-cli/-/ajv-cli-0.6.0.tgz#47834530250068bf391fd065ba0d30b3f2de55c5" + integrity sha512-85CnFX7Nft+UvmAi1fHNnqrtOiSL8KW/o4hS096AVT1P+45SAe5YO242pn9+rqC9F1UKyeFioCG52jKXTOI2FA== + dependencies: + ajv "^3.8.0" + glob "^7.0.3" + minimist "^1.2.0" + +ajv@^3.8.0: + version "3.8.10" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-3.8.10.tgz#78dfd5615e2cf4c0f8cb7f3284e0257dbbe867c9" + integrity sha512-h74deHfbgeB8TuWq6UQxP4fwsCbo9T+pvWofl4pEdZzI6lMSSFAWZr3PsNXHjSFABCj49j6fgbzUTHWIrMdHMw== + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^6.12.6: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +file-type@^10.10.0: + version "10.11.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-10.11.0.tgz#2961d09e4675b9fb9a3ee6b69e9cd23f43fd1890" + integrity sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob@^7.0.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +image-size@^0.9.7: + version "0.9.7" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.9.7.tgz#43b4ead4b1310d5ae310a559d52935a347e47c09" + integrity sha512-KRVgLNZkr00YGN0qn9MlIrmlxbRhsCcEb1Byq3WKGnIV4M48iD185cprRtaoK4t5iC+ym2Q5qlArxZ/V1yzDgA== + dependencies: + queue "6.0.2" + +image-type@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/image-type/-/image-type-4.1.0.tgz#72a88d64ff5021371ed67b9a466442100be57cd1" + integrity sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg== + dependencies: + file-type "^10.10.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz#e06f23128e0bbe342dc996ed5a19e28b57b580e0" + integrity sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g== + dependencies: + jsonify "^0.0.1" + +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-try@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +prettier@^2.6.2: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +read-chunk@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-3.2.0.tgz#2984afe78ca9bfbbdb74b19387bf9e86289c16ca" + integrity sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ== + dependencies: + pify "^4.0.1" + with-open-file "^0.1.6" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +with-open-file@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/with-open-file/-/with-open-file-0.1.7.tgz#e2de8d974e8a8ae6e58886be4fe8e7465b58a729" + integrity sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA== + dependencies: + p-finally "^1.0.0" + p-try "^2.1.0" + pify "^4.0.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2"