Payload Format
Codec
A complete LoRaWAN payload codec with examples can be found in the downloads section. See also Decoder example for reference.
Overview
The payload of up and downlink messages consists of an arbitrary number of data structs (DS) of different types and lengths.
| DS 1 | DS 2 | ... | DS n |
|---|---|---|---|
Each data struct is a combination of a header and the actual data payload:
| L | T | payload |
|---|---|---|
The header consists of two fields:
| Field | Description |
|---|---|
| L | Length of data struct, 1 byte, not including the length byte itself |
| T | Data struct type, 1 byte |
Data Encoding
Signed integers use two's complement for encoding.
Important
Unless otherwise noted, payloads will use little endian data encoding.
Uplinks
All uplinks are sent on LoRaWAN port 15.
Regular Status Message
The device sends regular status messages at a configurable interval. The content of the status message consists of three messages:
- Battery Message (only EU868 MHz devices)
- Current scene Message
- Battery voltage Message
An additional purpose of the status message is to allow reception of downlink messages if the network server does not support class C.
Battery
This device doesn't have batteries. The battery message can be ignored.
Message type T = 0x02 and length L = 0x05 for EU868 devices
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x05) | uint8 |
| 1 | 1 | Message type (0x02) | uint8 |
| 2-5 | 4 | Accu uAh | uint32 |
Example
Payload 05:02:00:00:00:55 reports accumulated energy consumption of 85 uAh
CurrentScene
The CurrentScene message type is used to get information about the current scene. It is included in every regular status message and is also sent in response to a Get Current Scene downlink.
Message type T = 0x04 and length L = 0x02.
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x02) | uint8 |
| 1 | 1 | Message type (0x04) | uint8 |
| 2 | 1 | Current Scene | uint8 |
Example
Payload 02:04:07 reports current scene 7
BatteryVoltage
This device doesn't have batteries. The battery voltage message returns the measured voltage and temperature from the microcontroller.
Message type T = 0x03 and length L = 0x03.
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x03) | uint8 |
| 1 | 1 | Message type (0x03) | uint8 |
| 2 | 1 | Battery Voltage | uint8 |
| 3 | 1 | Temperature | uint8 |
Example
Payload 03:03:0C:E4 reports voltage raw value 0x0C and temperature raw value 0xE4
CurrentSceneColor
This uplink is sent in response to a Configure Scene Color downlink command. It reports the stored color of the queried scene.
Message type T = 0x05 and length L = 0x06.
Warning
Message type value is not visible in main.c — confirm numeric T value from the enum definition in the firmware headers.
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x06) | uint8 |
| 1 | 1 | Message type (0x05) | uint8 |
| 2 | 1 | Scene number | uint8 |
| 3 | 1 | Red value | uint8 |
| 4 | 1 | Green value | uint8 |
| 5 | 1 | Blue value | uint8 |
CurrentSceneTimeout
This uplink is sent in response to a Configure Scene Timeout downlink command. It reports the stored timeout of the queried scene.
Message type T = 0x06 and length L = 0x05.
Warning
Message type value is not visible in main.c — confirm numeric T value from the enum definition in the firmware headers.
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x05) | uint8 |
| 1 | 1 | Message type (0x06) | uint8 |
| 2 | 1 | Scene number | uint8 |
| 3-4 | 2 | Timeout (0=no timeout) | uint16, minutes |
CurrentSceneMelody
This uplink is sent in response to a Configure Scene Melody downlink command. It reports the stored melody and repeat setting of the queried scene.
Message type T = (0x07) and length L = 0x05.
Warning
Message type value is not visible in main.c — confirm numeric T value from the enum definition in the firmware headers.
| Byte | Size | Description | Format |
|---|---|---|---|
| 0 | 1 | Message length (0x05) | uint8 |
| 1 | 1 | Message type (0x07) | uint8 |
| 2 | 1 | Scene number | uint8 |
| 3 | 1 | Melody (see melody enum) | enum |
| 4 | 1 | Repeat (0=disabled, 1=enabled) | bool |
Downlinks
Downlink messages are used to change the configuration of the device. They use the same general payload format as uplinks.
Important
All down-links must be sent on the LoRaWAN port 3
Device Configuration
The device configuration message type is used to set general device configuration.
Message type T = 0x80 and length L = 0x06.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x06) | 0x06 | |
| 1 | 1 | Message type (0x80) | 0x80 | |
| 2 | 1 | Flags, bitwise or combination: Bit 7 = Buzzer (0 = off, 1 = on) Bit 6 = LoRaWAN Class (0 = A, 1 = C) Bit 5 = Duty Cycle (0 = off, 1 = on) Bit 4 = Confirmed uplinks (0 = off, 1 = on) Bits 0-3: RFU |
Bitfield | |
| 3-4 | 2 | Keep Alive interval in minutes | uint16 | 0..0xFFFF |
| 5 | 1 | Number of LEDs in ring (max 48) | uint8 | 0..48 |
| 6 | 1 | Automatic reset time (0 = disabled) | uint8, hours | 0..0xFF |
Example
payload 06:80:60:00:0A:10:0A will result in following configuration options
| Config option | Value |
|---|---|
| Flags | Class C, Duty Cycling on, Buzzer off |
| Status interval | 10 minutes |
| Number of LEDs | 16 |
| Reset time | 10 |
Set Scene
This downlink command is used to activate a scene. A scene is a preprogrammed LED color. The same color is set on all LEDs. Optionally, a scene also includes a buzzer melody to be played, either once or on repeat until the scene ends. Only a select few melodies are available. A scene is valid until the next scene will be set with another downlink command or until its preconfigured timeout is reached and the LEDs and buzzer is switched off.
Message type T = 0x81 and length L = 0x02:
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x02) | uint8 | 0x02 |
| 1 | 1 | Message type (0x81) | uint8 | 0x81 |
| 2 | 1 | Scene (0=Off) | uint8 | 0..NB_SCENES |
Example
payload 02:81:01 will activate scene 1 on the device
Set Brightness
Set LED brightness.
Message type T = 0x82 and length L = 0x02.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x02) | uint8 | 0x02 |
| 1 | 1 | Message type (0x82) | uint8 | 0x82 |
| 2 | 1 | Brightness [0=darkest,255=brightest] | uint8 | 0..0xFF |
Example
payload 02:82:80 will set brightness to 128 which is equal to 50% of maximum brightness
Set Volume
Set buzzer volume.
Message type T = 0x85 and length L = 0x02.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x02) | uint8 | 0x02 |
| 1 | 1 | Message type (0x85) | uint8 | 0x85 |
| 2 | 1 | Volume [0=off,1=low,2=medium,3=high] | enum | 0..3 |
Note
It is recommended to use the buzzer flag instead of setting the volume to 0.
Example
payload 02:85:03 will set buzzer volume to high (maximum)
Configure Scene (LED)
Configure an LED scene by changing its color and timeout time. Note that scene 0 (Off mode) can't be changed.
Message type T = 0x83 and length L = 0x07 (single scene).
Note
Multiple scenes can be configured in a single message. Each additional scene adds 6 bytes and L increases accordingly.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x07) | 0x07 | |
| 1 | 1 | Message type (0x83) | 0x83 | |
| 2 | 1 | Scene to configure | uint8 | 1..NB_SCENES |
| 3 | 1 | Red value | uint8 | 0..0xFF |
| 4 | 1 | Green value | uint8 | 0..0xFF |
| 5 | 1 | Blue value | uint8 | 0..0xFF |
| 6-7 | 2 | Timeout time of scene (0=no timeout) | uint16, minutes | 0..0xFFFF |
Example
payload 07:83:02:FF:9A:00:00:0A will configure scene 2 with following values
| Parameter | Value |
|---|---|
| Red value | 0xFF = 255 |
| Green value | 0x9A = 154 |
| Blue value | 0x00 = 0 |
| Timeout time | 10 minutes |
The hex color 0xff9a00 is a yellow.
Configure Scene (LED and Buzzer/HSP sirene)
Configure an LED scene by changing its color and timeout time. Additionally, configure the buzzer melody for this specific scene. Note that scene 0 (Off mode) can't be changed.
Message type T = 0x84 and length L = 0x09 (single scene).
Note
Multiple scenes can be configured in a single message. Each additional scene adds 8 bytes and L increases accordingly.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x09) | 0x09 | |
| 1 | 1 | Message type (0x84) | 0x84 | |
| 2 | 1 | Scene to configure | uint8 | 1..NB_SCENES |
| 3 | 1 | Red value | uint8 | 0..0xFF |
| 4 | 1 | Green value | uint8 | 0..0xFF |
| 5 | 1 | Blue value | uint8 | 0..0xFF |
| 6-7 | 2 | Timeout time of scene (0=no timeout) | uint16, minutes | 0..0xFFFF |
| 8 | 1 | Melody: 0 = None 1 = Fast 2 = Medium 3 = Slow 4 = Ascending 5 = DoubleUp 6 = Double 7 = Triple 8 = Always On (HSP ON) 9 = Always Off (HSP OFF) 10 = Short Tone 1 Sec Period (HSP 1 sec) |
enum | 0..10 |
| 9 | 1 | Repeat [0=disabled, 1=enabled] | bool | 0,1 |
Example
payload 09:84:02:FF:9A:00:00:0A:03:01 will configure scene 2 with following values
| Parameter | Value |
|---|---|
| Red value | 0xFF = 255 |
| Green value | 0x9A = 154 |
| Blue value | 0x00 = 0 |
| Timeout time | 10 minutes |
| Melody | slow |
| Repeat | enabled |
The hex color 0xff9a00 is a yellow.
Example for HSP siren:
- Configure Scene 1 Green, no HSP siren:
09:84:01:00:FF:00:00:00:09:00 - Configure Scene 3 Red, with HSP siren:
09:84:03:FF:00:00:00:00:08:01
Configure Scene Color (AS923 compatible)
Configure only the LED color of a scene. This message is a split version of "Configure Scene (LED and Buzzer)" to fit within AS923 downlink payload size limits.
Adding an offset of 100 to the scene number triggers a read-only operation: the device does not write to flash but instead responds with a CurrentSceneColor uplink.
Note that scene 0 (Off mode) can't be changed.
Message type T = 0x89 and length L = 0x05 (single scene).
Note
Multiple scenes can be configured in a single message. Each additional scene adds 4 bytes and L increases accordingly.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x05) | uint8 | 0x05 |
| 1 | 1 | Message type (0x89) | uint8 | 0x89 |
| 2 | 1 | Scene to configure (add 100 to scene number for read-only) | uint8 | 1..NB_SCENES or 101..NB_SCENES+100 |
| 3 | 1 | Red value | uint8 | 0..0xFF |
| 4 | 1 | Green value | uint8 | 0..0xFF |
| 5 | 1 | Blue value | uint8 | 0..0xFF |
Configure scene 2 color
payload 05:89:02:FF:9A:00 will configure scene 2 color
| Parameter | Value |
|---|---|
| Scene | 2 |
| Red value | 0xFF = 255 |
| Green value | 0x9A = 154 |
| Blue value | 0x00 = 0 |
Read back scene 2 color
payload 05:89:66:00:00:00 triggers a read-back of scene 2 (0x66 = 102 = 100 + 2). The device responds with a CurrentSceneColor uplink. The color bytes are ignored.
Configure Scene Timeout (AS923 compatible)
Configure only the timeout of a scene. This message is a split version of "Configure Scene (LED and Buzzer)" to fit within AS923 downlink payload size limits.
Adding an offset of 100 to the scene number triggers a read-only operation: the device does not write to flash but instead responds with a CurrentSceneTimeout uplink.
Note that scene 0 (Off mode) can't be changed.
Message type T = 0x8A and length L = 0x04 (single scene).
Note
Multiple scenes can be configured in a single message. Each additional scene adds 3 bytes and L increases accordingly.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x04) | uint8 | 0x04 |
| 1 | 1 | Message type (0x8A) | uint8 | 0x8A |
| 2 | 1 | Scene to configure (add 100 to scene number for read-only) | uint8 | 1..NB_SCENES or 101..NB_SCENES+100 |
| 3-4 | 2 | Timeout time of scene (0=no timeout) | uint16, minutes | 0..0xFFFF |
Set scene 2 timeout to 10 minutes
payload 04:8A:02:00:0A
| Parameter | Value |
|---|---|
| Scene | 2 |
| Timeout time | 10 minutes |
Read back scene 2 timeout
payload 04:8A:66:00:00 triggers a read-back of scene 2 (0x66 = 102 = 100 + 2). The device responds with a CurrentSceneTimeout uplink. The timeout bytes are ignored.
Configure Scene Melody (AS923 compatible)
Configure the melody and repeat settings of a scene. This message is a split version of "Configure Scene (LED and Buzzer)" to fit within AS923 downlink payload size limits.
Adding an offset of 100 to the scene number triggers a read-only operation: the device does not write to flash but instead responds with a CurrentSceneMelody uplink.
Note that scene 0 (Off mode) can't be changed.
Message type T = 0x8B and length L = 0x04 (single scene).
Note
Multiple scenes can be configured in a single message. Each additional scene adds 3 bytes and L increases accordingly.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x04) | uint8 | 0x04 |
| 1 | 1 | Message type (0x8B) | uint8 | 0x8B |
| 2 | 1 | Scene to configure (add 100 to scene number for read-only) | uint8 | 1..NB_SCENES or 101..NB_SCENES+100 |
| 3 | 1 | Melody: 0 = None 1 = Fast 2 = Medium 3 = Slow 4 = Ascending 5 = DoubleUp 6 = Double 7 = Triple 8 = Always On (HSP ON) 9 = Always Off (HSP OFF) 10 = Short Tone 1 Sec Period (HSP 1 sec) |
enum | 0..10 |
| 4 | 1 | Repeat [0=disabled, 1=enabled] | bool | 0,1 |
Set scene 2 melody to Slow, repeating
payload 04:8B:02:03:01
| Parameter | Value |
|---|---|
| Scene | 2 |
| Melody | Slow |
| Repeat | enabled |
Read back scene 2 melody
payload 04:8B:66:03:01 triggers a read-back of scene 2 (0x66 = 102 = 100 + 2). The device responds with a CurrentSceneMelody uplink. The melody/repeat bytes are ignored.
Get Current Scene
Request the currently active scene. The device responds with a CurrentScene uplink on port 15.
Available on devices produced after 2026.
Message type T = 0x8C and length L = 0x01.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x01) | uint8 | 0x01 |
| 1 | 1 | Message type (0x8C) | uint8 | 0x8C |
Example
payload 01:8C requests the current scene from the device
Set Scene Type
Configure the visual animation type and speed of a scene. This determines how the LEDs display the configured color (static, blinking, or breathing).
Note that scene 0 (Off mode) can't be changed.
No possiblity to read back over LoRaWAN.
Message type T = 0x87 and length L = 0x05 (single scene).
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x05) | uint8 | 0x05 |
| 1 | 1 | Message type | uint8 | 0x87 |
| 2 | 1 | Scene to configure | uint8 | 1..NB_SCENES |
| 3 | 1 | Scene type: 0 = Static 1 = Blinking 2 = Breathing |
enum | 0..2 |
| 4-5 | 2 | Animation speed in ms (period for blink/breathe) | uint16, ms | 0..0xFFFF |
Configure scene 2 to breathe with 1000ms period
payload: 05:87:02:02:03:E8
| Parameter | Value |
|---|---|
| Scene | 2 |
| Type | Breathing (2) |
| Speed | 0x03E8 = 1000 ms |
Print Trace (attention: only expert use)
Request the device to output the internal backtrace log to the debug UART. This command has no effect on LEDs or buzzer.
Message type T = 0x86 and length L = 0x01.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x01) | uint8 | 0x01 |
| 1 | 1 | Message type | uint8 | 0x86 |
Set LED Timing (attention: only expert use)
Configure low-level WS2812B LED signal timing parameters. This is an advanced command for tuning timing to match specific LED requirements.
Message type T = 0x88 and length L = 0x0B.
| Byte | Size | Description | Format | Value Range |
|---|---|---|---|---|
| 0 | 1 | Message length (0x0B) | uint8 | 0x0B |
| 1 | 1 | Message type | uint8 | 0x88 |
| 2-3 | 2 | Low time before data (µs) | uint16 BE | 0..0xFFFF |
| 4-5 | 2 | Low time after data (µs) | uint16 BE | 0..0xFFFF |
| 6-7 | 2 | High time after commit (µs) | uint16 BE | 0..0xFFFF |
| 8-9 | 2 | Logic 1 high duration (% of period) | uint16 BE | 0..100 |
| 10-11 | 2 | Logic 0 high duration (% of period) | uint16 BE | 0..100 |
Decoder example
var MELODIES = [
"None", "Fast", "Medium", "Slow", "Ascending",
"DoubleUp", "Double", "Triple",
"AlwaysOn", "AlwaysOff", "ShortTone1SecPeriod"
];
var SCENE_TYPES = ["Static", "Blinking", "Breathing"];
function melodyName(value) {
return value < MELODIES.length ? MELODIES[value] : "Unknown(" + value + ")";
}
function melodyIndex(name) {
var idx = MELODIES.indexOf(name);
return idx >= 0 ? idx : -1;
}
function decodeUplink(input) {
var bytes = input.bytes;
var port = input.fPort;
var decoded = {};
var warnings = [];
var errors = [];
if (port === 15) {
var idx = 0;
var total = bytes.length;
while (idx < total) {
var length = bytes[idx];
var type = bytes[idx + 1];
switch (type) {
case 2: // eMsgBattery – EU868 only, little-endian uint32
decoded.battery_usage_uah =
bytes[idx + 2] +
bytes[idx + 3] * 256 +
bytes[idx + 4] * 65536 +
bytes[idx + 5] * 16777216;
break;
case 3: // eMsgBatteryVoltage + temperature (one byte each)
decoded.battery_voltage = bytes[idx + 2];
decoded.battery_temperature = bytes[idx + 3];
break;
case 4: // eMsgCurrentScene
decoded.current_scene = bytes[idx + 2];
break;
case 5: // eMsgCurrentSceneColor – scene readout response
decoded.scene_color_scene = bytes[idx + 2];
decoded.scene_color_r = bytes[idx + 3];
decoded.scene_color_g = bytes[idx + 4];
decoded.scene_color_b = bytes[idx + 5];
break;
case 6: // eMSgCurrentSceneTimeout – scene readout response
decoded.scene_timeout_scene = bytes[idx + 2];
decoded.scene_timeout_min = bytes[idx + 4] + bytes[idx + 3] * 256;
break;
case 7: // eMsgCurrentSceneMelody – scene readout response
decoded.scene_melody_scene = bytes[idx + 2];
decoded.scene_melody = melodyName(bytes[idx + 3]);
decoded.scene_melody_repeat = Boolean(bytes[idx + 4]);
break;
default:
warnings.push("Unknown uplink type: " + type);
break;
}
idx += length + 1;
}
} else {
errors.push("Uplink is not on port 15");
}
var output = { data: decoded };
if (warnings.length > 0) output.warnings = warnings;
if (errors.length > 0) output.errors = errors;
return output;
}
function decodeDownlink(input) {
var bytes = input.bytes;
var port = input.fPort;
var decoded = {};
var warnings = [];
var errors = [];
if (port === 3) {
var idx = 0;
var total = bytes.length;
while (idx < total) {
var length = bytes[idx];
var type = bytes[idx + 1];
switch (type) {
case 128: // eMsgConfiguration (0x80)
decoded.settings_confirmed = Boolean(bytes[idx + 2] & 0x80);
decoded.settings_duty_cycle = Boolean(bytes[idx + 2] & 0x40);
decoded.settings_class_c = Boolean(bytes[idx + 2] & 0x20);
decoded.settings_buzzer = Boolean(bytes[idx + 2] & 0x10);
decoded.settings_status_interval_min = bytes[idx + 4] + bytes[idx + 3] * 256;
decoded.settings_num_led = bytes[idx + 5];
decoded.settings_reset_time_h = bytes[idx + 6];
break;
case 129: // eMsgControlLED (0x81) – Set Scene
decoded.current_scene = bytes[idx + 2];
break;
case 130: // eMsgSetBrightness (0x82)
decoded.settings_brightness_percent = Math.round(100 / 255 * bytes[idx + 2]);
break;
case 131: // eMsgSetScene (0x83) – Configure Scene (LED only)
decoded.selected_scene = bytes[idx + 2];
decoded.scene_red = bytes[idx + 3];
decoded.scene_green = bytes[idx + 4];
decoded.scene_blue = bytes[idx + 5];
decoded.scene_timeout_min = bytes[idx + 7] + bytes[idx + 6] * 256;
break;
case 132: // eMsgSetSceneBuzzer (0x84) – Configure Scene (LED + Buzzer)
decoded.selected_scene = bytes[idx + 2];
decoded.scene_red = bytes[idx + 3];
decoded.scene_green = bytes[idx + 4];
decoded.scene_blue = bytes[idx + 5];
decoded.scene_timeout_min = bytes[idx + 7] + bytes[idx + 6] * 256;
decoded.scene_buzzer_melody = melodyName(bytes[idx + 8]);
decoded.scene_buzzer_repeat = Boolean(bytes[idx + 9]);
break;
case 133: // eMsgSetVolume (0x85)
switch (bytes[idx + 2]) {
case 0: decoded.settings_volume = "off"; break;
case 1: decoded.settings_volume = "low"; break;
case 2: decoded.settings_volume = "medium"; break;
case 3: decoded.settings_volume = "high"; break;
default:
warnings.push("Unknown volume: " + bytes[idx + 2]);
break;
}
break;
case 135: // eMsgSetSceneType (0x87)
decoded.scene_type_scene = bytes[idx + 2];
decoded.scene_type = bytes[idx + 3] < SCENE_TYPES.length
? SCENE_TYPES[bytes[idx + 3]]
: "Unknown(" + bytes[idx + 3] + ")";
decoded.scene_type_speed_ms = bytes[idx + 5] + bytes[idx + 4] * 256;
break;
case 137: // eMsgSetSceneColor (0x89) – Configure Scene Color, AS923
decoded.scene_color_scene = bytes[idx + 2];
decoded.scene_color_r = bytes[idx + 3];
decoded.scene_color_g = bytes[idx + 4];
decoded.scene_color_b = bytes[idx + 5];
break;
case 138: // eMsgSetSceneTimeout (0x8A) – Configure Scene Timeout, AS923
decoded.scene_timeout_scene = bytes[idx + 2];
decoded.scene_timeout_min = bytes[idx + 4] + bytes[idx + 3] * 256;
break;
case 139: // eMsgSetSceneMelody (0x8B) – Configure Scene Melody, AS923
decoded.scene_melody_scene = bytes[idx + 2];
decoded.scene_melody = melodyName(bytes[idx + 3]);
decoded.scene_melody_repeat = Boolean(bytes[idx + 4]);
break;
case 140: // eMsgGetCurrentScene (0x8C)
decoded.get_current_scene = true;
break;
default:
warnings.push("Unknown downlink type: " + type);
break;
}
idx += length + 1;
}
} else {
errors.push("Downlink is not on port 3");
}
var output = { data: decoded };
if (warnings.length > 0) output.warnings = warnings;
if (errors.length > 0) output.errors = errors;
return output;
}
function checkAllKeys(keys, object) {
for (var i in keys) {
if (!(keys[i] in object)) return false;
}
return true;
}
function checkOneKey(keys, object) {
for (var i in keys) {
if (keys[i] in object) return true;
}
return false;
}
function encodeDownlink(input) {
var warnings = [];
var errors = [];
var output = { fPort: 3, bytes: [] };
var data = input.data;
var configKeys = [
"settings_confirmed", "settings_duty_cycle", "settings_class_c", "settings_buzzer",
"settings_status_interval_min", "settings_num_led", "settings_reset_time_h"
];
var configSceneKeys = ["selected_scene", "scene_red", "scene_green", "scene_blue", "scene_timeout_min"];
var configSceneBuzzerKeys = ["selected_scene", "scene_red", "scene_green", "scene_blue",
"scene_timeout_min", "scene_buzzer_melody", "scene_buzzer_repeat"];
// Device Configuration (0x80)
if (checkOneKey(configKeys, data)) {
if (checkAllKeys(configKeys, data)) {
var flags = 0;
if (data.settings_confirmed) flags |= 0x80;
if (data.settings_duty_cycle) flags |= 0x40;
if (data.settings_class_c) flags |= 0x20;
if (data.settings_buzzer) flags |= 0x10;
output.bytes.push(6, 128, flags);
output.bytes.push((parseInt(data.settings_status_interval_min) >> 8) & 0xff);
output.bytes.push(parseInt(data.settings_status_interval_min) & 0xff);
output.bytes.push(parseInt(data.settings_num_led) & 0xff);
output.bytes.push(parseInt(data.settings_reset_time_h) & 0xff);
} else {
errors.push("All config keys required: " + JSON.stringify(configKeys));
}
}
// Set Scene (0x81)
if ("current_scene" in data) {
output.bytes.push(2, 129);
output.bytes.push(parseInt(data.current_scene) & 0xff);
}
// Set Brightness (0x82)
if ("settings_brightness_percent" in data) {
var brightness = parseFloat(data.settings_brightness_percent);
output.bytes.push(2, 130);
if (brightness >= 0 && brightness <= 100) {
output.bytes.push(Math.round(brightness / 100 * 255) & 0xff);
} else {
errors.push("Brightness must be 0–100 (percent)");
output.bytes.push(0);
}
}
// Set Volume (0x85)
if ("settings_volume" in data) {
output.bytes.push(2, 133);
switch (data.settings_volume) {
case "off": output.bytes.push(0); break;
case "low": output.bytes.push(1); break;
case "medium": output.bytes.push(2); break;
case "high": output.bytes.push(3); break;
default:
output.bytes.push(0);
errors.push("Volume must be 'off', 'low', 'medium', or 'high'");
break;
}
}
// Configure Scene LED only (0x83) – only when buzzer keys are absent
if (checkAllKeys(configSceneKeys, data) && !checkOneKey(["scene_buzzer_melody", "scene_buzzer_repeat"], data)) {
var sceneNo = parseInt(data.selected_scene);
if (sceneNo < 1 || sceneNo > 22) {
errors.push("Scene must be 1–22");
sceneNo = 1;
}
output.bytes.push(7, 131, sceneNo & 0xff);
output.bytes.push(parseInt(data.scene_red) & 0xff);
output.bytes.push(parseInt(data.scene_green) & 0xff);
output.bytes.push(parseInt(data.scene_blue) & 0xff);
output.bytes.push((parseInt(data.scene_timeout_min) >> 8) & 0xff);
output.bytes.push(parseInt(data.scene_timeout_min) & 0xff);
}
// Configure Scene LED + Buzzer (0x84)
if (checkAllKeys(configSceneBuzzerKeys, data)) {
var sceneNo = parseInt(data.selected_scene);
if (sceneNo < 1 || sceneNo > 22) {
errors.push("Scene must be 1–22");
sceneNo = 1;
}
output.bytes.push(9, 132, sceneNo & 0xff);
output.bytes.push(parseInt(data.scene_red) & 0xff);
output.bytes.push(parseInt(data.scene_green) & 0xff);
output.bytes.push(parseInt(data.scene_blue) & 0xff);
output.bytes.push((parseInt(data.scene_timeout_min) >> 8) & 0xff);
output.bytes.push(parseInt(data.scene_timeout_min) & 0xff);
var mIdx = melodyIndex(data.scene_buzzer_melody);
if (mIdx >= 0) {
output.bytes.push(mIdx);
} else {
output.bytes.push(0);
errors.push("Melody must be one of: " + JSON.stringify(MELODIES));
}
output.bytes.push(data.scene_buzzer_repeat ? 1 : 0);
}
// Configure Scene Type (0x87)
if ("scene_type_scene" in data) {
var sceneNo = parseInt(data.scene_type_scene);
var typeIdx = SCENE_TYPES.indexOf(data.scene_type);
var speed = parseInt(data.scene_type_speed_ms) || 0;
if (sceneNo < 1 || sceneNo > 22) {
errors.push("Scene must be 1–22");
sceneNo = 1;
}
if (typeIdx < 0) {
errors.push("Scene type must be one of: " + JSON.stringify(SCENE_TYPES));
typeIdx = 0;
}
output.bytes.push(5, 135, sceneNo & 0xff, typeIdx);
output.bytes.push((speed >> 8) & 0xff);
output.bytes.push(speed & 0xff);
}
// Configure Scene Color, AS923 (0x89)
if ("scene_color_scene" in data) {
output.bytes.push(5, 137);
output.bytes.push(parseInt(data.scene_color_scene) & 0xff);
output.bytes.push(parseInt(data.scene_color_r) & 0xff);
output.bytes.push(parseInt(data.scene_color_g) & 0xff);
output.bytes.push(parseInt(data.scene_color_b) & 0xff);
}
// Configure Scene Timeout, AS923 (0x8A)
if ("scene_timeout_scene" in data) {
var timeout = parseInt(data.scene_timeout_min) || 0;
output.bytes.push(4, 138);
output.bytes.push(parseInt(data.scene_timeout_scene) & 0xff);
output.bytes.push((timeout >> 8) & 0xff);
output.bytes.push(timeout & 0xff);
}
// Configure Scene Melody, AS923 (0x8B)
if ("scene_melody_scene" in data) {
var mIdx = melodyIndex(data.scene_melody);
if (mIdx < 0) {
errors.push("Melody must be one of: " + JSON.stringify(MELODIES));
mIdx = 0;
}
output.bytes.push(4, 139);
output.bytes.push(parseInt(data.scene_melody_scene) & 0xff);
output.bytes.push(mIdx);
output.bytes.push(data.scene_melody_repeat ? 1 : 0);
}
// Get Current Scene (0x8C)
if ("get_current_scene" in data && data.get_current_scene) {
output.bytes.push(1, 140);
}
if (warnings.length > 0) output.warnings = warnings;
if (errors.length > 0) output.errors = errors;
return output;
}