Uplink Decoder:
// Decode uplink function. // // Input is an object with the following fields: // - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0] // - fPort = Uplink fPort. // - variables = Object containing the configured device variables. // // Output must be an object with the following fields: // - output = Object representing the decoded payload. function decodeUplink(input) { return { data: Decode(input.fPort, input.bytes, input.variables) }; } function get_user_mode(input) { var user_mode = input[9] & 0x7; switch (user_mode) { case 0: return "Valve_Position"; case 1: return "RESERVED"; case 2: return "SP_Ambient_Temperature"; case 3: return "Detecting_Opening_Point"; case 4: return "Slow_Harvesting"; case 5: return "Temperature_Drop"; case 6: return "Freeze_Protect"; case 7: return "Forced_Heating"; default: return "Unknown Operating Mode"; } } function get_user_value(input) { var user_mode = get_user_mode(input); switch (user_mode) { case "Valve_Position": case "Freeze_Protect": case "Forced_Heating": return input[10]; case "SP_Ambient_Temperature": return input[10] * 0.5; case "Detecting_Opening_Point": case "Slow_Harvesting": return input[10] * 0.25; default: return "Invalid User Mode"; } } function decode_port_1(input) { var output = { Current_Valve_Position: input[0], Flow_Sensor_Raw: input[1] * 0.5, Flow_Temperature: input[2] * 0.5, Ambient_Sensor_Raw: input[3] * 0.25, Ambient_Temperature: input[4] * 0.25, Temperature_Drop_Detection: input[5] >> 7 & 0x01, Energy_Storage: input[5] >> 6 & 0x01, Harvesting_Active: input[5] >> 5 & 0x01, Ambient_Sensor_Failure: input[5] >> 4 & 0x01, Flow_Sensor_Failure: input[5] >> 3 & 0x01, Radio_Communication_Error: input[5] >> 2 & 0x01, Received_Signal_Strength: input[5] >> 1 & 0x01, Motor_Error: input[5] >> 0 & 0x01, Storage_Voltage: Number((input[6] * 0.02).toFixed(2)), Average_Current_Consumed: input[7] * 10, Average_Current_Generated: input[8] * 10, Operating_Condition: input[9] >> 7 & 0x01, Storage_Fully_Charged: input[9] >> 6 & 0x01, Zero_Error: input[9] >> 5 & 0x01, Calibration_OK: input[9] >> 4 & 0x01, } output.User_Mode = get_user_mode(input); output.User_Value = get_user_value(input); if (input.length == 12) { utmp = input[11] * 0.25; output.Used_Temperature = utmp; } return output; } function Decode(fPort, bytes) { var output = {}; switch (fPort) { case 1: output = decode_port_1(bytes); break; default: return { errors: ['unknown FPort'], }; } return output; }
Downlink Encoder:
// Encode downlink function. // // Input is an object with the following fields: // - output = Object representing the payload that must be encoded. // - variables = Object containing the configured device variables. // // Output must be an object with the following fields: // - bytes = Byte array containing the downlink payload. // Possible Options: // userMode : "Ambient_Temperature" or "Valve_Position" // setValue : 0 to 40 (0.5° resolution) for "Ambient_Temperature" // setValue : 0 to 100 (1% resolution) for "Valve_Position" // safetyMode : "Ambient_Temperature" or "Valve_Position" // safetyValue : 0 to 40 (0.5° resolution) for "Ambient_Temperature" // safetyValue : 0 to 100 (1% resolution) for "Valve_Position" // roomTemperature : 0 (Internal Ambient Estimate) or Temperature from External Sensor (0.25° resolution) // radioInterval : 5 or 10 or 60 or 120 or 480 (minutes) // doReferenceRunNow : 0 or 1 (0-False; 1-True) function encodeDownlink(input) { let mode = input.data.userMode; // "Ambient_Temperature" or "Valve_Position" let safetyMode = input.data.safetyMode; // "Ambient_Temperature" or "Valve_Position" let setValue = input.data.setValue; // 0-40 for Ambient_Temperature, 0-100 for Valve_Position let roomTemperature = input.data.roomTemperature; // 0-40 let safetyValue = input.data.safetyValue; // 0-40 for Ambient_Temperature, 0-100 for Valve_Position let radioInterval = input.data.radioInterval; // 5, 10, 60, 120, 480 let doReferenceRunNow = input.data.doReferenceRunNow; // 0 or 1 let bytes = [0, 0, 0, 0, 0, 0]; // Byte 1: Set value if (mode === "Ambient_Temperature") { if (setValue < 0 || setValue > 40) { throw new Error("Set value out of range for ambient mode"); } else { bytes[0] = setValue *2; } } else if (mode === "Valve_Position") { if (setValue < 0 || setValue > 100) { throw new Error("Set value out of range for valve mode"); } else { bytes[0] = setValue; } } else { throw new Error("Invalid user mode"); } // Byte 2: Room temperature (0-40) if (roomTemperature < 0 || roomTemperature > 40) { throw new Error("Room temperature out of range"); } else { bytes[1] = roomTemperature * 4; } // Byte 3: Safety value if (safetyMode === "Ambient_Temperature") { if (safetyValue < 0 || safetyValue > 40) { throw new Error("Safety value out of range for ambient mode"); } else { bytes[2] = safetyValue * 2; } } else if (safetyMode === "Valve_Position") { if (safetyValue < 0 || safetyValue > 100) { throw new Error("Safety value out of range for valve mode"); } else { bytes[2] = safetyValue; } } else { throw new Error("Invalid safety mode"); } // Byte 4: Radio interval, user mode, safety mode let radioBits; switch (radioInterval) { case 5: radioBits = 1 << 4; // Radio interval 5 minutes break; case 10: radioBits = 0 << 4; // Radio interval 10 minutes break; case 60: radioBits = 2 << 4; // Radio interval 60 minutes break; case 120: radioBits = 3 << 4; // Radio interval 120 minutes break; case 480: radioBits = 4 << 4; // Radio interval 480 minutes break; default: throw new Error("Invalid radio interval"); } let userModeBits; if (mode === "Ambient_Temperature") { userModeBits = 2 << 2; // User mode "Ambient_Temperature" in bits 3 and 4 } else { userModeBits = 0 << 2; // User mode "Valve_Position" in bits 3 and 4 } let safetyModeBits; if (safetyMode === "Ambient_Temperature") { safetyModeBits = 0 << 0; // Safety mode "Ambient_Temperature" in bits 1 and 2 } else { safetyModeBits = 2 << 0; // Safety mode "Valve_Position" in bits 1 and 2 } bytes[3] = radioBits | userModeBits | safetyModeBits; // Byte 5: Reserved (set to 0) bytes[4] = 0; // Byte 6: doReferenceRunNow bit (bit 8) if (doReferenceRunNow < 0 || doReferenceRunNow > 1) throw new Error("Invalid doReferenceRunNow value"); bytes[5] = doReferenceRunNow << 7; return { "bytes": bytes }; }
Downlink Encoder Example:
// Example 1 { "userMode": "Valve_Position", "setValue": 42, "roomTemperature": 0, "safetyMode": "Ambient_Temperature", "safetyValue": 19, "radioInterval": 5, "doReferenceRunNow": 1 } // Example 2 { "userMode": "Ambient_Temperature", "setValue": 21.5, "roomTemperature": 24, "safetyMode": "Valve_Position", "safetyValue": 0, "radioInterval": 60, "doReferenceRunNow": 0 } // Example 3 { "userMode": "Valve_Position", "setValue": 36, "roomTemperature": 0, "safetyMode": "Valve_Position", "safetyValue": 21, "radioInterval": 5, "doReferenceRunNow": 1 } // Example 4 { "userMode": "Ambient_Temperature", "setValue": 20.5, "roomTemperature": 23.75, "safetyMode": "Ambient_Temperature", "safetyValue": 18, "radioInterval": 10, "doReferenceRunNow": 0 }