// Encode encodes the given object into an array of bytes. // - fPort contains the LoRaWAN fPort number // - obj is an object, e.g. {"temperature": 22.5} // The function must return an array of bytes, e.g. [225, 230, 255, 0] function Encode(fPort, obj) { var cmd = []; function toHexString(byteArray) { return Array.prototype.map.call(byteArray, function(byte) { return ('0' + (byte & 0xFF).toString(16)).slice(-2); }).join('-'); } console.log("LHI110 downlink JSON: " + JSON.stringify(obj)); if (fPort == 1 ) { // Query Commands if ('query_FWBuildHash' in obj) { cmd = [0x02, 0x03]; } if ('query_CPUVoltage' in obj) { cmd = [0x02, 0x06]; } if ('query_CPUTemperature' in obj) { cmd = [0x02, 0x0A]; } if ('query_Status' in obj) { cmd = [0x02, 0x20]; } if ('query_ReportInterval' in obj) { cmd = [0x02, 0x22]; } if ('query_MsgType' in obj) { cmd = [0x02, 0x23]; } if ('query_EquipmentId' in obj) { cmd = [0x02, 0x24]; } if ('query_GasMeterEquipmentId' in obj) { cmd = [0x02, 0x25]; } if ('query_GasMeterMbusDevId' in obj) { cmd = [0x02, 0x26]; } if ('query_DiagCount1' in obj) { cmd = [0x02, 0x30]; } if ('query_InvalidIndex' in obj) { cmd = [0x02, 0x99]; } // Set Commands if ('set_ReportInterval' in obj) { cmd = [0x01, 0x22, (obj.set_ReportInterval >> 8 ) & 0xFF, (obj.set_ReportInterval >> 0 ) & 0xFF]; } if ('set_MsgType' in obj) { cmd = [0x01, 0x23, obj.set_MsgType]; } if ('set_GasMeterMbusDevId' in obj) { cmd = [0x01, 0x26, obj.set_GasMeterMbusDevId]; } if ('set_ADRCtrl' in obj) { cmd = [0x01, 0x0B, obj.set_ADRCtrl]; } // Action Commands if('action_Reset' in obj) { cmd = [0x03, 0x05]; } // Invalid command if('invalidCommand' in obj) { cmd = [0x99, 0x99]; } // Invalid length if('invalidLength' in obj) { var longArray = new Array(100); for (var i = 0; i < longArray.length; i++) { longArray[i] = i; } cmd = longArray; } } console.log("LHI110 downlink bytes: " + toHexString(cmd)); return cmd; } // Decode decodes an array of bytes into an object. // - fPort contains the LoRaWAN fPort number // - bytes is an array of bytes, e.g. [225, 230, 255, 0] function Decode(fPort, bytes) { // Use the port to determine uplink data type // All are MSB, Most Significant Bit/Byte first. // Port 1: Protocol data var obj = {}; function toHexString(byteArray) { return Array.prototype.map.call(byteArray, function(byte) { return ('0' + (byte & 0xFF).toString(16)).slice(-2); }).join('-'); } console.log("LHI110 uplink bytes: " + toHexString(bytes) + " on port " + String(fPort)); if (fPort === 1) { // Data if (bytes[0] === 0x01) { // Data received, byte 1 contains index switch (bytes[1]) { // FW Build hash case 0x03: { // Convert received butes to a string from byte 2 onwards obj.fwVersion = String.fromCharCode.apply(String,bytes.slice(2)); break; } // CPU Voltage case 0x06: { // CPU Voltage is send as a 16-bit unsigned integer obj.CPUVoltage = (bytes[2] << 8 | bytes[3]); break; } // CPU Temperature case 0x0A: { // CPU Temperature is send as a 16-bit unsigned int // with 0.01C coding and 50C offset obj.CPUTemperature = (((bytes[2] << 8 | bytes[3]) - 5000) / 100); break; } // Status case 0x20: { // Bitfield, convert to string of base 2 obj.status = bytes[2].toString(2); break; } // ReportInterval case 0x22: { // Interval encoded as uint16 obj.reportInterval = (bytes[2] << 8 | bytes[3]); break; } // MsgType case 0x23: { obj.msgType = bytes[2]; break; } // GasMeterMbusDevId override case 0x26: { obj.gasMeterMbusDevId = bytes[2]; break; } // Diag Counter 1 case 0x30: { obj.diagCount1 = (bytes[2] << 24 | bytes[3] << 16 | bytes[4] << 8 | bytes[5]); break; } default: { console.log("Unsupported data index: "); console.log(bytes[1]); } } } else if (bytes[0] === 0x02) { // NACK received, byte 1 contains nack index obj.nackIndex = (bytes[1]); } else { console.log("Unsupported byte[0]: " + String(bytes[0])); } } else if (fPort === 2) { function zfill(num, len) {return (Array(len).join("0") + num).slice(-len);} function arrToUint16(byteArr, startIdx) { return (byteArr[startIdx] << 8 | byteArr[startIdx + 1]); } function arrToUint32(byteArr, startIdx) { return (byteArr[startIdx] << 24 | byteArr[startIdx + 1] << 16 | byteArr[startIdx + 2] << 8 | byteArr[startIdx + 3]); } function arrToUint40(byteArr, startIdx) { // This doesn't work with old "otto" JS engine in "Go" language, 32-bit limits hit... Leave it for now return (byteArr[startIdx] << 32 | byteArr[startIdx + 1] << 24 | byteArr[startIdx + 2] << 16 | byteArr[startIdx + 3] << 8 | byteArr[startIdx + 4]); } function decodePower(pwrData) { if ((1 << 15) & pwrData) { // If bit 15 is set, scale by 100W return (pwrData & ~(1 << 15)) * 100; } else { return pwrData; } } function decodeVoltages(byteArr, startIdx) { var lineVoltArr = arrToUint32(byteArr, startIdx); var l1Volt_mV = ((lineVoltArr & 0x3FF00000) >> 20) * 250; var l2Volt_mV = ((lineVoltArr & 0x000FFC00) >> 10) * 250; var l3Volt_mV = ((lineVoltArr & 0x000003FF) >> 0) * 250; if (l1Volt_mV != 0) { l1Volt_mV += 20000; } if (l2Volt_mV != 0) { l2Volt_mV += 20000; } if (l3Volt_mV != 0) { l3Volt_mV += 20000; } return [ l1Volt_mV, l2Volt_mV, l3Volt_mV ]; } function decodeCurrents(byteArr, startIdx) { var lineCurrArr = arrToUint32(byteArr, startIdx); return [ ((lineCurrArr & 0x3FF00000) >> 20) * 100, ((lineCurrArr & 0x000FFC00) >> 10) * 100, ((lineCurrArr & 0x000003FF) >> 0) * 100 ]; } function timeString(timeStamp) { // JS expects timestamp in milliseconds var dateObj = new Date(timeStamp * 1000); var timeString = dateObj.getUTCFullYear() + "-" + zfill(dateObj.getUTCMonth() + 1, 2) + "-" + zfill(dateObj.getUTCDate(), 2) timeString += " " + zfill(dateObj.getUTCHours(), 2) + ":" + zfill(dateObj.getUTCMinutes(), 2) + ":" + zfill(dateObj.getUTCSeconds(), 2) return timeString; } // Msg type and timestamp is common to all message formats obj.msgType = bytes[0]; obj.timeStamp = arrToUint32(bytes, 1); obj.timeString = timeString(obj.timeStamp); switch (obj.msgType) { // Msg type 1 case 0x01: { // Meter readings obj.activeImportReading = arrToUint40(bytes, 5); // Active power data, peak and averages obj.actL1ImportPowerPeak = decodePower(arrToUint16(bytes, 10)); obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 12)); obj.actL2ImportPowerPeak = decodePower(arrToUint16(bytes, 14)); obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 16)); obj.actL3ImportPowerPeak = decodePower(arrToUint16(bytes, 18)); obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 20)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 22)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 22)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 22)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 26)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 26)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 26)[2]; break; } // Msg type 2 case 0x02: { // Meter readings obj.activeImportReading = arrToUint40(bytes, 5); obj.activeExportReading = arrToUint40(bytes, 10); // Active power data, peak and averages obj.actL1ImportPowerPeak = decodePower(arrToUint16(bytes, 15)); obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 17)); obj.actL2ImportPowerPeak = decodePower(arrToUint16(bytes, 19)); obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 21)); obj.actL3ImportPowerPeak = decodePower(arrToUint16(bytes, 23)); obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 25)); obj.actL1ExportPowerAver = decodePower(arrToUint16(bytes, 27)); obj.actL2ExportPowerAver = decodePower(arrToUint16(bytes, 29)); obj.actL3ExportPowerAver = decodePower(arrToUint16(bytes, 31)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 33)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 33)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 33)[2]; // Line current obj.l1CurrentPeak_mA = decodeCurrents(bytes, 37)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 37)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 37)[2]; break; } // Msg type 3 case 0x03: { // Meter readings obj.activeImportReading = arrToUint40(bytes, 5); obj.reactiveImportReading = arrToUint40(bytes, 10); obj.reactiveExportReading = arrToUint40(bytes, 15); // Active power data, peak and averages obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 20)); obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 22)); obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 24)); obj.reactL1ImportPowerAver = decodePower(arrToUint16(bytes, 26)); obj.reactL2ImportPowerAver = decodePower(arrToUint16(bytes, 28)); obj.reactL3ImportPowerAver = decodePower(arrToUint16(bytes, 30)); obj.reactL1ExportPowerAver = decodePower(arrToUint16(bytes, 32)); obj.reactL2ExportPowerAver = decodePower(arrToUint16(bytes, 34)); obj.reactL3ExportPowerAver = decodePower(arrToUint16(bytes, 36)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 38)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 38)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 38)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 42)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 42)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 42)[2]; break; } // Msg type 4 case 0x04: { // Meter readings obj.activeImportReadingLowTariff = arrToUint40(bytes, 5); obj.activeImportReadingNormTariff = arrToUint40(bytes, 10); // Active power averages obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 15)); obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 17)); obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 19)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 21)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 21)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 21)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 25)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 25)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 25)[2]; // Tariff indicator obj.tariff = bytes[29]; break; } // Msg type 5 case 0x05: { // Meter readings obj.activeImportReadingLowTariff = arrToUint40(bytes, 5); obj.activeImportReadingNormTariff = arrToUint40(bytes, 10); obj.activeExportReadingLowTariff = arrToUint40(bytes, 15); obj.activeExportReadingNormTariff = arrToUint40(bytes, 20); // Active power averages obj.actL1ImportPowerAver = decodePower(arrToUint16(bytes, 25)); obj.actL2ImportPowerAver = decodePower(arrToUint16(bytes, 27)); obj.actL3ImportPowerAver = decodePower(arrToUint16(bytes, 29)); obj.actL1ExportPowerAver = decodePower(arrToUint16(bytes, 31)); obj.actL2ExportPowerAver = decodePower(arrToUint16(bytes, 33)); obj.actL3ExportPowerAver = decodePower(arrToUint16(bytes, 35)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 37)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 37)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 37)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 41)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 41)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 41)[2]; // Tariff indicator obj.tariff = bytes[45]; break; } // Msg type 6 case 0x06: { // Meter readings obj.activeImportReadingLowTariff = arrToUint40(bytes, 5); obj.activeImportReadingNormTariff = arrToUint40(bytes, 10); // Gas meter data obj.gasMeterTimeStamp = arrToUint32(bytes, 15); obj.gasMetertimeString = timeString(obj.gasMeterTimeStamp); obj.gasMeterReading = arrToUint32(bytes, 19); // Active power averages obj.activeImportPowerAver = decodePower(arrToUint16(bytes, 23)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 25)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 25)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 25)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 29)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 29)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 29)[2]; // Tariff indicator obj.tariff = bytes[33]; break; } // Msg type 7 case 0x07: { // Meter readings obj.activeImportReadingLowTariff = arrToUint40(bytes, 5); obj.activeImportReadingNormTariff = arrToUint40(bytes, 10); obj.activeExportReadingLowTariff = arrToUint40(bytes, 15); obj.activeExportReadingNormTariff = arrToUint40(bytes, 20); // Gas meter data obj.gasMeterTimeStamp = arrToUint32(bytes, 25); obj.gasMetertimeString = timeString(obj.gasMeterTimeStamp); obj.gasMeterReading = arrToUint32(bytes, 29); // Active power averages obj.activeImportPowerAver = decodePower(arrToUint16(bytes, 33)); obj.activeExportPowerAver = decodePower(arrToUint16(bytes, 35)); // Line voltages obj.l1VoltageAver_mV = decodeVoltages(bytes, 37)[0]; obj.l2VoltageAver_mV = decodeVoltages(bytes, 37)[1]; obj.l3VoltageAver_mV = decodeVoltages(bytes, 37)[2]; // Line currents obj.l1CurrentPeak_mA = decodeCurrents(bytes, 41)[0]; obj.l2CurrentPeak_mA = decodeCurrents(bytes, 41)[1]; obj.l3CurrentPeak_mA = decodeCurrents(bytes, 41)[2]; // Tariff indicator obj.tariff = bytes[45]; break; } default: { console.log("Unsupported msg type: "); console.log(obj.msgType); } } } else if (fPort === 10) { obj.equipmentIdStr = String.fromCharCode.apply(String, bytes); } else if (fPort === 11) { obj.gasMeterEquipmentIdStr = String.fromCharCode.apply(String, bytes); } console.log("LHI110 JSON object: " + JSON.stringify(obj)); return obj; }