/
MLR003 Encoder/Decoder Chirpstack
Copyright Micropelt
MLR003 Encoder/Decoder Chirpstack
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");
}
else {
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
}