diff --git a/ws-series/ws501-v4/README.md b/ws-series/ws501-v4/README.md
new file mode 100644
index 00000000..e2f9f53f
--- /dev/null
+++ b/ws-series/ws501-v4/README.md
@@ -0,0 +1,242 @@
+# Smart Wall Switch - WS501
+
+
+
+For more detailed information, please visit [Milesight Official Website](https://www.milesight.com/iot/)
+
+## Payload
+
+```
++-------------------------------------------------------+
+| DEVICE UPLINK / DOWNLINK PAYLOAD |
++---------------------------+---------------------------+
+| DATA 1 | DATA 2 |
++--------+--------+---------+--------+--------+---------+
+| ID | TYPE | DATA | ID | TYPE | DATA |
++--------+--------+---------+--------+--------+---------+
+| 1 Byte | 1 Byte | N Bytes | 1 Byte | 1 Byte | N Bytes |
+|--------+--------+---------+--------+--------+---------+
+```
+
+### Attribute
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :-----------: | :--: | :--: | :----: | ------------------------------------------------------------------------------------------------ |
+| IPSO | 0xFF | 0x01 | 1 | ipso_version(1B) |
+| Hardware | 0xFF | 0x09 | 2 | hardware_version(2B)
hardware_version, e.g. 0110 -> v1.1 |
+| Firmware | 0xFF | 0x0A | 2 | firmware_version(2B)
firmware_version, e.g. 0110 -> v1.10 |
+| TSL | 0xFF | 0xFF | 2 | tsl_version(2B) |
+| Serial Number | 0xFF | 0x16 | 8 | sn(8B) |
+| LoRaWAN Class | 0xFF | 0x0F | 1 | lorawan_class(1B)
lorawan_class, values: (1: Class B, 2: Class C) |
+| Reset Event | 0xFF | 0xFE | 1 | reset_event(1B) |
+| Device Status | 0xFF | 0x0B | 1 | device_status(1B) |
+
+### Telemetry
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :----------------------: | :--: | :--: | :----: | -------------------------------------------------------------------------------------------------------------------------------------- |
+| Reporting Interval | 0xFF | 0x03 | 2 | reporting_interval(2B) |
+| Reboot | 0xFF | 0x10 | 1 | Reboot device |
+| Button Lock Config | 0xFF | 0x25 | 2 | enable(1B)
enable, values(0: disable, 1: enable) |
+| Report Status | 0xFF | 0x28 | 1 | report_status(1B) |
+| Switch Control | 0xFF | 0x29 | 1 | status_1(0..1)
status_1, values(0: off, 1: on) |
+| Report Attribute | 0xFF | 0x2C | 1 | report_attribute(1B) |
+| LED Mode | 0xFF | 0x2F | 1 | led_mode(1B)
led_mode, values(0: disable, 1: Enable (relay closed indicator off)) |
+| Button Reset Config | 0xFF | 0x5E | 1 | button_reset_config(1B)
button_reset_config, values(0: disable, 1: enable) |
+| Power Consumption Enable | 0xFF | 0x26 | 1 | power_consumption_enable(1B)
power_consumption_enable, values(0: disable, 1: enable) |
+| Power Consumption Clear | 0xFF | 0x27 | 1 | power_consumption_clear(1B) |
+| Overcurrent Alarm Config | 0xFF | 0x24 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| Overcurrent Protection | 0xFF | 0x30 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| High Current Config | 0xFF | 0x8D | 1 | highcurrent_config(1B)
highcurrent_config, values(0: disable, 1: enable) |
+| Power Switch Mode | 0xFF | 0x67 | 1 | power_switch_mode(1B)
power_switch_mode, values(0: off, 1: on, 2: keep) |
+| Time Synchronize | 0xFF | 0x4A | 1 | time_synchronize(1B) |
+| D2D Enable | 0xFF | 0xC7 | 1 | master_enable(0..1)+agent_enable(1..2)+master_enable_change(4..5)+agent_enable_change(5..6)
master_enable, values(0: disable, 1: enable)
agent_enable, values(0: disable, 1: enable)
master_enable_change, values(0: no, 1: yes)
agent_enable_change, values(0: no, 1: yes) |
+| D2D Agent Settings | 0xFF | 0x83 | 5 | d2d_agent_id(1B)+d2d_agent_enable(1B)+d2d_agent_command(2B)+d2d_agent_action(1B)
d2d_agent_id, range[0, 15]
d2d_agent_enable, values(0: disable, 1: enable)
d2d_agent_action.switch_object, values(1: switch1)
d2d_agent_action.switch_status, values(0: off, 1: on, 2: reversal) |
+| Time Zone | 0xFF | 0xBD | 2 | time_zone(2B) |
+| Schedule Settings | 0xF9 | 0x64 | 7 | channel(1B), range[1, 16]
enable(0..4), values(1: enable, 2: disable), use_config(4..8), values(0, no, 1: yes)
read: bits, (bit1: monday, bit2: tuesday, bit3: wednesday, bit4: thursday, bit5: friday, bit6: saturday, bit7: sunday)
execut_hour(1B)
execut_min(1B)
switch_1_state(0..2), values(0: keep, 1: on, 2: off, 3: reversal)
lock_state(1B), values(0: keep, 1: lock, 2: unlock) |
+| Get Local Rule | 0xF9 | 0x65 | 1 | task_id(1B), range[1, 16] & 255 |
+| Anti Flash Mode | 0xF9 | 0xAA | 1 | enable(0..1), values(0: disable, 1: enable) |
+| Load Power | 0xF9 | 0xAB | 6 | power_1(2B), range[0, 1100] |
+| LoRaWAN Class Config | 0xF9 | 0xA4 | 8 | timestamp(4B)+continue(2B)+class_mode(1B)
class_mode, values(1: CLASS_B, 2: CLASS_C) |
+| D2D Controller Settings | 0xF9 | 0xB8 | 5 | keyid(1B)+key_contrl_enable(1B)+uplink(1B)+contrl_cmd(2B)
keyid, values(0: key1)
key_contrl_enable, values(0: disable, 1: enable)
uplink.lora_enable(0..1), values(0: disable, 1: enable), uplink.key_enable(1..2), values(0: disable, 1: enable) |
+| Daylight Saving Time | 0xF9 | 0x72 | 9 | daylight_saving_time_offset(0..6)+daylight_saving_time_enable(6..7)+start_month(1B)+start_week_num(0..3)+start_week_day(4..7)+start_hour_min(2B)+end_month(1B)+end_week_num(0..3)+end_week_day(4..7)+end_hour_min(2B)
daylight_saving_time_offset, range[1, 120]
daylight_saving_time_enable, values(0: disable, 1: enable)
start_month,end_month, values(1: Jan., 2: Feb., 3: Mar., 4: Apr., 5: May, 6: Jun., 7: Jul., 8: Aug. 9: Sep., 10: Oct., 11: Nov., 12: Dec.)
start_week_num,end_week_num, values(1: 1st, 2: 2nd, 3: 3rd, 4: 4th, 5: last)
start_week_day,end_week_day, values(1:Mon., 2:Tues., 3:Wed., 4:Thurs., 5:Fri., 6:Sat., 7:Sun.)
start_hour_min,end_hour_min, range[0, 1440] |
+
+### Status Definition
+
+| bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+| :--: | :-: | :-------------: | :-------------: | :-------------: | :-: | :------: | :------: | :------: |
+| | - | switch_3_change | switch_2_change | switch_1_change | - | switch_3 | switch_2 | switch_1 |
+
+## Example
+
+```json
+// Reporting Interval (FF033C00)
+{
+ "reporting_interval": 60
+}
+
+// Reboot (FF10FF)
+{
+ "reboot": "yes"
+}
+
+// Button Lock Config (FF250080)
+{
+ "button_lock_config": {
+ "enable": "enable"
+ }
+}
+
+// Report Status (FF28FF)
+{
+ "report_status": "yes"
+}
+
+// Switch Control (FF2910)
+{
+ "switch_control": {
+ "status_1": "off"
+ }
+}
+
+// Report Attribute (FF2CFF)
+{
+ "report_attribute": "yes"
+}
+
+// LED Mode (FF2F00)
+{
+ "led_mode":"disable"
+}
+
+// Button Reset Config (FF5E01)
+{
+ "button_reset_config": "enable"
+}
+
+// Power Consumption Enable (FF2601)
+{
+ "power_consumption_enable": "enable"
+}
+
+// Power Consumption Clear (FF2701)
+{
+ "power_consumption_clear": "yes"
+}
+
+// Overcurrent Alarm (FF24010A)
+{
+ "overcurrent_alarm_config": {
+ "enable": "enable",
+ "threshold": 10
+ }
+}
+
+// Overcurrent Protection (FF300101)
+{
+ "overcurrent_protection": {
+ "enable": "enable",
+ "threshold": 1
+ }
+}
+
+// High Current Config (FF8D01)
+{
+ "highcurrent_config": "enable"
+}
+
+// Schedule Settings (F96401117F01010102)
+{
+ "schedule_settings": [
+ {
+ "channel": 1,
+ "enable": "enable",
+ "execut_hour": 1,
+ "execut_min": 1,
+ "friday": "enable",
+ "lock_state": "unlock",
+ "monday": "enable",
+ "saturday": "enable",
+ "sunday": "enable",
+ "switch_1_state": "on",
+ "thursday": "enable",
+ "tuesday": "enable",
+ "use_config": "yes",
+ "wednesday": "enable"
+ }
+ ]
+}
+
+// D2D Agent Settings (FF830001191711)
+{
+ "d2d_agent_settings_array": [
+ {
+ "d2d_agent_action": {
+ "switch_object": "switch1",
+ "switch_status": "on"
+ },
+ "d2d_agent_command": "1719",
+ "d2d_agent_enable": "enable",
+ "d2d_agent_id": 0
+ }
+ ]
+}
+
+// Get Local Rule (F96501)
+{
+ "get_local_rule": {
+ "task_id": 1
+ }
+}
+
+// Anti Flash Mode (F9AA01)
+{
+ "anti_flash_mode": {
+ "enable": "enable"
+ }
+}
+
+// Load Power (F9AB580100000000)
+{
+ "load_power": {
+ "power_1": 344
+ }
+}
+
+// LoRaWAN Class Config(F9A40000000000000100)
+{
+ "lorawan_class_cfg": {
+ "timestamp": 0,
+ "continue": 0,
+ "class_mode": "CLASS_B"
+ }
+}
+
+// D2D Controller Settings (F9B80001023412)
+{
+ "key1_d2d_controller_settings": {
+ "key_contrl_enable": "enable",
+ "uplink":{
+ "lora_enable":"enable",
+ "key_enable":"enable"
+ },
+ "contrl_cmd": "1234"
+ }
+}
+
+// Daylight Saving Time (F9726F011126020111FF00)
+{
+ "daylight_saving_time": {
+ "daylight_saving_time_enable": "disable",
+ "end_month": 1,
+ "end_hour_min": 255,
+ "end_week_day": 1,
+ "end_week_num": 1,
+ "daylight_saving_time_offset": 111,
+ "start_month": 1,
+ "start_hour_min": 550,
+ "start_week_day": 1,
+ "start_week_num": 1
+ }
+}
+```
diff --git a/ws-series/ws501-v4/ws501-v4-codec.json b/ws-series/ws501-v4/ws501-v4-codec.json
new file mode 100644
index 00000000..21206e6f
--- /dev/null
+++ b/ws-series/ws501-v4/ws501-v4-codec.json
@@ -0,0 +1,1179 @@
+{
+ "version": "1.0.0",
+ "object": [
+ {
+ "id": "voltage",
+ "name": "Voltage",
+ "value": "",
+ "unit": "V",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "FLOAT",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 5,
+ "bacnet_unit_type": "UNITS_VOLTS"
+ },
+ {
+ "id": "electric_power",
+ "name": "Electric Power",
+ "value": "",
+ "unit": "W",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 47,
+ "bacnet_unit_type": "UNITS_WATTS"
+ },
+ {
+ "id": "power_factor",
+ "name": "Power Factor",
+ "value": "",
+ "unit": "%",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 0,
+ 100
+ ],
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 98,
+ "bacnet_unit_type": "UNITS_PERCENT"
+ },
+ {
+ "id": "power_consumption",
+ "name": "Power Consumption",
+ "value": "",
+ "unit": "Wh",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 18,
+ "bacnet_unit_type": "UNITS_WATT_HOURS"
+ },
+ {
+ "id": "current",
+ "name": "Current Rating",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES"
+ },
+ {
+ "id": "switch_reading.status_1",
+ "name": "Switch 1",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "reporting_interval",
+ "name": "Reporting Interval",
+ "value": "1200",
+ "unit": "s",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 60,
+ 64800
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 73,
+ "bacnet_unit_type": "UNITS_SECONDS"
+ },
+ {
+ "id": "power_consumption_enable",
+ "name": "Power Consumption Enable",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "led_mode",
+ "name": "LED Indicator",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "Enable (relay closed indicator off)",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_reset_config",
+ "name": "Reset Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_lock_config.enable",
+ "name": "Switch Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "overcurrent_alarm_config.enable",
+ "name": "Overcurrent Alarm Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm_config.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm_config.threshold",
+ "name": "Overcurrent Alarm Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_alarm_config.enable"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.enable",
+ "name": "Overcurrent Protection Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_protection.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.threshold",
+ "name": "Overcurrent Protection Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_protection.enable"
+ ]
+ },
+ {
+ "id": "highcurrent_config",
+ "name": "Highcurrent Protection",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "power_switch_mode",
+ "name": "Power Switch Mode",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ },
+ {
+ "name": "keep",
+ "value": 2
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "d2d_global_enable.master_enable",
+ "name": "D2D Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.agent_enable"
+ ]
+ },
+ {
+ "id": "d2d_global_enable.agent_enable",
+ "name": "D2D Agent Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.master_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink",
+ "key1_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.key_contrl_enable",
+ "key1_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "time_zone",
+ "name": "Time Zone",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "INT16",
+ "values": [
+ {
+ "name": "UTC-12(IDLW)",
+ "value": -720
+ },
+ {
+ "name": "UTC-11(SST)",
+ "value": -660
+ },
+ {
+ "name": "UTC-10(HST)",
+ "value": -600
+ },
+ {
+ "name": "UTC-9:30(MIT)",
+ "value": -570
+ },
+ {
+ "name": "UTC-9(AKST)",
+ "value": -540
+ },
+ {
+ "name": "UTC-8(PST)",
+ "value": -480
+ },
+ {
+ "name": "UTC-7(MST)",
+ "value": -420
+ },
+ {
+ "name": "UTC-6(CST)",
+ "value": -360
+ },
+ {
+ "name": "UTC-5(EST)",
+ "value": -300
+ },
+ {
+ "name": "UTC-4(AST)",
+ "value": -240
+ },
+ {
+ "name": "UTC-3:30(NST)",
+ "value": -210
+ },
+ {
+ "name": "UTC-3(BRT)",
+ "value": -180
+ },
+ {
+ "name": "UTC-2(FNT)",
+ "value": -120
+ },
+ {
+ "name": "UTC-1(CVT)",
+ "value": -60
+ },
+ {
+ "name": "UTC(WET)",
+ "value": 0
+ },
+ {
+ "name": "UTC+1(CET)",
+ "value": 60
+ },
+ {
+ "name": "UTC+2(EET)",
+ "value": 120
+ },
+ {
+ "name": "UTC+3(MSK)",
+ "value": 180
+ },
+ {
+ "name": "UTC+3:30(IRST)",
+ "value": 210
+ },
+ {
+ "name": "UTC+4(GST)",
+ "value": 240
+ },
+ {
+ "name": "UTC+4:30(AFT)",
+ "value": 270
+ },
+ {
+ "name": "UTC+5(PKT)",
+ "value": 300
+ },
+ {
+ "name": "UTC+5:30(IST)",
+ "value": 330
+ },
+ {
+ "name": "UTC+5:45(NPT)",
+ "value": 345
+ },
+ {
+ "name": "UTC+6(BHT)",
+ "value": 360
+ },
+ {
+ "name": "UTC+6:30(MMT)",
+ "value": 390
+ },
+ {
+ "name": "UTC+7(ICT)",
+ "value": 420
+ },
+ {
+ "name": "UTC+8(CT/CST)",
+ "value": 480
+ },
+ {
+ "name": "UTC+9(JST)",
+ "value": 540
+ },
+ {
+ "name": "UTC+9:30(ACST)",
+ "value": 570
+ },
+ {
+ "name": "UTC+10(AEST)",
+ "value": 600
+ },
+ {
+ "name": "UTC+10:30(LHST)",
+ "value": 630
+ },
+ {
+ "name": "UTC+11(VUT)",
+ "value": 660
+ },
+ {
+ "name": "UTC+12(NZST)",
+ "value": 720
+ },
+ {
+ "name": "UTC+12:45(CHAST)",
+ "value": 765
+ },
+ {
+ "name": "UTC+13(PHOT)",
+ "value": 780
+ },
+ {
+ "name": "UTC+14(LINT)",
+ "value": 840
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES"
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_enable",
+ "name": "DST Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_offset",
+ "name": "DST Bias",
+ "value": "1",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 120
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.current",
+ "name": "Current",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES",
+ "reference": [
+ "overcurrent_alarm.status"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overcurrent",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm.current"
+ ]
+ },
+ {
+ "id": "device_abnormal_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "abnormal",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "temperature_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overtemperature",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ws-series/ws501-v4/ws501-v4-decoder.js b/ws-series/ws501-v4/ws501-v4-decoder.js
new file mode 100644
index 00000000..1ae2fc70
--- /dev/null
+++ b/ws-series/ws501-v4/ws501-v4-decoder.js
@@ -0,0 +1,750 @@
+/**
+ * Payload Decoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS501
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function decodeUplink(input) {
+ var decoded = milesightDeviceDecode(input.bytes);
+ return { data: decoded };
+}
+
+// Chirpstack v3
+function Decode(fPort, bytes) {
+ return milesightDeviceDecode(bytes);
+}
+
+// The Things Network
+function Decoder(bytes, port) {
+ return milesightDeviceDecode(bytes);
+}
+/* eslint-enable */
+
+function milesightDeviceDecode(bytes) {
+ var decoded = {};
+
+ for (var i = 0; i < bytes.length;) {
+ var channel_id = bytes[i++];
+ var channel_type = bytes[i++];
+
+ // IPSO VERSION
+ if (channel_id === 0xff && channel_type === 0x01) {
+ decoded.ipso_version = readProtocolVersion(bytes[i]);
+ i += 1;
+ }
+ // HARDWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x09) {
+ decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // FIRMWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x0a) {
+ decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // TSL VERSION
+ else if (channel_id === 0xff && channel_type === 0xff) {
+ decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // SERIAL NUMBER
+ else if (channel_id === 0xff && channel_type === 0x16) {
+ decoded.sn = readSerialNumber(bytes.slice(i, i + 8));
+ i += 8;
+ }
+ // LORAWAN CLASS TYPE
+ else if (channel_id === 0xff && channel_type === 0x0f) {
+ decoded.lorawan_class = readLoRaWANClass(bytes[i]);
+ i += 1;
+ }
+ // RESET EVENT
+ else if (channel_id === 0xff && channel_type === 0xfe) {
+ decoded.reset_event = readResetEvent(1);
+ i += 1;
+ }
+ // DEVICE STATUS
+ else if (channel_id === 0xff && channel_type === 0x0b) {
+ decoded.device_status = readDeviceStatus(1);
+ i += 1;
+ }
+ // SWITCH STATE
+ else if (channel_id === 0x08 && channel_type === 0x29) {
+ // payload (0 0 0 0 0 0 0 0)
+ // Switch 3 2 1 3 2 1
+ // ------- -------
+ // bit mask change state
+ var value = bytes[i];
+ var switch_bit_offset = { status_1: 0 };
+ decoded.switch_reading = {};
+ for (var key in switch_bit_offset) {
+ decoded.switch_reading[key] = readOnOffStatus((value >>> switch_bit_offset[key]) & 0x01);
+ decoded.switch_reading[key + '_change'] = readYesNoStatus((value >>> (switch_bit_offset[key] + 4)) & 0x01);
+ }
+ i += 1;
+ }
+ // Voltage
+ else if (channel_id === 0x03 && channel_type === 0x74) {
+ decoded.voltage = readUInt16LE(bytes.slice(i, i + 2)) / 10;
+ i += 2;
+ }
+ // Electric Power
+ else if (channel_id === 0x04 && channel_type === 0x80) {
+ decoded.electric_power = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Power Factor
+ else if (channel_id === 0x05 && channel_type === 0x81) {
+ decoded.power_factor = readUInt8(bytes.slice(i, i + 1));
+ i += 1;
+ }
+ // Power Consumption
+ else if (channel_id === 0x06 && channel_type === 0x83) {
+ decoded.power_consumption = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Current Rating
+ else if (channel_id === 0x07 && channel_type === 0xc9) {
+ decoded.current = readUInt16LE(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // Over Current Alarm
+ else if (channel_id === 0x87 && channel_type === 0xc9) {
+ decoded.overcurrent_alarm = {};
+ decoded.overcurrent_alarm.current = readUInt16LE(bytes.slice(i, i + 2));
+ decoded.overcurrent_alarm.status = readOverCurrentStatus(bytes.slice(i + 2, i + 3));
+ i += 3;
+ }
+ // Device Abnormal Alarm
+ else if (channel_id === 0x88 && channel_type === 0x29) {
+ decoded.device_abnormal_alarm = {};
+ decoded.device_abnormal_alarm.status = readAlarmStatus(bytes[i]);
+ i += 1;
+ }
+ // Temperature Alarm
+ else if (channel_id === 0x89 && channel_type === 0xdf) {
+ decoded.temperature_alarm = {};
+ decoded.temperature_alarm.status = readTemperatureAlarmStatus(bytes[i]);
+ i += 1;
+ }
+ // RULE CONFIG
+ else if (channel_id === 0xf9 && channel_type === 0x64) {
+ var schedule_settings = readScheduleSettings(bytes.slice(i, i + 7));
+ i += 7;
+
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ }
+ // DOWNLINK RESPONSE
+ else if (channel_id === 0xfe || channel_id === 0xff) {
+ var result = handle_downlink_response(channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else if (channel_id === 0xf8 || channel_id === 0xf9) {
+ var result = handle_downlink_response_ext(channel_id, channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else {
+ break;
+ }
+ }
+
+ return decoded;
+}
+
+function handle_downlink_response(channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x03:
+ decoded.reporting_interval = readUInt16LE(bytes.slice(offset, offset + 2));
+ offset += 2;
+ break;
+ case 0x10:
+ decoded.reboot = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x25:
+ var data = readUInt16LE(bytes.slice(offset, offset + 2));
+ decoded.button_lock_config = {};
+ decoded.button_lock_config.enable = readEnableStatus((data >>> 15) & 0x01);
+ offset += 2;
+ break;
+ case 0x28:
+ decoded.report_status = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x29:
+ var data = readUInt8(bytes[offset]);
+ var switch_bit_offset = { status_1: 0 };
+ decoded.switch_control = {};
+ for (var key in switch_bit_offset) {
+ if ((data >>> (switch_bit_offset[key] + 4)) & 0x01) {
+ decoded.switch_control[key] = readOnOffStatus((data >>> (switch_bit_offset[key])) & 0x01);
+ }
+ }
+ offset += 1;
+ break;
+ case 0x2c:
+ decoded.report_attribute = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x2f:
+ decoded.led_mode = readLedMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x5e:
+ decoded.button_reset_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x26:
+ decoded.power_consumption_enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x27:
+ decoded.power_consumption_clear = readYesNoStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x24:
+ decoded.overcurrent_alarm_config = {};
+ decoded.overcurrent_alarm_config.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_alarm_config.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x30:
+ decoded.overcurrent_protection = {};
+ decoded.overcurrent_protection.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_protection.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x8d:
+ decoded.highcurrent_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x67:
+ decoded.power_switch_mode = readPowerSwitchMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x4a:
+ decoded.time_synchronize = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0xc7:
+ var d2d_enable_data = readUInt8(bytes[offset]);
+ offset += 1;
+
+ decoded.d2d_global_enable = {};
+ decoded.d2d_global_enable.master_enable = readEnableStatus((d2d_enable_data >>> 0) & 1);
+ decoded.d2d_global_enable.master_enable_change = readYesNoStatus((d2d_enable_data >>> 4) & 1);
+ decoded.d2d_global_enable.agent_enable = readEnableStatus((d2d_enable_data >>> 1) & 1);
+ decoded.d2d_global_enable.agent_enable_change = readYesNoStatus((d2d_enable_data >>> 5) & 1);
+ break;
+ case 0x83:
+ var d2d_agent_settings = readD2DAgentSettings(bytes.slice(offset, offset + 5));
+ offset += 5;
+ decoded.d2d_agent_settings_array = decoded.d2d_agent_settings_array || [];
+ decoded.d2d_agent_settings_array.push(d2d_agent_settings);
+ break;
+ case 0xbd:
+ decoded.time_zone = readTimeZone(readInt16LE(bytes.slice(offset, offset + 2)));
+ offset += 2;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function handle_downlink_response_ext(code, channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x64:
+ var schedule_settings_result = readUInt8(bytes[offset + 7]);
+ if (schedule_settings_result === 0) {
+ var schedule_settings = readScheduleSettings(bytes.slice(offset, offset + 7));
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ } else {
+ decoded.schedule_settings_result = readScheduleSettingsResult(schedule_settings_result);
+ }
+ offset += 7;
+ break;
+ case 0x65:
+ var data = readUInt8(bytes[offset]);
+ decoded.get_local_rule = {};
+ if (data === 0xff) {
+ decoded.get_local_rule.task_id = 'all schedules';
+ } else {
+ decoded.get_local_rule.task_id = data;
+ }
+ offset += 1;
+ break;
+ case 0xaa:
+ decoded.anti_flash_mode = {};
+ decoded.anti_flash_mode.enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0xab:
+ decoded.load_power = {};
+ var power_bit_offset = [ "power_1" ];
+ for(var i= 0; i < power_bit_offset.length; i++) {
+ decoded.load_power[power_bit_offset[i]] = readUInt16LE(bytes.slice(offset + i * 2, offset + (i + 1) * 2));
+ }
+ offset += 6;
+ break;
+ case 0xa4:
+ decoded.lorawan_class_cfg = readLorawanClassCfg(bytes.slice(offset, offset + 8));
+ offset += 8;
+ break;
+ case 0xb8:
+ decoded.key1_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ offset += 5;
+ break;
+ case 0x72:
+ decoded.daylight_saving_time = readDstConfig(bytes.slice(offset, offset + 9));
+ offset += 9;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ if (hasResultFlag(code)) {
+ var result_value = readUInt8(bytes[offset]);
+ offset += 1;
+
+ if (result_value !== 0) {
+ var request = decoded;
+ decoded = {};
+ decoded.device_response_result = {};
+ decoded.device_response_result.channel_type = channel_type;
+ decoded.device_response_result.result = readResultStatus(result_value);
+ decoded.device_response_result.request = request;
+ }
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function readDstConfig(bytes) {
+ var offset = 0;
+
+ var data = bytes[offset];
+ var enable_value = (data >> 7) & 0x01;
+ var offset_value = data & 0x7f;
+
+ var daylight_saving_time = {};
+ daylight_saving_time.daylight_saving_time_enable = readEnableStatus(enable_value);
+ daylight_saving_time.daylight_saving_time_offset = offset_value;
+ daylight_saving_time.start_month = readUInt8(bytes[offset + 1]);
+ var start_week_value = readUInt8(bytes[offset + 2]);
+ daylight_saving_time.start_week_num = start_week_value >> 4;
+ daylight_saving_time.start_week_day = start_week_value & 0x0f;
+ daylight_saving_time.start_hour_min = readUInt16LE(bytes.slice(offset + 3, offset + 5));
+ daylight_saving_time.end_month = readUInt8(bytes[offset + 5]);
+ var end_week_value = readUInt8(bytes[offset + 6]);
+ daylight_saving_time.end_week_num = end_week_value >> 4;
+ daylight_saving_time.end_week_day = end_week_value & 0x0f;
+ daylight_saving_time.end_hour_min = readUInt16LE(bytes.slice(offset + 7, offset + 9));
+ offset += 9;
+
+ return daylight_saving_time;
+}
+
+function hasResultFlag(code) {
+ return code === 0xf8;
+}
+
+function readProtocolVersion(bytes) {
+ var major = (bytes & 0xf0) >> 4;
+ var minor = bytes & 0x0f;
+ return "v" + major + "." + minor;
+}
+
+function readResultStatus(status) {
+ var status_map = {
+ 0: "success",
+ 1: "forbidden",
+ 2: "out of range",
+ 16: "continue is 0",
+ 17: "The continue is greater than the maximum value allowed by the device",
+ 18: "Command expires (start time + continue <= current time)"
+ };
+ return getValue(status_map, status);
+}
+
+function readHardwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff) >> 4;
+ return "v" + major + "." + minor;
+}
+
+function readFirmwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff).toString(16);
+ return "v" + major + "." + minor;
+}
+
+function readTslVersion(bytes) {
+ var major = bytes[0] & 0xff;
+ var minor = bytes[1] & 0xff;
+ return "v" + major + "." + minor;
+}
+
+function readSerialNumber(bytes) {
+ var temp = [];
+ for (var idx = 0; idx < bytes.length; idx++) {
+ temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2));
+ }
+ return temp.join("");
+}
+
+function readLoRaWANClass(type) {
+ var class_map = {
+ 1: "Class B",
+ 2: "Class C",
+ };
+ return getValue(class_map, type);
+}
+
+function readResetEvent(status) {
+ var status_map = { 0: "normal", 1: "reset" };
+ return getValue(status_map, status);
+}
+
+function readDeviceStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readOnOffStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readYesNoStatus(status) {
+ var status_map = { 0: "no", 1: "yes" };
+ return getValue(status_map, status);
+}
+
+function readLedMode(type) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ return getValue(led_mode_map, type);
+}
+
+function readTemperatureAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "overtemperature" };
+ return getValue(status_map, status);
+}
+
+function readAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "abnormal" };
+ return getValue(status_map, status);
+}
+
+function readOverCurrentStatus(status) {
+ var status_map = { 0: "normal", 1: "overcurrent" };
+ return getValue(status_map, status);
+}
+
+function readScheduleSettings(bytes) {
+ var offset = 0;
+
+ var schedule_settings = {};
+ schedule_settings.channel = readUInt8(bytes[offset]);
+ schedule_settings.enable = readEnableStatus2(bytes[offset + 1] & 0x03);
+ schedule_settings.use_config = readYesNoStatus(bytes[offset + 1] >> 4 & 0x01);
+ // condition
+ var day_bit_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var day = readUInt8(bytes[offset + 2]);
+ for (var key in day_bit_offset) {
+ schedule_settings[key] = readEnableStatus((day >> day_bit_offset[key]) & 0x01);
+ }
+ schedule_settings.execut_hour = readUInt8(bytes[offset + 3]);
+ schedule_settings.execut_min = readUInt8(bytes[offset + 4]);
+
+ // action
+ var switch_bit_offset = { switch_1_state: 0 };
+ var switch_raw_data = readUInt8(bytes[offset + 5]);
+ for (var key in switch_bit_offset) {
+ schedule_settings[key] = readSwitchStatus((switch_raw_data >> switch_bit_offset[key]) & 0x03);
+ }
+ schedule_settings.lock_state = readChildLockStatus(bytes[offset + 6]);
+
+ return schedule_settings;
+}
+
+function readScheduleSettingsResult(result) {
+ var schedule_settings_result_map = {
+ 0: "success",
+ 2: "failed, out of range",
+ 17: "success, conflict with channel=1",
+ 18: "success, conflict with channel=2",
+ 19: "success, conflict with channel=3",
+ 20: "success, conflict with channel=4",
+ 21: "success, conflict with channel=5",
+ 22: "success, conflict with channel=6",
+ 23: "success, conflict with channel=7",
+ 24: "success, conflict with channel=8",
+ 25: "success, conflict with channel=9",
+ 26: "success, conflict with channel=10",
+ 27: "success, conflict with channel=11",
+ 28: "success, conflict with channel=12",
+ 29: "success, conflict with channel=13",
+ 30: "success, conflict with channel=14",
+ 31: "success, conflict with channel=15",
+ 32: "success, conflict with channel=16",
+ 49: "failed, conflict with channel=1",
+ 50: "failed, conflict with channel=2",
+ 51: "failed, conflict with channel=3",
+ 52: "failed, conflict with channel=4",
+ 53: "failed, conflict with channel=5",
+ 54: "failed, conflict with channel=6",
+ 55: "failed, conflict with channel=7",
+ 56: "failed, conflict with channel=8",
+ 57: "failed, conflict with channel=9",
+ 58: "failed, conflict with channel=10",
+ 59: "failed, conflict with channel=11",
+ 60: "failed, conflict with channel=12",
+ 61: "failed, conflict with channel=13",
+ 62: "failed, conflict with channel=14",
+ 63: "failed, conflict with channel=15",
+ 64: "failed, conflict with channel=16",
+ 81: "failed,rule config empty",
+ };
+ return getValue(schedule_settings_result_map, result);
+}
+
+function readD2DCommand(bytes) {
+ return ("0" + (bytes[1] & 0xff).toString(16)).slice(-2) + ("0" + (bytes[0] & 0xff).toString(16)).slice(-2);
+}
+
+function readSwitchObject(switchs) {
+ var switch_object_map = {
+ 1: "switch1"
+ };
+ return getValue(switch_object_map, switchs);
+}
+
+function readActionSwitchStatus(status) {
+ var switch_status_map = {
+ 0: "off",
+ 1: "on",
+ 2: "reversel"
+ };
+ return getValue(switch_status_map, status);
+}
+
+function readD2DAgentSettings(bytes) {
+ var offset = 0;
+
+ var d2d_agent_settings = {};
+ d2d_agent_settings.d2d_agent_id = readUInt8(bytes[offset]);
+ d2d_agent_settings.d2d_agent_enable = readEnableStatus(bytes[offset + 1] & 0x01);
+ d2d_agent_settings.d2d_agent_command = readD2DCommand(bytes.slice(offset + 2, offset + 4));
+
+ d2d_agent_settings.d2d_agent_action = {};
+ var switchs = bytes[offset + 4] >> 4;
+ var status = bytes[offset + 4] & 0x0f;
+ d2d_agent_settings.d2d_agent_action.switch_object = readSwitchObject(switchs);
+ d2d_agent_settings.d2d_agent_action.switch_status = readActionSwitchStatus(status);
+
+ return d2d_agent_settings;
+}
+
+function readLorawanClassCfg(bytes) {
+ var offset = 0;
+
+ var lorawan_class_cfg = {};
+ lorawan_class_cfg.timestamp = readUInt32LE(bytes.slice(offset, offset + 4));
+ lorawan_class_cfg.continue = readUInt16LE(bytes.slice(offset + 4, offset + 6));
+ lorawan_class_cfg.class_mode = readClassMode(readUInt8(bytes.slice(offset + 6, offset + 7)));
+
+ return lorawan_class_cfg;
+}
+
+function readD2DControllerSettings(bytes) {
+ var offset = 0;
+
+ var d2d_controller_settings = {};
+ var keyid = readUInt8(bytes[offset]);
+ if(keyid !== 0) {
+ return d2d_controller_settings;
+ }
+ d2d_controller_settings.key_contrl_enable = readEnableStatus(bytes[offset + 1]);
+ d2d_controller_settings.uplink = {};
+ d2d_controller_settings.uplink.lora_enable = readEnableStatus(bytes[offset + 2] & 0x01);
+ d2d_controller_settings.uplink.key_enable = readEnableStatus(bytes[offset + 2] >> 1 & 0x01);
+ d2d_controller_settings.contrl_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));
+
+ return d2d_controller_settings;
+}
+
+function readSwitchStatus(status) {
+ var switch_status_map = { 0: "keep", 1: "on", 2: "off", 3: "reversal" };
+ return getValue(switch_status_map, status);
+}
+
+function readChildLockStatus(status) {
+ var child_lock_status_map = { 0: "keep", 1: "lock", 2: "unlock" };
+ return getValue(child_lock_status_map, status);
+}
+
+function readTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ return getValue(timezone_map, time_zone);
+}
+
+function readEnableStatus(status) {
+ var status_map = { 0: "disable", 1: "enable" };
+ return getValue(status_map, status);
+}
+
+function readEnableStatus2(status) {
+ var status_map = { 1: "enable", 2: "disable" };
+ return getValue(status_map, status);
+}
+
+function readPowerSwitchMode(mode) {
+ var mode_map = { 0: "off", 1: "on", 2: "keep" };
+ return getValue(mode_map, mode);
+}
+
+function readEnableMask(mask) {
+ var mask_map = { 0: "keep", 1: "set" };
+ return getValue(mask_map, mask);
+}
+
+function readClassMode(mode) {
+ var mode_map = { 1: "CLASS_B", 2: "CLASS_C" };
+ return getValue(mode_map, mode);
+}
+
+function readMonth(month) {
+ var month_map = {
+ 1:"Jan.",
+ 2:"Feb.",
+ 3: "Mar.",
+ 4: "Apr.",
+ 5: "May.",
+ 6: "Jun.",
+ 7: "Jul.",
+ 8: "Aug.",
+ 9: "Sep.",
+ 10: "Oct.",
+ 11: "Nov.",
+ 12: "Dec.",
+ };
+ return getValue(month_map, month);
+}
+
+function readWeek(week) {
+ var weeks_map = {
+ 1: "1st",
+ 2: "2nd",
+ 3: "3rd",
+ 4: "4th",
+ 5: "last"
+ };
+ return getValue(weeks_map, week);
+}
+
+function readWeekDay(day) {
+ var week_map = {
+ 1: "Mon.",
+ 2: "Tues.",
+ 3: "Wed.",
+ 4: "Thurs.",
+ 5: "Fri.",
+ 6: "Sat.",
+ 7: "Sun."
+ };
+ return getValue(week_map, day);
+}
+
+function readKeyNum(key_num) {
+ var key_map = { 0: "key1" };
+ return getValue(key_map, key_num);
+}
+
+/* eslint-disable */
+function readUInt8(bytes) {
+ return bytes & 0xff;
+}
+
+function readInt8(bytes) {
+ var ref = readUInt8(bytes);
+ return ref > 0x7f ? ref - 0x100 : ref;
+}
+
+function readUInt16LE(bytes) {
+ var value = (bytes[1] << 8) + bytes[0];
+ return value & 0xffff;
+}
+
+function readInt16LE(bytes) {
+ var ref = readUInt16LE(bytes);
+ return ref > 0x7fff ? ref - 0x10000 : ref;
+}
+
+function readUInt32LE(bytes) {
+ var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
+ return (value & 0xffffffff) >>> 0;
+}
+
+function readInt32LE(bytes) {
+ var ref = readUInt32LE(bytes);
+ return ref > 0x7fffffff ? ref - 0x100000000 : ref;
+}
+
+function getValue(map, key) {
+ if (RAW_VALUE) return key;
+
+ var value = map[key];
+ if (!value) value = "unknown";
+ return value;
+}
+
+if (!Object.assign) {
+ Object.defineProperty(Object, "assign", {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function (target) {
+ "use strict";
+ if (target == null) {
+ throw new TypeError("Cannot convert first argument to object");
+ }
+
+ var to = Object(target);
+ for (var i = 1; i < arguments.length; i++) {
+ var nextSource = arguments[i];
+ if (nextSource == null) {
+ continue;
+ }
+ nextSource = Object(nextSource);
+
+ var keysArray = Object.keys(Object(nextSource));
+ for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+ var nextKey = keysArray[nextIndex];
+ var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+ if (desc !== undefined && desc.enumerable) {
+ // concat array
+ if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {
+ to[nextKey] = to[nextKey].concat(nextSource[nextKey]);
+ } else {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ },
+ });
+}
diff --git a/ws-series/ws501-v4/ws501-v4-encoder.js b/ws-series/ws501-v4/ws501-v4-encoder.js
new file mode 100644
index 00000000..ceaf92d6
--- /dev/null
+++ b/ws-series/ws501-v4/ws501-v4-encoder.js
@@ -0,0 +1,927 @@
+/**
+ * Payload Encoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS501
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function encodeDownlink(input) {
+ var encoded = milesightDeviceEncode(input.data);
+ return { bytes: encoded };
+}
+
+// Chirpstack v3
+function Encode(fPort, obj) {
+ return milesightDeviceEncode(obj);
+}
+
+// The Things Network
+function Encoder(obj, port) {
+ return milesightDeviceEncode(obj);
+}
+/* eslint-enable */
+
+function milesightDeviceEncode(payload) {
+ var encoded = [];
+
+ if ("reboot" in payload) {
+ encoded = encoded.concat(reboot(payload.reboot));
+ }
+ if ("reporting_interval" in payload) {
+ encoded = encoded.concat(setReportingInterval(payload.reporting_interval));
+ }
+ if ("report_status" in payload) {
+ encoded = encoded.concat(reportStatus(payload.report_status));
+ }
+ if ("report_attribute" in payload) {
+ encoded = encoded.concat(reportAttribute(payload.report_attribute));
+ }
+ if ("led_mode" in payload) {
+ encoded = encoded.concat(setLedMode(payload.led_mode));
+ }
+ if ("button_lock_config" in payload) {
+ encoded = encoded.concat(setButtonLockConfig(payload.button_lock_config));
+ }
+ if("switch_control" in payload) {
+ encoded = encoded.concat(setSwitchControl(payload.switch_control));
+ }
+ if("button_reset_config" in payload) {
+ encoded = encoded.concat(setButtonResetConfig(payload.button_reset_config));
+ }
+ if("power_consumption_enable" in payload) {
+ encoded = encoded.concat(setPowerConsumptionEnable(payload.power_consumption_enable));
+ }
+ if("load_power" in payload) {
+ encoded = encoded.concat(setLoadPower(payload.load_power));
+ }
+ if("power_consumption_clear" in payload) {
+ encoded = encoded.concat(setPowerConsumptionClear(payload.power_consumption_clear));
+ }
+ if("schedule_settings" in payload) {
+ for (var schedule_index = 0; schedule_index < payload.schedule_settings.length; schedule_index++) {
+ var schedule = payload.schedule_settings[schedule_index];
+ encoded = encoded.concat(setScheduleSettings(schedule));
+ }
+ }
+ if("get_local_rule" in payload) {
+ encoded = encoded.concat(setGetLocalRule(payload.get_local_rule));
+ }
+ if("anti_flash_mode" in payload) {
+ encoded = encoded.concat(setAntiFlashMode(payload.anti_flash_mode));
+ }
+ if("overcurrent_alarm_config" in payload) {
+ encoded = encoded.concat(setOvercurrentAlarmConfig(payload.overcurrent_alarm_config));
+ }
+ if("overcurrent_protection" in payload) {
+ encoded = encoded.concat(setOvercurrentProtection(payload.overcurrent_protection));
+ }
+ if("highcurrent_config" in payload) {
+ encoded = encoded.concat(setHighcurrentConfig(payload.highcurrent_config));
+ }
+ if("power_switch_mode" in payload) {
+ encoded = encoded.concat(setPowerSwitchMode(payload.power_switch_mode));
+ }
+ if("lorawan_class_cfg" in payload) {
+ encoded = encoded.concat(setLorawanClassCfg(payload.lorawan_class_cfg));
+ }
+ if("time_synchronize" in payload) {
+ encoded = encoded.concat(setTimeSynchronize(payload.time_synchronize));
+ }
+ if("d2d_global_enable" in payload) {
+ encoded = encoded.concat(setD2DGlobalEnable(payload.d2d_global_enable));
+ }
+ if("d2d_agent_settings_array" in payload) {
+ for (var agent_index = 0; agent_index < payload.d2d_agent_settings_array.length; agent_index++) {
+ var agent = payload.d2d_agent_settings_array[agent_index];
+ encoded = encoded.concat(setD2DAgentSettings(agent));
+ }
+ }
+ if("key1_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key1_d2d_controller_settings));
+ }
+ if("time_zone" in payload) {
+ encoded = encoded.concat(setTimeZone(payload.time_zone));
+ }
+ if("daylight_saving_time" in payload) {
+ encoded = encoded.concat(setDaylightSavingTime(payload.daylight_saving_time));
+ }
+
+ return encoded;
+}
+
+/**
+ * reboot
+ * @param {number} reboot values: (0: "no", 1: "yes")
+ * @example { "reboot": 1 }
+ */
+function reboot(reboot) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(reboot) === -1) {
+ throw new Error("reboot must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, reboot) === 0) {
+ return [];
+ }
+ return [0xff, 0x10, 0xff];
+}
+
+/**
+ * report interval configuration
+ * @param {number} reporting_interval uint: second, range: [60, 64800]
+ * @example { "reporting_interval": 1200 }
+ */
+function setReportingInterval(reporting_interval) {
+ if (typeof reporting_interval !== "number") {
+ throw new Error("reporting_interval must be a number");
+ }
+ if (reporting_interval < 60 || reporting_interval > 64800) {
+ throw new Error("reporting_interval must be in the range of [60, 64800]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x03);
+ buffer.writeUInt16LE(reporting_interval);
+ return buffer.toBytes();
+}
+
+/**
+ * report status
+ * @param {number} report_status values: (0: "no", 1: "yes")
+ * @example { "report_status": 1 }
+ */
+function reportStatus(report_status) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_status) === -1) {
+ throw new Error("report_status must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_status) === 0) {
+ return [];
+ }
+ return [0xff, 0x28, 0xff];
+}
+
+/**
+ * report attribute
+ * @param {number} report_attribute values: (0: "no", 1: "yes")
+ * @example { "report_attribute": 1 }
+ */
+function reportAttribute(report_attribute) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_attribute) === -1) {
+ throw new Error("report_attribute must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_attribute) === 0) {
+ return [];
+ }
+ return [0xff, 0x2c, 0xff];
+}
+
+/**
+ * set led mode
+ * @param {number} led_mode, values: (0: "disable", 1: "Enable (relay closed indicator off)")
+ * @example { "led_mode": 1 }
+ */
+function setLedMode(led_mode) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ var led_mode_values = getValues(led_mode_map);
+ if (led_mode_values.indexOf(led_mode) === -1) {
+ throw new Error("led_mode must be one of: " + led_mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x2f);
+ buffer.writeUInt8(getValue(led_mode_map, led_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * button lock configuration
+ * @param {object} button_lock_config
+ * @param {number} button_lock_config.enable values: (0: "disable", 1: "enable")
+ * @example { "button_lock_config": { "enable": 1 } }
+ */
+function setButtonLockConfig(button_lock_config) {
+ var enable = button_lock_config.enable;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("button_lock_config.enable must be one of: " + enable_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 15;
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x25);
+ buffer.writeUInt16LE(data);
+ return buffer.toBytes();
+}
+
+/**
+ * switch control
+ * @param {object} switch_control
+ * @param {number} switch_control.status_1 values: (0: "off", 1: "on")
+ * @example { "switch_control": { "status_1": 1 } }
+ */
+function setSwitchControl(switch_control) {
+ var status_map = {0: "off", 1: "on"};
+ var status_values = getValues(status_map);
+
+ var data = 0x00;
+ var switch_bit_offset = { status_1: 0 };
+ for (var key in switch_bit_offset) {
+ if (key in switch_control) {
+ if (status_values.indexOf(switch_control[key]) === -1) {
+ throw new Error("switch_control." + key + " must be one of: " + status_values.join(", "));
+ }
+
+ data |= 1 << (switch_bit_offset[key] + 4);
+ data |= getValue(status_map, switch_control[key]) << switch_bit_offset[key];
+ }
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x29);
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * button reset configuration
+ * @param {number} button_reset_config values: (0: "disable", 1: "enable")
+ * @example { "button_reset_config": 1 }
+ */
+function setButtonResetConfig(button_reset_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(button_reset_config) === -1) {
+ throw new Error("button_reset_config must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x5e, getValue(enable_map, button_reset_config)];
+}
+
+/**
+ * power consumption enable
+ * @param {number} power_consumption_enable values: (0: "disable", 1: "enable")
+ * @example { "power_consumption_enable": 1 }
+ */
+function setPowerConsumptionEnable(power_consumption_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(power_consumption_enable) === -1) {
+ throw new Error("power_consumption_enable must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x26, getValue(enable_map, power_consumption_enable)];
+}
+
+/**
+ * load power
+ * @param {object} load_power
+ * @param {number} load_power.power_1
+ * @example { "load_power": {"power_1": 0 } }
+ */
+function setLoadPower(load_power) {
+ var powers = [ "power_1" ];
+ var buffer = new Buffer(8);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xab);
+ for(var i = 0; i < powers.length; i++) {
+ if (typeof load_power[powers[i]] !== "number") {
+ throw new Error("load_power." + powers[i] + " must be a number");
+ }
+ if (load_power[powers[i]] < 0 || load_power[powers[i]] > 1100) {
+ throw new Error("load_power." + powers[i] + " must be in the range of [0, 1100]");
+ }
+ buffer.writeUInt16LE(load_power[powers[i]]);
+ }
+
+ return buffer.toBytes();
+}
+
+/**
+ * power consumption clear
+ * @param {number} power_consumption_clear values: (0: "no", 1: "yes")
+ * @example { "power_consumption_clear": 1 }
+ */
+function setPowerConsumptionClear(power_consumption_clear) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(power_consumption_clear) === -1) {
+ throw new Error("power_consumption_clear must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, power_consumption_clear) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x27, 0x01];
+}
+
+/**
+ * set schedule settings
+ * @param {object} schedule_settings
+ * @param {number} schedule_settings.channel range: [1, 16]
+ * @param {number} schedule_settings.enable values: (1: "enable", 2: "disable")
+ * @param {number} schedule_settings.use_config values: (0: "no", 1: "yes")
+ * @param {number} schedule_settings.monday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.tuesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.wednesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.thursday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.friday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.saturday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.sunday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.execut_hour range: [0, 23]
+ * @param {number} schedule_settings.execut_min range: [0, 59]
+ * @param {number} schedule_settings.switch_1_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.lock_state values: (0: "keep", 1: "lock", 2: "unlock")
+ * @example { "schedule_settings": [{ "channel": 1, "enable": 1, "use_config": 1, "monday": 1, "tuesday": 0, "wednesday": 1, "thursday": 0, "friday": 1, "saturday": 0, "sunday": 1, "execut_hour": 10, "execut_min": 5, "switch_1_state": 1, "lock_state": 1 }] }
+ */
+function setScheduleSettings(schedule) {
+ var channel = schedule.channel;
+ var enable = schedule.enable;
+ var use_config = schedule.use_config;
+ var lock_state = schedule.lock_state;
+ var execut_hour = schedule.execut_hour;
+ var execut_min = schedule.execut_min;
+
+ if (typeof channel !== "number") {
+ throw new Error("schedule_settings._item.channel must be a number");
+ }
+ if (channel < 1 || channel > 16) {
+ throw new Error("schedule_settings._item.channel must be in range [1, 16]");
+ }
+ var enable_map = { 1: "enable", 2: "disable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("schedule_settings._item.enable must be one of " + enable_values.join(", "));
+ }
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(use_config) === -1) {
+ throw new Error("schedule_settings._item.use_config must be one of " + yes_no_values.join(", "));
+ }
+ var week_day_bits_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var days = 0x00;
+ var enable_map_2 = { 0: "disable", 1: "enable" };
+ var enable_values_2 = getValues(enable_map_2);
+ for (var day in week_day_bits_offset) {
+ if (enable_values_2.indexOf(schedule[day]) === -1) {
+ throw new Error("schedule_settings._item." + day + " must be one of " + enable_values_2.join(", "));
+ }
+ days |= getValue(enable_map_2, schedule[day]) << week_day_bits_offset[day];
+ }
+ var switch_bits_offset = { switch_1_state: 0 };
+ var switch_state_map = {0: "keep", 1: "on", 2: "off", 3: "reversal"};
+ var switch_state_values = getValues(switch_state_map);
+ var switchs = 0x00;
+ for (var switch_state in switch_bits_offset) {
+ if (switch_state_values.indexOf(schedule[switch_state]) === -1) {
+ throw new Error("schedule_settings._item." + switch_state + " must be one of " + switch_state_values.join(", "));
+ }
+ switchs |= getValue(switch_state_map, schedule[switch_state]) << switch_bits_offset[switch_state];
+ }
+ var lock_state_map = {0: "keep", 1: "lock", 2: "unlock"};
+ var lock_state_values = getValues(lock_state_map);
+ if (lock_state_values.indexOf(lock_state) === -1) {
+ throw new Error("schedule_settings._item.lock_state must be one of: " + lock_state_values.join(", "));
+ }
+ if(typeof execut_hour !== "number") {
+ throw new Error("schedule_settings._item.execut_hour must be a number");
+ }
+ if(execut_hour < 0 || execut_hour > 23) {
+ throw new Error("schedule_settings._item.execut_hour must be in range [0, 23]");
+ }
+ if(typeof execut_min !== "number") {
+ throw new Error("schedule_settings._item.execut_min must be a number");
+ }
+ if(execut_min < 0 || execut_min > 59) {
+ throw new Error("schedule_settings._item.execut_min must be in range [0, 59]");
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x64);
+ buffer.writeUInt8(channel);
+ var schedule_option = 0x00;
+ schedule_option |= getValue(enable_map, enable);
+ schedule_option |= getValue(yes_no_map, use_config) << 4;
+ buffer.writeUInt8(schedule_option);
+ buffer.writeUInt8(days);
+ buffer.writeUInt8(execut_hour);
+ buffer.writeUInt8(execut_min);
+ buffer.writeUInt8(switchs);
+ buffer.writeUInt8(getValue(lock_state_map, lock_state));
+ return buffer.toBytes();
+}
+
+/**
+ * get local rule
+ * @param {object} get_local_rule
+ * @param {number | string} get_local_rule.task_id range: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"]
+ * @example { "get_local_rule": {"task_id": 1} }
+ */
+function setGetLocalRule(get_local_rule) {
+ var task_id = get_local_rule.task_id;
+ var task_id_values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"];
+
+ if(task_id_values.indexOf(task_id) === -1) {
+ throw new Error("get_local_rule.task_id must be one of: " + task_id_values.join(", "));
+ }
+
+ var data = (task_id === "all schedules") ? 0xff : (task_id & 0xff);
+ return [0xf9, 0x65, data];
+}
+
+/**
+ * Anti-flash mode
+ * @param {object} anti_flash_mode
+ * @param {number} anti_flash_mode.enable values: (0: "disable", 1: "enable")
+ * @example { "anti_flash_mode": {"enable": 1} }
+ */
+function setAntiFlashMode(anti_flash_mode) {
+ var enable = anti_flash_mode.enable;
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xaa);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent alarm configuration
+ * @param {object} overcurrent_alarm_config
+ * @param {number} overcurrent_alarm_config.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_alarm_config.threshold range: [1, 10]
+ * @example { "overcurrent_alarm_config": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentAlarmConfig(overcurrent_alarm_config) {
+ var enable = overcurrent_alarm_config.enable;
+ var threshold = overcurrent_alarm_config.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_alarm_config.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_alarm_config.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x24);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent protection
+ * @param {object} overcurrent_protection
+ * @param {number} overcurrent_protection.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_protection.threshold range: [1, 10]
+ * @example { "overcurrent_protection": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentProtection(overcurrent_protection) {
+ var enable = overcurrent_protection.enable;
+ var threshold = overcurrent_protection.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("overcurrent_protection.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_protection.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_protection.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x30);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * highcurrent configuration
+ * @param {number} highcurrent_config values: (0: "disable", 1: "enable")
+ * @example { "highcurrent_config": 1 }
+ */
+function setHighcurrentConfig(highcurrent_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(highcurrent_config) === -1) {
+ throw new Error("highcurrent_config must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x8d);
+ buffer.writeUInt8(getValue(enable_map, highcurrent_config));
+ return buffer.toBytes();
+}
+
+/**
+ * power switch mode
+ * @param {number} power_switch_mode values: (0: "off", 1: "on", 2: "keep")
+ * @example { "power_switch_mode": 1 }
+ */
+function setPowerSwitchMode(power_switch_mode) {
+ var mode_map = {0: "off", 1: "on", 2: "keep"};
+ var mode_values = getValues(mode_map);
+ if (mode_values.indexOf(power_switch_mode) === -1) {
+ throw new Error("power_switch_mode must be one of " + mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x67);
+ buffer.writeUInt8(getValue(mode_map, power_switch_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * class mode
+ * @param {object} lorawan_class_cfg
+ * @param {number} lorawan_class_cfg.timestamp
+ * @param {number} lorawan_class_cfg.continue
+ * @param {number} lorawan_class_cfg.class_mode values: ("CLASS_B", 2: "CLASS_C")
+ * @example { "lorawan_class_cfg": {"timestamp": 1758768956, "continue": 600, "class_mode": 1} }
+ */
+function setLorawanClassCfg(lorawan_class_cfg) {
+ var class_mode = lorawan_class_cfg.class_mode;
+ var class_map = {1: "CLASS_B", 2: "CLASS_C"};
+ var class_values = getValues(class_map);
+ if (class_values.indexOf(class_mode) === -1) {
+ throw new Error("lorawan_class_cfg.class_mode must be one of " + class_values.join(", "));
+ }
+
+ var buffer = new Buffer(10);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xa4);
+ buffer.writeUInt32LE(lorawan_class_cfg.timestamp);
+ buffer.writeUInt16LE(lorawan_class_cfg.continue);
+ buffer.writeUInt8(getValue(class_map, class_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * Time Synchronize
+ * @param {number} time_synchronize values: (0: "no", 1: "yes")
+ * @example { "time_synchronize": 0 }
+ */
+function setTimeSynchronize(time_synchronize) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(time_synchronize) === -1) {
+ throw new Error("time_synchronize must be one of: " + yes_no_values.join(", "));
+ }
+ if (getValue(yes_no_map, time_synchronize) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x4a, 0x00];
+}
+
+/**
+ * D2D Settings
+ * @param {object, object} d2d_global_enable
+ * @param {number} d2d_global_enable.master_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.master_enable_change values: (0: "no", 1: "yes")
+ * @param {number} d2d_global_enable.agent_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.agent_enable_change values: (0: "no", 1: "yes")
+ * @example { "d2d_global_enable": {"master_enable": 0, "master_enable_change": 0, "agent_enable": 0, "agent_enable_change": 0} }
+ */
+function setD2DGlobalEnable(d2d_global_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ var master_enable = d2d_global_enable.master_enable;
+ var agent_enable = d2d_global_enable.agent_enable;
+ var master_enable_change = d2d_global_enable.master_enable_change;
+ var agent_enable_change = d2d_global_enable.agent_enable_change;
+
+ if (enable_values.indexOf(master_enable) === -1) {
+ throw new Error("d2d_global_enable.master_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(agent_enable) === -1) {
+ throw new Error("d2d_global_enable.agent_enable must be one of " + enable_values.join(", "));
+ }
+ if (yes_no_values.indexOf(master_enable_change) === -1) {
+ throw new Error("d2d_global_enable.master_enable_change must be one of " + yes_no_values.join(", "));
+ }
+ if (yes_no_values.indexOf(agent_enable_change) === -1) {
+ throw new Error("d2d_global_enable.agent_enable_change must be one of " + yes_no_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xc7);
+ var data = 0x00;
+ data |= getValue(enable_map, master_enable);
+ data |= getValue(yes_no_map, master_enable_change) << 4;
+ data |= getValue(enable_map, agent_enable) << 1;
+ data |= getValue(yes_no_map, agent_enable_change) << 5;
+
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Agent Settings
+ * @param {object} d2d_agent_settings_array
+ * @param {number} d2d_agent_settings_array.d2d_agent_id range: [0, 15]
+ * @param {number} d2d_agent_settings_array.d2d_agent_enable values: (0: "disable", 1: "enable")
+ * @param {string} d2d_agent_settings_array.d2d_agent_command
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_object values: (1: "switch1")
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_status values: (0: "off", 1: "on", 2: "reversel")
+ * @example { "d2d_agent_settings_array": [{"d2d_agent_id": 0, "d2d_agent_enable": 1, "d2d_agent_command": "0000", "d2d_agent_action": {"switch_object": 1, "switch_status": 1}}] }
+ */
+function setD2DAgentSettings(d2d_agent_settings) {
+ var d2d_agent_id = d2d_agent_settings.d2d_agent_id;
+ var d2d_agent_enable = d2d_agent_settings.d2d_agent_enable;
+ var d2d_agent_command = d2d_agent_settings.d2d_agent_command;
+ var switch_object = d2d_agent_settings.d2d_agent_action.switch_object;
+ var switch_status = d2d_agent_settings.d2d_agent_action.switch_status;
+
+ if (typeof d2d_agent_id !== "number") {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be a number");
+ }
+ if (d2d_agent_id < 0 || d2d_agent_id > 15) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be in range [0, 15]");
+ }
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(d2d_agent_enable) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_enable must be one of " + enable_values.join(", "));
+ }
+ var switch_map = {1: "switch1"};
+ var switch_values = getValues(switch_map);
+ if (switch_values.indexOf(switch_object) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_object must be one of " + switch_values.join(", "));
+ }
+ var switch_status_map = {0: "off", 1: "on", 2: "reversel"};
+ var switch_status_values = getValues(switch_status_map);
+ if (switch_status_values.indexOf(switch_status) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_status must be one of " + switch_status_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x83);
+ buffer.writeUInt8(d2d_agent_id);
+ buffer.writeUInt8(getValue(enable_map, d2d_agent_enable));
+ buffer.writeD2DCommand(d2d_agent_command, "0000");
+ var data = 0x00;
+ data |= getValue(switch_status_map, switch_status);
+ data |= getValue(switch_map, switch_object) << 4;
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Controller Settings
+ * @param {object} key1_d2d_controller_settings
+ * @param {number} key1_d2d_controller_settings.key_contrl_enable values: (0: "disable", 1: "enable")
+ * @param {number} key1_d2d_controller_settings.uplink.lora_enable values: (0: "disable", 1: "enable")
+ * @param {number} key1_d2d_controller_settings.uplink.key_enable values: (0: "disable", 1: "enable")
+ * @param {string} key1_d2d_controller_settings.contrl_cmd
+ * @example { "key1_d2d_controller_settings": [{"key_contrl_enable": 1, "uplink": { "lora_enable": 1, "key_enable": 1 }, "contrl_cmd": "0000"}] }
+ */
+function setD2DControllerSettings(key1_d2d_controller_settings) {
+ var enable = key1_d2d_controller_settings.key_contrl_enable;
+ var lora_enable = key1_d2d_controller_settings.uplink.lora_enable;
+ var key_enable = key1_d2d_controller_settings.uplink.key_enable;
+ var contrl_cmd = key1_d2d_controller_settings.contrl_cmd;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("key1_d2d_controller_settings.key_contrl_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(lora_enable) === -1) {
+ throw new Error("key1_d2d_controller_settings.uplink.lora_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(key_enable) === -1) {
+ throw new Error("key1_d2d_controller_settings.uplink.key_enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xb8);
+ buffer.writeUInt8(0x00);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ var data = 0x00;
+ data |= getValue(enable_map, lora_enable);
+ data |= getValue(enable_map, key_enable) << 1;
+ buffer.writeUInt8(data);
+ buffer.writeD2DCommand(contrl_cmd);
+ return buffer.toBytes();
+}
+
+/**
+ * set time zone
+ * @param {number} time_zone unit: minute, convert: "hh:mm" -> "hh * 60 + mm", values: ( -720: UTC-12, -660: UTC-11, -600: UTC-10, -570: UTC-9:30, -540: UTC-9, -480: UTC-8, -420: UTC-7, -360: UTC-6, -300: UTC-5, -240: UTC-4, -210: UTC-3:30, -180: UTC-3, -120: UTC-2, -60: UTC-1, 0: UTC, 60: UTC+1, 120: UTC+2, 180: UTC+3, 240: UTC+4, 300: UTC+5, 360: UTC+6, 420: UTC+7, 480: UTC+8, 540: UTC+9, 570: UTC+9:30, 600: UTC+10, 660: UTC+11, 720: UTC+12, 765: UTC+12:45, 780: UTC+13, 840: UTC+14 )
+ * @example { "time_zone": 480 }
+ * @example { "time_zone": -240 }
+ */
+function setTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ var timezone_values = getValues(timezone_map);
+ if (timezone_values.indexOf(time_zone) === -1) {
+ throw new Error("time_zone must be one of " + timezone_values.join(", "));
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xbd);
+ buffer.writeInt16LE(getValue(timezone_map, time_zone));
+ return buffer.toBytes();
+}
+
+/**
+ * set daylight saving time
+ * @since v2.0
+ * @param {object} daylight_saving_time
+ * @param {number} daylight_saving_time.daylight_saving_time_enable values: (0: disable, 1: enable)
+ * @param {number} daylight_saving_time.daylight_saving_time_offset, unit: minute range: [1, 120]
+ * @param {number} daylight_saving_time.start_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.start_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.start_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.start_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @param {number} daylight_saving_time.end_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.end_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.end_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.end_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @example { "daylight_saving_time": { "daylight_saving_time_enable": 1, "daylight_saving_time_offset": 60, "start_month": 3, "start_week_num": 2, "start_week_day": 7, "start_hour_min": 120, "end_month": 1, "end_week_num": 4, "end_week_day": 1, "end_hour_min": 180 } } output: FFBA013C032778000141B400
+ */
+function setDaylightSavingTime(daylight_saving_time) {
+ var enable = daylight_saving_time.daylight_saving_time_enable;
+ var offset = daylight_saving_time.daylight_saving_time_offset;
+ var start_month = daylight_saving_time.start_month;
+ var start_week_num = daylight_saving_time.start_week_num;
+ var start_week_day = daylight_saving_time.start_week_day;
+ var start_hour_min = daylight_saving_time.start_hour_min;
+ var end_month = daylight_saving_time.end_month;
+ var end_week_num = daylight_saving_time.end_week_num;
+ var end_week_day = daylight_saving_time.end_week_day;
+ var end_hour_min = daylight_saving_time.end_hour_min;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("daylight_saving_time.daylight_saving_time_enable must be one of " + enable_values.join(", "));
+ }
+ if (typeof offset !== "number") {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be a number");
+ }
+ if (offset < 1 || offset > 120) {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be in range [1, 120]");
+ }
+
+ var week_values = [1, 2, 3, 4, 5, 6, 7];
+ var month_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+ var enable_value = getValue(enable_map, enable);
+ if (enable_value && month_values.indexOf(start_month) === -1) {
+ throw new Error("daylight_saving_time.start_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && month_values.indexOf(end_month) === -1) {
+ throw new Error("daylight_saving_time.end_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(start_week_day) === -1) {
+ throw new Error("daylight_saving_time.start_week_day must be one of " + week_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(end_week_day) === -1) {
+ throw new Error("daylight_saving_time.end_week_day must be one of " + week_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 7;
+ data |= offset;
+
+ var buffer = new Buffer(11);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x72);
+ buffer.writeUInt8(data);
+ buffer.writeUInt8(start_month);
+ buffer.writeUInt8((start_week_num << 4) | start_week_day);
+ buffer.writeUInt16LE(start_hour_min);
+ buffer.writeUInt8(end_month);
+ buffer.writeUInt8((end_week_num << 4) | end_week_day);
+ buffer.writeUInt16LE(end_hour_min);
+ return buffer.toBytes();
+}
+
+function getValues(map) {
+ var values = [];
+ for (var key in map) {
+ values.push(RAW_VALUE ? parseInt(key) : map[key]);
+ }
+ return values;
+}
+
+function getValue(map, value) {
+ if (RAW_VALUE) return value;
+
+ for (var key in map) {
+ if (map[key] === value) {
+ return parseInt(key);
+ }
+ }
+
+ throw new Error("not match in " + JSON.stringify(map));
+}
+
+function Buffer(size) {
+ this.buffer = new Array(size);
+ this.offset = 0;
+
+ for (var i = 0; i < size; i++) {
+ this.buffer[i] = 0;
+ }
+}
+
+Buffer.prototype._write = function (value, byteLength, isLittleEndian) {
+ var offset = 0;
+ for (var index = 0; index < byteLength; index++) {
+ offset = isLittleEndian ? index << 3 : (byteLength - 1 - index) << 3;
+ this.buffer[this.offset + index] = (value >> offset) & 0xff;
+ }
+};
+
+Buffer.prototype.writeUInt8 = function (value) {
+ this._write(value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeInt8 = function (value) {
+ this._write(value < 0 ? value + 0x100 : value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeUInt16LE = function (value) {
+ this._write(value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeInt16LE = function (value) {
+ this._write(value < 0 ? value + 0x10000 : value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeUInt32LE = function (value) {
+ this._write(value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeInt32LE = function (value) {
+ this._write(value < 0 ? value + 0x100000000 : value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeD2DCommand = function (value, defaultValue) {
+ if (typeof value !== "string") {
+ value = defaultValue;
+ }
+ if (value.length !== 4) {
+ throw new Error("d2d_agent_command length must be 4");
+ }
+ this.buffer[this.offset] = parseInt(value.substr(2, 2), 16);
+ this.buffer[this.offset + 1] = parseInt(value.substr(0, 2), 16);
+ this.offset += 2;
+};
+
+Buffer.prototype.toBytes = function () {
+ return this.buffer;
+};
diff --git a/ws-series/ws501-v4/ws501-v4.png b/ws-series/ws501-v4/ws501-v4.png
new file mode 100644
index 00000000..cd25652e
Binary files /dev/null and b/ws-series/ws501-v4/ws501-v4.png differ
diff --git a/ws-series/ws502-v4/README.md b/ws-series/ws502-v4/README.md
new file mode 100644
index 00000000..e1c58290
--- /dev/null
+++ b/ws-series/ws502-v4/README.md
@@ -0,0 +1,245 @@
+# Smart Wall Switch - WS502
+
+
+
+For more detailed information, please visit [Milesight Official Website](https://www.milesight.com/iot/)
+
+## Payload
+
+```
++-------------------------------------------------------+
+| DEVICE UPLINK / DOWNLINK PAYLOAD |
++---------------------------+---------------------------+
+| DATA 1 | DATA 2 |
++--------+--------+---------+--------+--------+---------+
+| ID | TYPE | DATA | ID | TYPE | DATA |
++--------+--------+---------+--------+--------+---------+
+| 1 Byte | 1 Byte | N Bytes | 1 Byte | 1 Byte | N Bytes |
+|--------+--------+---------+--------+--------+---------+
+```
+
+### Attribute
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :-----------: | :--: | :--: | :----: | ------------------------------------------------------------------------------------------------ |
+| IPSO | 0xFF | 0x01 | 1 | ipso_version(1B) |
+| Hardware | 0xFF | 0x09 | 2 | hardware_version(2B)
hardware_version, e.g. 0110 -> v1.1 |
+| Firmware | 0xFF | 0x0A | 2 | firmware_version(2B)
firmware_version, e.g. 0110 -> v1.10 |
+| TSL | 0xFF | 0xFF | 2 | tsl_version(2B) |
+| Serial Number | 0xFF | 0x16 | 8 | sn(8B) |
+| LoRaWAN Class | 0xFF | 0x0F | 1 | lorawan_class(1B)
lorawan_class, values: (1: Class B, 2: Class C) |
+| Reset Event | 0xFF | 0xFE | 1 | reset_event(1B) |
+| Device Status | 0xFF | 0x0B | 1 | device_status(1B) |
+
+### Telemetry
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :----------------------: | :--: | :--: | :----: | -------------------------------------------------------------------------------------------------------------------------------------- |
+| Reporting Interval | 0xFF | 0x03 | 2 | reporting_interval(2B) |
+| Reboot | 0xFF | 0x10 | 1 | Reboot device |
+| Button Lock Config | 0xFF | 0x25 | 2 | enable(1B)
enable, values(0: disable, 1: enable) |
+| Report Status | 0xFF | 0x28 | 1 | report_status(1B) |
+| Switch Control | 0xFF | 0x29 | 1 | status_1(0..1)+status_2(1..2)
status_1,status_2, values(0: off, 1: on) |
+| Report Attribute | 0xFF | 0x2C | 1 | report_attribute(1B) |
+| LED Mode | 0xFF | 0x2F | 1 | led_mode(1B)
led_mode, values(0: disable, 1: Enable (relay closed indicator off)) |
+| Button Reset Config | 0xFF | 0x5E | 1 | button_reset_config(1B)
button_reset_config, values(0: disable, 1: enable) |
+| Power Consumption Enable | 0xFF | 0x26 | 1 | power_consumption_enable(1B)
power_consumption_enable, values(0: disable, 1: enable) |
+| Power Consumption Clear | 0xFF | 0x27 | 1 | power_consumption_clear(1B) |
+| Overcurrent Alarm Config | 0xFF | 0x24 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| Overcurrent Protection | 0xFF | 0x30 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| High Current Config | 0xFF | 0x8D | 1 | highcurrent_config(1B)
highcurrent_config, values(0: disable, 1: enable) |
+| Power Switch Mode | 0xFF | 0x67 | 1 | power_switch_mode(1B)
power_switch_mode, values(0: off, 1: on, 2: keep) |
+| Time Synchronize | 0xFF | 0x4A | 1 | time_synchronize(1B) |
+| D2D Enable | 0xFF | 0xC7 | 1 | master_enable(0..1)+agent_enable(1..2)+master_enable_change(4..5)+agent_enable_change(5..6)
master_enable, values(0: disable, 1: enable)
agent_enable, values(0: disable, 1: enable)
master_enable_change, values(0: no, 1: yes)
agent_enable_change, values(0: no, 1: yes) |
+| D2D Agent Settings | 0xFF | 0x83 | 5 | d2d_agent_id(1B)+d2d_agent_enable(1B)+d2d_agent_command(2B)+d2d_agent_action(1B)
d2d_agent_id, range[0, 15]
d2d_agent_enable, values(0: disable, 1: enable)
d2d_agent_action.switch_object, values(1: switch1, 2: switch2, 3: switch1, switch2)
d2d_agent_action.switch_status, values(0: off, 1: on, 2: reversal) |
+| Time Zone | 0xFF | 0xBD | 2 | time_zone(2B) |
+| Schedule Settings | 0xF9 | 0x64 | 7 | channel(1B), range[1, 16]
enable(0..4), values(1: enable, 2: disable), use_config(4..8), values(0, no, 1: yes)
read: bits, (bit1: monday, bit2: tuesday, bit3: wednesday, bit4: thursday, bit5: friday, bit6: saturday, bit7: sunday)
execut_hour(1B)
execut_min(1B)
switch_1_state(0..2),switch_2_state(2..4), values(0: keep, 1: on, 2: off, 3: reversal)
lock_state(1B), values(0: keep, 1: lock, 2: unlock) |
+| Get Local Rule | 0xF9 | 0x65 | 1 | task_id(1B), range[1, 16] & 255 |
+| Anti Flash Mode | 0xF9 | 0xAA | 1 | enable(0..1), values(0: disable, 1: enable) |
+| Load Power | 0xF9 | 0xAB | 6 | power_1(2B),power_2(2B), range[0, 1100] |
+| LoRaWAN Class Config | 0xF9 | 0xA4 | 8 | timestamp(4B)+continue(2B)+class_mode(1B)
class_mode, values(1: CLASS_B, 2: CLASS_C) |
+| D2D Controller Settings | 0xF9 | 0xB8 | 5 | keyid(1B)+key_contrl_enable(1B)+uplink(1B)+contrl_cmd(2B)
keyid, values(0: key1, 1: key2)
key_contrl_enable, values(0: disable, 1: enable)
uplink.lora_enable(0..1), values(0: disable, 1: enable), uplink.key_enable(1..2), values(0: disable, 1: enable) |
+| Daylight Saving Time | 0xF9 | 0x72 | 9 | daylight_saving_time_offset(0..6)+daylight_saving_time_enable(6..7)+start_month(1B)+start_week_num(0..3)+start_week_day(4..7)+start_hour_min(2B)+end_month(1B)+end_week_num(0..3)+end_week_day(4..7)+end_hour_min(2B)
daylight_saving_time_offset, range[1, 120]
daylight_saving_time_enable, values(0: disable, 1: enable)
start_month,end_month, values(1: Jan., 2: Feb., 3: Mar., 4: Apr., 5: May, 6: Jun., 7: Jul., 8: Aug. 9: Sep., 10: Oct., 11: Nov., 12: Dec.)
start_week_num,end_week_num, values(1: 1st, 2: 2nd, 3: 3rd, 4: 4th, 5: last)
start_week_day,end_week_day, values(1:Mon., 2:Tues., 3:Wed., 4:Thurs., 5:Fri., 6:Sat., 7:Sun.)
start_hour_min,end_hour_min, range[0, 1440] |
+
+### Status Definition
+
+| bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+| :--: | :-: | :-------------: | :-------------: | :-------------: | :-: | :------: | :------: | :------: |
+| | - | switch_3_change | switch_2_change | switch_1_change | - | switch_3 | switch_2 | switch_1 |
+
+## Example
+
+```json
+// Reporting Interval (FF033C00)
+{
+ "reporting_interval": 60
+}
+
+// Reboot (FF10FF)
+{
+ "reboot": "yes"
+}
+
+// Button Lock Config (FF250080)
+{
+ "button_lock_config": {
+ "enable": "enable"
+ }
+}
+
+// Report Status (FF28FF)
+{
+ "report_status": "yes"
+}
+
+// Switch Control (FF2932)
+{
+ "switch_control": {
+ "status_1": "off",
+ "status_2": "on"
+ }
+}
+
+// Report Attribute (FF2CFF)
+{
+ "report_attribute": "yes"
+}
+
+// LED Mode (FF2F00)
+{
+ "led_mode":"disable"
+}
+
+// Button Reset Config (FF5E01)
+{
+ "button_reset_config": "enable"
+}
+
+// Power Consumption Enable (FF2601)
+{
+ "power_consumption_enable": "enable"
+}
+
+// Power Consumption Clear (FF2701)
+{
+ "power_consumption_clear": "yes"
+}
+
+// Overcurrent Alarm (FF24010A)
+{
+ "overcurrent_alarm_config": {
+ "enable": "enable",
+ "threshold": 10
+ }
+}
+
+// Overcurrent Protection (FF300101)
+{
+ "overcurrent_protection": {
+ "enable": "enable",
+ "threshold": 1
+ }
+}
+
+// High Current Config (FF8D01)
+{
+ "highcurrent_config": "enable"
+}
+
+// Schedule Settings (F96401117F01010502)
+{
+ "schedule_settings": [
+ {
+ "channel": 1,
+ "enable": "enable",
+ "execut_hour": 1,
+ "execut_min": 1,
+ "friday": "enable",
+ "lock_state": "unlock",
+ "monday": "enable",
+ "saturday": "enable",
+ "sunday": "enable",
+ "switch_1_state": "on",
+ "switch_2_state": "on",
+ "thursday": "enable",
+ "tuesday": "enable",
+ "use_config": "yes",
+ "wednesday": "enable"
+ }
+ ]
+}
+
+// D2D Agent Settings (FF830101DDCC30)
+{
+ "d2d_agent_settings_array": [
+ {
+ "d2d_agent_action": {
+ "switch_object": "switch1, switch2",
+ "switch_status": "off"
+ },
+ "d2d_agent_command": "ccdd",
+ "d2d_agent_enable": "enable",
+ "d2d_agent_id": 1
+ }
+ ]
+}
+
+// Get Local Rule (F96501)
+{
+ "get_local_rule": {
+ "task_id": 1
+ }
+}
+
+// Anti Flash Mode (F9AA01)
+{
+ "anti_flash_mode": {
+ "enable": "enable"
+ }
+}
+
+// Load Power (F9AB580158010000)
+{
+ "load_power": {
+ "power_1": 344,
+ "power_2": 344
+ }
+}
+
+// LoRaWAN Class Config(F9A40000000000000100)
+{
+ "lorawan_class_cfg": {
+ "timestamp": 0,
+ "continue": 0,
+ "class_mode": "CLASS_B"
+ }
+}
+
+// D2D Controller Settings (F9B80101023412)
+{
+ "key2_d2d_controller_settings": {
+ "key_contrl_enable": "enable",
+ "uplink":{
+ "lora_enable":"enable",
+ "key_enable":"enable"
+ },
+ "contrl_cmd": "1234"
+ }
+}
+
+// Daylight Saving Time (F9726F011126020111FF00)
+{
+ "daylight_saving_time": {
+ "daylight_saving_time_enable": "disable",
+ "end_month": 1,
+ "end_hour_min": 255,
+ "end_week_day": 1,
+ "end_week_num": 1,
+ "daylight_saving_time_offset": 111,
+ "start_month": 1,
+ "start_hour_min": 550,
+ "start_week_day": 1,
+ "start_week_num": 1
+ }
+}
+```
diff --git a/ws-series/ws502-v4/ws502-v4-codec.json b/ws-series/ws502-v4/ws502-v4-codec.json
new file mode 100644
index 00000000..3f44688e
--- /dev/null
+++ b/ws-series/ws502-v4/ws502-v4-codec.json
@@ -0,0 +1,1348 @@
+{
+ "version": "1.0.0",
+ "object": [
+ {
+ "id": "voltage",
+ "name": "Voltage",
+ "value": "",
+ "unit": "V",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "FLOAT",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 5,
+ "bacnet_unit_type": "UNITS_VOLTS"
+ },
+ {
+ "id": "electric_power",
+ "name": "Electric Power",
+ "value": "",
+ "unit": "W",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 47,
+ "bacnet_unit_type": "UNITS_WATTS"
+ },
+ {
+ "id": "power_factor",
+ "name": "Power Factor",
+ "value": "",
+ "unit": "%",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 0,
+ 100
+ ],
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 98,
+ "bacnet_unit_type": "UNITS_PERCENT"
+ },
+ {
+ "id": "power_consumption",
+ "name": "Power Consumption",
+ "value": "",
+ "unit": "Wh",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 18,
+ "bacnet_unit_type": "UNITS_WATT_HOURS"
+ },
+ {
+ "id": "current",
+ "name": "Current Rating",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES"
+ },
+ {
+ "id": "switch_reading.status_1",
+ "name": "Switch 1",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_reading.status_2"
+ ]
+ },
+ {
+ "id": "switch_reading.status_2",
+ "name": "Switch 2",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_reading.status_1"
+ ]
+ },
+ {
+ "id": "reporting_interval",
+ "name": "Reporting Interval",
+ "value": "1200",
+ "unit": "s",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 60,
+ 64800
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 73,
+ "bacnet_unit_type": "UNITS_SECONDS"
+ },
+ {
+ "id": "power_consumption_enable",
+ "name": "Power Consumption Enable",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "led_mode",
+ "name": "LED Indicator",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "Enable (relay closed indicator off)",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_reset_config",
+ "name": "Reset Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_lock_config.enable",
+ "name": "Switch Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "overcurrent_alarm_config.enable",
+ "name": "Overcurrent Alarm Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm_config.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm_config.threshold",
+ "name": "Overcurrent Alarm Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_alarm_config.enable"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.enable",
+ "name": "Overcurrent Protection Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_protection.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.threshold",
+ "name": "Overcurrent Protection Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_protection.enable"
+ ]
+ },
+ {
+ "id": "highcurrent_config",
+ "name": "Highcurrent Protection",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "switch_control.status_1",
+ "name": "Switch 1",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_control.status_2"
+ ]
+ },
+ {
+ "id": "switch_control.status_2",
+ "name": "Switch 2",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_control.status_1"
+ ]
+ },
+ {
+ "id": "power_switch_mode",
+ "name": "Power Switch Mode",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ },
+ {
+ "name": "keep",
+ "value": 2
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "d2d_global_enable.master_enable",
+ "name": "D2D Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.agent_enable"
+ ]
+ },
+ {
+ "id": "d2d_global_enable.agent_enable",
+ "name": "D2D Agent Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.master_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink",
+ "key1_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.key_contrl_enable",
+ "key1_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink",
+ "key2_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.key_contrl_enable",
+ "key2_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "time_zone",
+ "name": "Time Zone",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "INT16",
+ "values": [
+ {
+ "name": "UTC-12(IDLW)",
+ "value": -720
+ },
+ {
+ "name": "UTC-11(SST)",
+ "value": -660
+ },
+ {
+ "name": "UTC-10(HST)",
+ "value": -600
+ },
+ {
+ "name": "UTC-9:30(MIT)",
+ "value": -570
+ },
+ {
+ "name": "UTC-9(AKST)",
+ "value": -540
+ },
+ {
+ "name": "UTC-8(PST)",
+ "value": -480
+ },
+ {
+ "name": "UTC-7(MST)",
+ "value": -420
+ },
+ {
+ "name": "UTC-6(CST)",
+ "value": -360
+ },
+ {
+ "name": "UTC-5(EST)",
+ "value": -300
+ },
+ {
+ "name": "UTC-4(AST)",
+ "value": -240
+ },
+ {
+ "name": "UTC-3:30(NST)",
+ "value": -210
+ },
+ {
+ "name": "UTC-3(BRT)",
+ "value": -180
+ },
+ {
+ "name": "UTC-2(FNT)",
+ "value": -120
+ },
+ {
+ "name": "UTC-1(CVT)",
+ "value": -60
+ },
+ {
+ "name": "UTC(WET)",
+ "value": 0
+ },
+ {
+ "name": "UTC+1(CET)",
+ "value": 60
+ },
+ {
+ "name": "UTC+2(EET)",
+ "value": 120
+ },
+ {
+ "name": "UTC+3(MSK)",
+ "value": 180
+ },
+ {
+ "name": "UTC+3:30(IRST)",
+ "value": 210
+ },
+ {
+ "name": "UTC+4(GST)",
+ "value": 240
+ },
+ {
+ "name": "UTC+4:30(AFT)",
+ "value": 270
+ },
+ {
+ "name": "UTC+5(PKT)",
+ "value": 300
+ },
+ {
+ "name": "UTC+5:30(IST)",
+ "value": 330
+ },
+ {
+ "name": "UTC+5:45(NPT)",
+ "value": 345
+ },
+ {
+ "name": "UTC+6(BHT)",
+ "value": 360
+ },
+ {
+ "name": "UTC+6:30(MMT)",
+ "value": 390
+ },
+ {
+ "name": "UTC+7(ICT)",
+ "value": 420
+ },
+ {
+ "name": "UTC+8(CT/CST)",
+ "value": 480
+ },
+ {
+ "name": "UTC+9(JST)",
+ "value": 540
+ },
+ {
+ "name": "UTC+9:30(ACST)",
+ "value": 570
+ },
+ {
+ "name": "UTC+10(AEST)",
+ "value": 600
+ },
+ {
+ "name": "UTC+10:30(LHST)",
+ "value": 630
+ },
+ {
+ "name": "UTC+11(VUT)",
+ "value": 660
+ },
+ {
+ "name": "UTC+12(NZST)",
+ "value": 720
+ },
+ {
+ "name": "UTC+12:45(CHAST)",
+ "value": 765
+ },
+ {
+ "name": "UTC+13(PHOT)",
+ "value": 780
+ },
+ {
+ "name": "UTC+14(LINT)",
+ "value": 840
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES"
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_enable",
+ "name": "DST Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_offset",
+ "name": "DST Bias",
+ "value": "1",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 120
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.current",
+ "name": "Current",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES",
+ "reference": [
+ "overcurrent_alarm.status"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overcurrent",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm.current"
+ ]
+ },
+ {
+ "id": "device_abnormal_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "abnormal",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "temperature_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overtemperature",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ws-series/ws502-v4/ws502-v4-decoder.js b/ws-series/ws502-v4/ws502-v4-decoder.js
new file mode 100644
index 00000000..50e50635
--- /dev/null
+++ b/ws-series/ws502-v4/ws502-v4-decoder.js
@@ -0,0 +1,762 @@
+/**
+ * Payload Decoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS502
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function decodeUplink(input) {
+ var decoded = milesightDeviceDecode(input.bytes);
+ return { data: decoded };
+}
+
+// Chirpstack v3
+function Decode(fPort, bytes) {
+ return milesightDeviceDecode(bytes);
+}
+
+// The Things Network
+function Decoder(bytes, port) {
+ return milesightDeviceDecode(bytes);
+}
+/* eslint-enable */
+
+function milesightDeviceDecode(bytes) {
+ var decoded = {};
+
+ for (var i = 0; i < bytes.length;) {
+ var channel_id = bytes[i++];
+ var channel_type = bytes[i++];
+
+ // IPSO VERSION
+ if (channel_id === 0xff && channel_type === 0x01) {
+ decoded.ipso_version = readProtocolVersion(bytes[i]);
+ i += 1;
+ }
+ // HARDWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x09) {
+ decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // FIRMWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x0a) {
+ decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // TSL VERSION
+ else if (channel_id === 0xff && channel_type === 0xff) {
+ decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // SERIAL NUMBER
+ else if (channel_id === 0xff && channel_type === 0x16) {
+ decoded.sn = readSerialNumber(bytes.slice(i, i + 8));
+ i += 8;
+ }
+ // LORAWAN CLASS TYPE
+ else if (channel_id === 0xff && channel_type === 0x0f) {
+ decoded.lorawan_class = readLoRaWANClass(bytes[i]);
+ i += 1;
+ }
+ // RESET EVENT
+ else if (channel_id === 0xff && channel_type === 0xfe) {
+ decoded.reset_event = readResetEvent(1);
+ i += 1;
+ }
+ // DEVICE STATUS
+ else if (channel_id === 0xff && channel_type === 0x0b) {
+ decoded.device_status = readDeviceStatus(1);
+ i += 1;
+ }
+ // SWITCH STATE
+ else if (channel_id === 0x08 && channel_type === 0x29) {
+ // payload (0 0 0 0 0 0 0 0)
+ // Switch 3 2 1 3 2 1
+ // ------- -------
+ // bit mask change state
+ var value = bytes[i];
+ var switch_bit_offset = { status_1: 0, status_2: 1 };
+ decoded.switch_reading = {};
+ for (var key in switch_bit_offset) {
+ decoded.switch_reading[key] = readOnOffStatus((value >>> switch_bit_offset[key]) & 0x01);
+ decoded.switch_reading[key + '_change'] = readYesNoStatus((value >>> (switch_bit_offset[key] + 4)) & 0x01);
+ }
+ i += 1;
+ }
+ // Voltage
+ else if (channel_id === 0x03 && channel_type === 0x74) {
+ decoded.voltage = readUInt16LE(bytes.slice(i, i + 2)) / 10;
+ i += 2;
+ }
+ // Electric Power
+ else if (channel_id === 0x04 && channel_type === 0x80) {
+ decoded.electric_power = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Power Factor
+ else if (channel_id === 0x05 && channel_type === 0x81) {
+ decoded.power_factor = readUInt8(bytes.slice(i, i + 1));
+ i += 1;
+ }
+ // Power Consumption
+ else if (channel_id === 0x06 && channel_type === 0x83) {
+ decoded.power_consumption = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Current Rating
+ else if (channel_id === 0x07 && channel_type === 0xc9) {
+ decoded.current = readUInt16LE(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // Over Current Alarm
+ else if (channel_id === 0x87 && channel_type === 0xc9) {
+ decoded.overcurrent_alarm = {};
+ decoded.overcurrent_alarm.current = readUInt16LE(bytes.slice(i, i + 2));
+ decoded.overcurrent_alarm.status = readOverCurrentStatus(bytes.slice(i + 2, i + 3))
+ i += 3;
+ }
+ // Device Abnormal Alarm
+ else if (channel_id === 0x88 && channel_type === 0x29) {
+ decoded.device_abnormal_alarm = {};
+ decoded.device_abnormal_alarm.status = readAlarmStatus(bytes[i]);
+ i += 1;
+ }
+ // Temperature Alarm
+ else if (channel_id === 0x89 && channel_type === 0xdf) {
+ decoded.temperature_alarm = {};
+ decoded.temperature_alarm.status = readTemperatureAlarmStatus(bytes.slice(i, i + 1));
+ i += 1;
+ }
+ // RULE CONFIG
+ else if (channel_id === 0xf9 && channel_type === 0x64) {
+ var schedule_settings = readScheduleSettings(bytes.slice(i, i + 7));
+ i += 7;
+
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ }
+ // DOWNLINK RESPONSE
+ else if (channel_id === 0xfe || channel_id === 0xff) {
+ var result = handle_downlink_response(channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else if (channel_id === 0xf8 || channel_id === 0xf9) {
+ var result = handle_downlink_response_ext(channel_id, channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else {
+ break;
+ }
+ }
+
+ return decoded;
+}
+
+function handle_downlink_response(channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x03:
+ decoded.reporting_interval = readUInt16LE(bytes.slice(offset, offset + 2));
+ offset += 2;
+ break;
+ case 0x10:
+ decoded.reboot = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x25:
+ var data = readUInt16LE(bytes.slice(offset, offset + 2));
+ decoded.button_lock_config = {};
+ decoded.button_lock_config.enable = readEnableStatus((data >>> 15) & 0x01);
+ offset += 2;
+ break;
+ case 0x28:
+ decoded.report_status = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x29:
+ var data = readUInt8(bytes[offset]);
+ var switch_bit_offset = { status_1: 0, status_2: 1 };
+ decoded.switch_control = {};
+ for (var key in switch_bit_offset) {
+ if ((data >>> (switch_bit_offset[key] + 4)) & 0x01) {
+ decoded.switch_control[key] = readOnOffStatus(data >>> switch_bit_offset[key] & 0x01);
+ }
+ }
+ offset += 1;
+ break;
+ case 0x2c:
+ decoded.report_attribute = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x2f:
+ decoded.led_mode = readLedMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x5e:
+ decoded.button_reset_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x26:
+ decoded.power_consumption_enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x27:
+ decoded.power_consumption_clear = readYesNoStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x24:
+ decoded.overcurrent_alarm_config = {};
+ decoded.overcurrent_alarm_config.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_alarm_config.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x30:
+ decoded.overcurrent_protection = {};
+ decoded.overcurrent_protection.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_protection.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x8d:
+ decoded.highcurrent_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x67:
+ decoded.power_switch_mode = readPowerSwitchMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x4a:
+ decoded.time_synchronize = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0xc7:
+ var d2d_enable_data = readUInt8(bytes[offset]);
+ offset += 1;
+
+ decoded.d2d_global_enable = {};
+ decoded.d2d_global_enable.master_enable = readEnableStatus((d2d_enable_data >>> 0) & 1);
+ decoded.d2d_global_enable.master_enable_change = readYesNoStatus((d2d_enable_data >>> 4) & 1);
+ decoded.d2d_global_enable.agent_enable = readEnableStatus((d2d_enable_data >>> 1) & 1);
+ decoded.d2d_global_enable.agent_enable_change = readYesNoStatus((d2d_enable_data >>> 5) & 1);
+ break;
+ case 0x83:
+ var d2d_agent_settings = readD2DAgentSettings(bytes.slice(offset, offset + 5));
+ offset += 5;
+ decoded.d2d_agent_settings_array = decoded.d2d_agent_settings_array || [];
+ decoded.d2d_agent_settings_array.push(d2d_agent_settings);
+ break;
+ case 0xbd:
+ decoded.time_zone = readTimeZone(readInt16LE(bytes.slice(offset, offset + 2)));
+ offset += 2;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function handle_downlink_response_ext(code, channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x64:
+ var schedule_settings_result = readUInt8(bytes[offset + 7]);
+ if (schedule_settings_result === 0) {
+ var schedule_settings = readScheduleSettings(bytes.slice(offset, offset + 7));
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ } else {
+ decoded.schedule_settings_result = readScheduleSettingsResult(schedule_settings_result);
+ }
+ offset += 7;
+ break;
+ case 0x65:
+ var data = readUInt8(bytes[offset]);
+ decoded.get_local_rule = {};
+ if (data === 0xff) {
+ decoded.get_local_rule.task_id = 'all schedules';
+ } else {
+ decoded.get_local_rule.task_id = data;
+ }
+ offset += 1;
+ break;
+ case 0xaa:
+ decoded.anti_flash_mode = {};
+ decoded.anti_flash_mode.enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0xab:
+ decoded.load_power = {};
+ var power_bit_offset = [ "power_1", "power_2" ];
+ for(var i= 0; i < power_bit_offset.length; i++) {
+ decoded.load_power[power_bit_offset[i]] = readUInt16LE(bytes.slice(offset + i * 2, offset + (i + 1) * 2));
+ }
+ offset += 6;
+ break;
+ case 0xa4:
+ decoded.lorawan_class_cfg = readLorawanClassCfg(bytes.slice(offset, offset + 8));
+ offset += 8;
+ break;
+ case 0xb8:
+ var keyid = readUInt8(bytes[offset]);
+ switch (keyid) {
+ case 0x00:
+ decoded.key1_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ break;
+ case 0x01:
+ decoded.key2_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ break;
+ default:
+ throw new Error("unknown keyid");
+ }
+ offset += 5;
+ break;
+ case 0x72:
+ decoded.daylight_saving_time = readDstConfig(bytes.slice(offset, offset + 9));
+ offset += 9;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ if (hasResultFlag(code)) {
+ var result_value = readUInt8(bytes[offset]);
+ offset += 1;
+
+ if (result_value !== 0) {
+ var request = decoded;
+ decoded = {};
+ decoded.device_response_result = {};
+ decoded.device_response_result.channel_type = channel_type;
+ decoded.device_response_result.result = readResultStatus(result_value);
+ decoded.device_response_result.request = request;
+ }
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function readDstConfig(bytes) {
+ var offset = 0;
+
+ var data = bytes[offset];
+ var enable_value = (data >> 7) & 0x01;
+ var offset_value = data & 0x7f;
+
+ var daylight_saving_time = {};
+ daylight_saving_time.enable = readEnableStatus(enable_value);
+ daylight_saving_time.offset = offset_value;
+ daylight_saving_time.start_month = readUInt8(bytes[offset + 1]);
+ var start_week_value = readUInt8(bytes[offset + 2]);
+ daylight_saving_time.start_week_num = start_week_value >> 4;
+ daylight_saving_time.start_week_day = start_week_value & 0x0f;
+ daylight_saving_time.start_hour_min = readUInt16LE(bytes.slice(offset + 3, offset + 5));
+ daylight_saving_time.end_month = readUInt8(bytes[offset + 5]);
+ var end_week_value = readUInt8(bytes[offset + 6]);
+ daylight_saving_time.end_week_num = end_week_value >> 4;
+ daylight_saving_time.end_week_day = end_week_value & 0x0f;
+ daylight_saving_time.end_hour_min = readUInt16LE(bytes.slice(offset + 7, offset + 9));
+ offset += 9;
+
+ return daylight_saving_time;
+}
+
+function hasResultFlag(code) {
+ return code === 0xf8;
+}
+
+function readProtocolVersion(bytes) {
+ var major = (bytes & 0xf0) >> 4;
+ var minor = bytes & 0x0f;
+ return "v" + major + "." + minor;
+}
+
+function readResultStatus(status) {
+ var status_map = {
+ 0: "success",
+ 1: "forbidden",
+ 2: "out of range",
+ 16: "continue is 0",
+ 17: "The continue is greater than the maximum value allowed by the device",
+ 18: "Command expires (start time + continue <= current time)"
+ };
+ return getValue(status_map, status);
+}
+
+function readHardwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff) >> 4;
+ return "v" + major + "." + minor;
+}
+
+function readFirmwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff).toString(16);
+ return "v" + major + "." + minor;
+}
+
+function readTslVersion(bytes) {
+ var major = bytes[0] & 0xff;
+ var minor = bytes[1] & 0xff;
+ return "v" + major + "." + minor;
+}
+
+function readSerialNumber(bytes) {
+ var temp = [];
+ for (var idx = 0; idx < bytes.length; idx++) {
+ temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2));
+ }
+ return temp.join("");
+}
+
+function readLoRaWANClass(type) {
+ var class_map = {
+ 1: "Class B",
+ 2: "Class C",
+ };
+ return getValue(class_map, type);
+}
+
+function readResetEvent(status) {
+ var status_map = { 0: "normal", 1: "reset" };
+ return getValue(status_map, status);
+}
+
+function readDeviceStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readOnOffStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readYesNoStatus(status) {
+ var status_map = { 0: "no", 1: "yes" };
+ return getValue(status_map, status);
+}
+
+function readLedMode(type) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ return getValue(led_mode_map, type);
+}
+
+function readTemperatureAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "overtemperature" };
+ return getValue(status_map, status);
+}
+
+function readAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "abnormal" };
+ return getValue(status_map, status);
+}
+
+function readOverCurrentStatus(status) {
+ var status_map = { 0: "normal", 1: "overcurrent" };
+ return getValue(status_map, status);
+}
+
+function readScheduleSettings(bytes) {
+ var offset = 0;
+
+ var schedule_settings = {};
+ schedule_settings.channel = readUInt8(bytes[offset]);
+ schedule_settings.enable = readEnableStatus2(bytes[offset + 1] & 0x03);
+ schedule_settings.use_config = readYesNoStatus(bytes[offset + 1] >> 4 & 0x01);
+ // condition
+ var day_bit_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var day = readUInt8(bytes[offset + 2]);
+ for (var key in day_bit_offset) {
+ schedule_settings[key] = readEnableStatus((day >> day_bit_offset[key]) & 0x01);
+ }
+ schedule_settings.execut_hour = readUInt8(bytes[offset + 3]);
+ schedule_settings.execut_min = readUInt8(bytes[offset + 4]);
+
+ // action
+ var switch_bit_offset = { switch_1_state: 0, switch_2_state: 2 };
+ var switch_raw_data = readUInt8(bytes[offset + 5]);
+ for (var key in switch_bit_offset) {
+ schedule_settings[key] = readSwitchStatus((switch_raw_data >> switch_bit_offset[key]) & 0x03);
+ }
+ schedule_settings.lock_state = readChildLockStatus(bytes[offset + 6]);
+
+ return schedule_settings;
+}
+
+function readScheduleSettingsResult(result) {
+ var schedule_settings_result_map = {
+ 0: "success",
+ 2: "failed, out of range",
+ 17: "success, conflict with channel=1",
+ 18: "success, conflict with channel=2",
+ 19: "success, conflict with channel=3",
+ 20: "success, conflict with channel=4",
+ 21: "success, conflict with channel=5",
+ 22: "success, conflict with channel=6",
+ 23: "success, conflict with channel=7",
+ 24: "success, conflict with channel=8",
+ 25: "success, conflict with channel=9",
+ 26: "success, conflict with channel=10",
+ 27: "success, conflict with channel=11",
+ 28: "success, conflict with channel=12",
+ 29: "success, conflict with channel=13",
+ 30: "success, conflict with channel=14",
+ 31: "success, conflict with channel=15",
+ 32: "success, conflict with channel=16",
+ 49: "failed, conflict with channel=1",
+ 50: "failed, conflict with channel=2",
+ 51: "failed, conflict with channel=3",
+ 52: "failed, conflict with channel=4",
+ 53: "failed, conflict with channel=5",
+ 54: "failed, conflict with channel=6",
+ 55: "failed, conflict with channel=7",
+ 56: "failed, conflict with channel=8",
+ 57: "failed, conflict with channel=9",
+ 58: "failed, conflict with channel=10",
+ 59: "failed, conflict with channel=11",
+ 60: "failed, conflict with channel=12",
+ 61: "failed, conflict with channel=13",
+ 62: "failed, conflict with channel=14",
+ 63: "failed, conflict with channel=15",
+ 64: "failed, conflict with channel=16",
+ 81: "failed,rule config empty",
+ };
+ return getValue(schedule_settings_result_map, result);
+}
+
+function readD2DCommand(bytes) {
+ return ("0" + (bytes[1] & 0xff).toString(16)).slice(-2) + ("0" + (bytes[0] & 0xff).toString(16)).slice(-2);
+}
+
+function readSwitchObject(switchs) {
+ var switch_object_map = {
+ 1: "switch1",
+ 2: "switch2",
+ 3: "switch1, switch2"
+ };
+ return getValue(switch_object_map, switchs);
+}
+
+function readActionSwitchStatus(status) {
+ var switch_status_map = {
+ 0: "off",
+ 1: "on",
+ 2: "reversel"
+ };
+ return getValue(switch_status_map, status);
+}
+
+function readD2DAgentSettings(bytes) {
+ var offset = 0;
+
+ var d2d_agent_settings = {};
+ d2d_agent_settings.d2d_agent_id = readUInt8(bytes[offset]);
+ d2d_agent_settings.d2d_agent_enable = readEnableStatus(bytes[offset + 1] & 0x01);
+ d2d_agent_settings.d2d_agent_command = readD2DCommand(bytes.slice(offset + 2, offset + 4));
+
+ d2d_agent_settings.d2d_agent_action = {};
+ var switchs = bytes[offset + 4] >> 4;
+ var status = bytes[offset + 4] & 0x0f;
+ d2d_agent_settings.d2d_agent_action.switch_object = readSwitchObject(switchs);
+ d2d_agent_settings.d2d_agent_action.switch_status = readActionSwitchStatus(status);
+
+ return d2d_agent_settings;
+}
+
+function readLorawanClassCfg(bytes) {
+ var offset = 0;
+
+ var lorawan_class_cfg = {};
+ lorawan_class_cfg.timestamp = readUInt32LE(bytes.slice(offset, offset + 4));
+ lorawan_class_cfg.continue = readUInt16LE(bytes.slice(offset + 4, offset + 6));
+ lorawan_class_cfg.class_mode = readClassMode(readUInt8(bytes.slice(offset + 6, offset + 7)));
+
+ return lorawan_class_cfg;
+}
+
+function readD2DControllerSettings(bytes) {
+ var offset = 0;
+
+ var d2d_controller_settings = {};
+ var keyid = readUInt8(bytes[offset]);
+ if(keyid !== 0 && keyid !== 1) {
+ return d2d_controller_settings;
+ }
+ d2d_controller_settings.key_contrl_enable = readEnableStatus(bytes[offset + 1]);
+ d2d_controller_settings.uplink = {};
+ d2d_controller_settings.uplink.lora_enable = readEnableStatus(bytes[offset + 2] & 0x01);
+ d2d_controller_settings.uplink.key_enable = readEnableStatus(bytes[offset + 2] >> 1 & 0x01);
+ d2d_controller_settings.contrl_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));
+
+ return d2d_controller_settings;
+}
+
+function readSwitchStatus(status) {
+ var switch_status_map = { 0: "keep", 1: "on", 2: "off", 3: "reversal" };
+ return getValue(switch_status_map, status);
+}
+
+function readChildLockStatus(status) {
+ var child_lock_status_map = { 0: "keep", 1: "lock", 2: "unlock" };
+ return getValue(child_lock_status_map, status);
+}
+
+function readTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ return getValue(timezone_map, time_zone);
+}
+
+function readEnableStatus(status) {
+ var status_map = { 0: "disable", 1: "enable" };
+ return getValue(status_map, status);
+}
+
+function readPowerSwitchMode(mode) {
+ var mode_map = { 0: "off", 1: "on", 2: "keep" };
+ return getValue(mode_map, mode);
+}
+
+function readEnableStatus2(status) {
+ var status_map = { 1: "enable", 2: "disable" };
+ return getValue(status_map, status);
+}
+
+function readEnableMask(mask) {
+ var mask_map = { 0: "keep", 1: "set" };
+ return getValue(mask_map, mask);
+}
+
+function readClassMode(mode) {
+ var mode_map = { 1: "CLASS_B", 2: "CLASS_C" };
+ return getValue(mode_map, mode);
+}
+
+function readMonth(month) {
+ var month_map = {
+ 1:"Jan.",
+ 2:"Feb.",
+ 3: "Mar.",
+ 4: "Apr.",
+ 5: "May.",
+ 6: "Jun.",
+ 7: "Jul.",
+ 8: "Aug.",
+ 9: "Sep.",
+ 10: "Oct.",
+ 11: "Nov.",
+ 12: "Dec.",
+ };
+ return getValue(month_map, month);
+}
+
+function readWeek(week) {
+ var weeks_map = {
+ 1: "1st",
+ 2: "2nd",
+ 3: "3rd",
+ 4: "4th",
+ 5: "last"
+ };
+ return getValue(weeks_map, week);
+}
+
+function readWeekDay(day) {
+ var week_map = {
+ 1: "Mon.",
+ 2: "Tues.",
+ 3: "Wed.",
+ 4: "Thurs.",
+ 5: "Fri.",
+ 6: "Sat.",
+ 7: "Sun."
+ };
+ return getValue(week_map, day);
+}
+
+function readKeyNum(key_num) {
+ var key_map = { 0: "key1", 1: "key2" };
+ return getValue(key_map, key_num);
+}
+
+/* eslint-disable */
+function readUInt8(bytes) {
+ return bytes & 0xff;
+}
+
+function readInt8(bytes) {
+ var ref = readUInt8(bytes);
+ return ref > 0x7f ? ref - 0x100 : ref;
+}
+
+function readUInt16LE(bytes) {
+ var value = (bytes[1] << 8) + bytes[0];
+ return value & 0xffff;
+}
+
+function readInt16LE(bytes) {
+ var ref = readUInt16LE(bytes);
+ return ref > 0x7fff ? ref - 0x10000 : ref;
+}
+
+function readUInt32LE(bytes) {
+ var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
+ return (value & 0xffffffff) >>> 0;
+}
+
+function readInt32LE(bytes) {
+ var ref = readUInt32LE(bytes);
+ return ref > 0x7fffffff ? ref - 0x100000000 : ref;
+}
+
+function getValue(map, key) {
+ if (RAW_VALUE) return key;
+
+ var value = map[key];
+ if (!value) value = "unknown";
+ return value;
+}
+
+if (!Object.assign) {
+ Object.defineProperty(Object, "assign", {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function (target) {
+ "use strict";
+ if (target == null) {
+ throw new TypeError("Cannot convert first argument to object");
+ }
+
+ var to = Object(target);
+ for (var i = 1; i < arguments.length; i++) {
+ var nextSource = arguments[i];
+ if (nextSource == null) {
+ continue;
+ }
+ nextSource = Object(nextSource);
+
+ var keysArray = Object.keys(Object(nextSource));
+ for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+ var nextKey = keysArray[nextIndex];
+ var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+ if (desc !== undefined && desc.enumerable) {
+ // concat array
+ if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {
+ to[nextKey] = to[nextKey].concat(nextSource[nextKey]);
+ } else {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ },
+ });
+}
diff --git a/ws-series/ws502-v4/ws502-v4-encoder.js b/ws-series/ws502-v4/ws502-v4-encoder.js
new file mode 100644
index 00000000..b55dfd7c
--- /dev/null
+++ b/ws-series/ws502-v4/ws502-v4-encoder.js
@@ -0,0 +1,934 @@
+/**
+ * Payload Encoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS502
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function encodeDownlink(input) {
+ var encoded = milesightDeviceEncode(input.data);
+ return { bytes: encoded };
+}
+
+// Chirpstack v3
+function Encode(fPort, obj) {
+ return milesightDeviceEncode(obj);
+}
+
+// The Things Network
+function Encoder(obj, port) {
+ return milesightDeviceEncode(obj);
+}
+/* eslint-enable */
+
+function milesightDeviceEncode(payload) {
+ var encoded = [];
+
+ if ("reboot" in payload) {
+ encoded = encoded.concat(reboot(payload.reboot));
+ }
+ if ("reporting_interval" in payload) {
+ encoded = encoded.concat(setReportingInterval(payload.reporting_interval));
+ }
+ if ("report_status" in payload) {
+ encoded = encoded.concat(reportStatus(payload.report_status));
+ }
+ if ("report_attribute" in payload) {
+ encoded = encoded.concat(reportAttribute(payload.report_attribute));
+ }
+ if ("led_mode" in payload) {
+ encoded = encoded.concat(setLedMode(payload.led_mode));
+ }
+ if ("button_lock_config" in payload) {
+ encoded = encoded.concat(setButtonLockConfig(payload.button_lock_config));
+ }
+ if("switch_control" in payload) {
+ encoded = encoded.concat(setSwitchControl(payload.switch_control));
+ }
+ if("button_reset_config" in payload) {
+ encoded = encoded.concat(setButtonResetConfig(payload.button_reset_config));
+ }
+ if("power_consumption_enable" in payload) {
+ encoded = encoded.concat(setPowerConsumptionEnable(payload.power_consumption_enable));
+ }
+ if("load_power" in payload) {
+ encoded = encoded.concat(setLoadPower(payload.load_power));
+ }
+ if("power_consumption_clear" in payload) {
+ encoded = encoded.concat(setPowerConsumptionClear(payload.power_consumption_clear));
+ }
+ if("schedule_settings" in payload) {
+ for (var schedule_index = 0; schedule_index < payload.schedule_settings.length; schedule_index++) {
+ var schedule = payload.schedule_settings[schedule_index];
+ encoded = encoded.concat(setScheduleSettings(schedule));
+ }
+ }
+ if("get_local_rule" in payload) {
+ encoded = encoded.concat(setGetLocalRule(payload.get_local_rule));
+ }
+ if("anti_flash_mode" in payload) {
+ encoded = encoded.concat(setAntiFlashMode(payload.anti_flash_mode));
+ }
+ if("overcurrent_alarm_config" in payload) {
+ encoded = encoded.concat(setOvercurrentAlarmConfig(payload.overcurrent_alarm_config));
+ }
+ if("overcurrent_protection" in payload) {
+ encoded = encoded.concat(setOvercurrentProtection(payload.overcurrent_protection));
+ }
+ if("highcurrent_config" in payload) {
+ encoded = encoded.concat(setHighcurrentConfig(payload.highcurrent_config));
+ }
+ if("power_switch_mode" in payload) {
+ encoded = encoded.concat(setPowerSwitchMode(payload.power_switch_mode));
+ }
+ if("lorawan_class_cfg" in payload) {
+ encoded = encoded.concat(setLorawanClassCfg(payload.lorawan_class_cfg));
+ }
+ if("time_synchronize" in payload) {
+ encoded = encoded.concat(setTimeSynchronize(payload.time_synchronize));
+ }
+ if("d2d_global_enable" in payload) {
+ encoded = encoded.concat(setD2DGlobalEnable(payload.d2d_global_enable));
+ }
+ if("d2d_agent_settings_array" in payload) {
+ for (var agent_index = 0; agent_index < payload.d2d_agent_settings_array.length; agent_index++) {
+ var agent = payload.d2d_agent_settings_array[agent_index];
+ encoded = encoded.concat(setD2DAgentSettings(agent));
+ }
+ }
+ if("key1_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key1_d2d_controller_settings, 0x00));
+ }
+ if("key2_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key2_d2d_controller_settings, 0x01));
+ }
+ if("time_zone" in payload) {
+ encoded = encoded.concat(setTimeZone(payload.time_zone));
+ }
+ if("daylight_saving_time" in payload) {
+ encoded = encoded.concat(setDaylightSavingTime(payload.daylight_saving_time));
+ }
+
+ return encoded;
+}
+
+/**
+ * reboot
+ * @param {number} reboot values: (0: "no", 1: "yes")
+ * @example { "reboot": 1 }
+ */
+function reboot(reboot) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(reboot) === -1) {
+ throw new Error("reboot must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, reboot) === 0) {
+ return [];
+ }
+ return [0xff, 0x10, 0xff];
+}
+
+/**
+ * report interval configuration
+ * @param {number} reporting_interval uint: second, range: [60, 64800]
+ * @example { "reporting_interval": 1200 }
+ */
+function setReportingInterval(reporting_interval) {
+ if (typeof reporting_interval !== "number") {
+ throw new Error("reporting_interval must be a number");
+ }
+ if (reporting_interval < 60 || reporting_interval > 64800) {
+ throw new Error("reporting_interval must be in the range of [60, 64800]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x03);
+ buffer.writeUInt16LE(reporting_interval);
+ return buffer.toBytes();
+}
+
+/**
+ * report status
+ * @param {number} report_status values: (0: "no", 1: "yes")
+ * @example { "report_status": 1 }
+ */
+function reportStatus(report_status) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_status) === -1) {
+ throw new Error("report_status must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_status) === 0) {
+ return [];
+ }
+ return [0xff, 0x28, 0xff];
+}
+
+/**
+ * report attribute
+ * @param {number} report_attribute values: (0: "no", 1: "yes")
+ * @example { "report_attribute": 1 }
+ */
+function reportAttribute(report_attribute) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_attribute) === -1) {
+ throw new Error("report_attribute must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_attribute) === 0) {
+ return [];
+ }
+ return [0xff, 0x2c, 0xff];
+}
+
+/**
+ * set led mode
+ * @param {number} led_mode, values: (0: "disable", 1: "Enable (relay closed indicator off)")
+ * @example { "led_mode": 1 }
+ */
+function setLedMode(led_mode) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ var led_mode_values = getValues(led_mode_map);
+ if (led_mode_values.indexOf(led_mode) === -1) {
+ throw new Error("led_mode must be one of: " + led_mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x2f);
+ buffer.writeUInt8(getValue(led_mode_map, led_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * button lock configuration
+ * @param {object} button_lock_config
+ * @param {number} button_lock_config.enable values: (0: "disable", 1: "enable")
+ * @example { "button_lock_config": { "enable": 1 } }
+ */
+function setButtonLockConfig(button_lock_config) {
+ var enable = button_lock_config.enable;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("button_lock_config.enable must be one of: " + enable_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 15;
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x25);
+ buffer.writeUInt16LE(data);
+ return buffer.toBytes();
+}
+
+/**
+ * switch control
+ * @param {object} switch_control
+ * @param {number} switch_control.status_1 values: (0: "off", 1: "on")
+ * @param {number} switch_control.status_2 values: (0: "off", 1: "on")
+ * @example { "switch_control": { "status_1": 1, "status_2": 1 } }
+ */
+function setSwitchControl(switch_control) {
+ var status_map = {0: "off", 1: "on"};
+ var status_values = getValues(status_map);
+
+ var data = 0x00;
+ var switch_bit_offset = { status_1: 0, status_2: 1 };
+ for (var key in switch_bit_offset) {
+ if (key in switch_control) {
+ if (status_values.indexOf(switch_control[key]) === -1) {
+ throw new Error("switch_control." + key + " must be one of: " + status_values.join(", "));
+ }
+
+ data |= 1 << (switch_bit_offset[key] + 4);
+ data |= getValue(status_map, switch_control[key]) << switch_bit_offset[key];
+ }
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x29);
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * button reset configuration
+ * @param {number} button_reset_config values: (0: "disable", 1: "enable")
+ * @example { "button_reset_config": 1 }
+ */
+function setButtonResetConfig(button_reset_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(button_reset_config) === -1) {
+ throw new Error("button_reset_config must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x5e, getValue(enable_map, button_reset_config)];
+}
+
+/**
+ * power consumption enable
+ * @param {number} power_consumption_enable values: (0: "disable", 1: "enable")
+ * @example { "power_consumption_enable": 1 }
+ */
+function setPowerConsumptionEnable(power_consumption_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(power_consumption_enable) === -1) {
+ throw new Error("power_consumption_enable must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x26, getValue(enable_map, power_consumption_enable)];
+}
+
+/**
+ * load power
+ * @param {object} load_power
+ * @param {number} load_power.power_1
+ * @param {number} load_power.power_2
+ * @example { "load_power": {"power_1": 0, "power_2": 1100 } }
+ */
+function setLoadPower(load_power) {
+ var powers = [ "power_1", "power_2" ];
+ var buffer = new Buffer(8);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xab);
+ for(var i = 0; i < powers.length; i++) {
+ if (typeof load_power[powers[i]] !== "number") {
+ throw new Error("load_power." + powers[i] + " must be a number");
+ }
+ if (load_power[powers[i]] < 0 || load_power[powers[i]] > 1100) {
+ throw new Error("load_power." + powers[i] + " must be in the range of [0, 1100]");
+ }
+ buffer.writeUInt16LE(load_power[powers[i]]);
+ }
+
+ return buffer.toBytes();
+}
+
+/**
+ * power consumption clear
+ * @param {number} power_consumption_clear values: (0: "no", 1: "yes")
+ * @example { "power_consumption_clear": 1 }
+ */
+function setPowerConsumptionClear(power_consumption_clear) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(power_consumption_clear) === -1) {
+ throw new Error("power_consumption_clear must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, power_consumption_clear) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x27, 0x01];
+}
+
+/**
+ * set schedule settings
+ * @param {object} schedule_settings
+ * @param {number} schedule_settings.channel range: [1, 16]
+ * @param {number} schedule_settings.enable values: (1: "enable", 2: "disable")
+ * @param {number} schedule_settings.use_config values: (0: "no", 1: "yes")
+ * @param {number} schedule_settings.monday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.tuesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.wednesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.thursday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.friday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.saturday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.sunday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.execut_hour range: [0, 23]
+ * @param {number} schedule_settings.execut_min range: [0, 59]
+ * @param {number} schedule_settings.switch_1_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.switch_2_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.lock_state values: (0: "keep", 1: "lock", 2: "unlock")
+ * @example { "schedule_settings": [{ "channel": 1, "enable": 1, "use_config": 1, "monday": 1, "tuesday": 0, "wednesday": 1, "thursday": 0, "friday": 1, "saturday": 0, "sunday": 1, "execut_hour": 10, "execut_min": 5, "switch_1_state": 1, "switch_2_state": 1, "lock_state": 1 }] }
+ */
+function setScheduleSettings(schedule) {
+ var channel = schedule.channel;
+ var enable = schedule.enable;
+ var use_config = schedule.use_config;
+ var lock_state = schedule.lock_state;
+ var execut_hour = schedule.execut_hour;
+ var execut_min = schedule.execut_min;
+
+ if (typeof channel !== "number") {
+ throw new Error("schedule_settings._item.channel must be a number");
+ }
+ if (channel < 1 || channel > 16) {
+ throw new Error("schedule_settings._item.channel must be in range [1, 16]");
+ }
+ var enable_map = { 1: "enable", 2: "disable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("schedule_settings._item.enable must be one of " + enable_values.join(", "));
+ }
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(use_config) === -1) {
+ throw new Error("schedule_settings._item.use_config must be one of " + yes_no_values.join(", "));
+ }
+ var week_day_bits_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var days = 0x00;
+ var enable_map_2 = { 0: "disable", 1: "enable" };
+ var enable_values_2 = getValues(enable_map_2);
+ for (var day in week_day_bits_offset) {
+ if (enable_values_2.indexOf(schedule[day]) === -1) {
+ throw new Error("schedule_settings._item." + day + " must be one of " + enable_values_2.join(", "));
+ }
+ days |= getValue(enable_map_2, schedule[day]) << week_day_bits_offset[day];
+ }
+ var switch_bits_offset = { switch_1_state: 0, switch_2_state: 2 };
+ var switch_state_map = {0: "keep", 1: "on", 2: "off", 3: "reversal"};
+ var switch_state_values = getValues(switch_state_map);
+ var switchs = 0x00;
+ for (var switch_state in switch_bits_offset) {
+ if (switch_state_values.indexOf(schedule[switch_state]) === -1) {
+ throw new Error("schedule_settings._item." + switch_state + " must be one of " + switch_state_values.join(", "));
+ }
+ switchs |= getValue(switch_state_map, schedule[switch_state]) << switch_bits_offset[switch_state];
+ }
+ var lock_state_map = {0: "keep", 1: "lock", 2: "unlock"};
+ var lock_state_values = getValues(lock_state_map);
+ if (lock_state_values.indexOf(lock_state) === -1) {
+ throw new Error("schedule_settings._item.lock_state must be one of: " + lock_state_values.join(", "));
+ }
+ if(typeof execut_hour !== "number") {
+ throw new Error("schedule_settings._item.execut_hour must be a number");
+ }
+ if(execut_hour < 0 || execut_hour > 23) {
+ throw new Error("schedule_settings._item.execut_hour must be in range [0, 23]");
+ }
+ if(typeof execut_min !== "number") {
+ throw new Error("schedule_settings._item.execut_min must be a number");
+ }
+ if(execut_min < 0 || execut_min > 59) {
+ throw new Error("schedule_settings._item.execut_min must be in range [0, 59]");
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x64);
+ buffer.writeUInt8(channel);
+ var schedule_option = 0x00;
+ schedule_option |= getValue(enable_map, enable);
+ schedule_option |= getValue(yes_no_map, use_config) << 4;
+ buffer.writeUInt8(schedule_option);
+ buffer.writeUInt8(days);
+ buffer.writeUInt8(execut_hour);
+ buffer.writeUInt8(execut_min);
+ buffer.writeUInt8(switchs);
+ buffer.writeUInt8(getValue(lock_state_map, lock_state));
+ return buffer.toBytes();
+}
+
+/**
+ * get local rule
+ * @param {object} get_local_rule
+ * @param {number | string} get_local_rule.task_id range: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"]
+ * @example { "get_local_rule": {"task_id": 1} }
+ */
+function setGetLocalRule(get_local_rule) {
+ var task_id = get_local_rule.task_id;
+ var task_id_values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"];
+
+ if(task_id_values.indexOf(task_id) === -1) {
+ throw new Error("get_local_rule.task_id must be one of: " + task_id_values.join(", "));
+ }
+
+ var data = (task_id === "all schedules") ? 0xff : (task_id & 0xff);
+ return [0xf9, 0x65, data];
+}
+
+/**
+ * Anti-flash mode
+ * @param {object} anti_flash_mode
+ * @param {number} anti_flash_mode.enable values: (0: "disable", 1: "enable")
+ * @example { "anti_flash_mode": {"enable": 1} }
+ */
+function setAntiFlashMode(anti_flash_mode) {
+ var enable = anti_flash_mode.enable;
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xaa);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent alarm configuration
+ * @param {object} overcurrent_alarm_config
+ * @param {number} overcurrent_alarm_config.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_alarm_config.threshold range: [1, 10]
+ * @example { "overcurrent_alarm_config": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentAlarmConfig(overcurrent_alarm_config) {
+ var enable = overcurrent_alarm_config.enable;
+ var threshold = overcurrent_alarm_config.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_alarm_config.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_alarm_config.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x24);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent protection
+ * @param {object} overcurrent_protection
+ * @param {number} overcurrent_protection.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_protection.threshold range: [1, 10]
+ * @example { "overcurrent_protection": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentProtection(overcurrent_protection) {
+ var enable = overcurrent_protection.enable;
+ var threshold = overcurrent_protection.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("overcurrent_protection.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_protection.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_protection.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x30);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * highcurrent configuration
+ * @param {number} highcurrent_config values: (0: "disable", 1: "enable")
+ * @example { "highcurrent_config": 1 }
+ */
+function setHighcurrentConfig(highcurrent_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(highcurrent_config) === -1) {
+ throw new Error("highcurrent_config must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x8d);
+ buffer.writeUInt8(getValue(enable_map, highcurrent_config));
+ return buffer.toBytes();
+}
+
+/**
+ * power switch mode
+ * @param {number} power_switch_mode values: (0: "off", 1: "on", 2: "keep")
+ * @example { "power_switch_mode": 1 }
+ */
+function setPowerSwitchMode(power_switch_mode) {
+ var mode_map = {0: "off", 1: "on", 2: "keep"};
+ var mode_values = getValues(mode_map);
+ if (mode_values.indexOf(power_switch_mode) === -1) {
+ throw new Error("power_switch_mode must be one of " + mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x67);
+ buffer.writeUInt8(getValue(mode_map, power_switch_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * class mode
+ * @param {object} lorawan_class_cfg
+ * @param {number} lorawan_class_cfg.timestamp
+ * @param {number} lorawan_class_cfg.continue
+ * @param {number} lorawan_class_cfg.class_mode values: ("CLASS_B", 2: "CLASS_C")
+ * @example { "lorawan_class_cfg": {"timestamp": 1758768956, "continue": 600, "class_mode": 1} }
+ */
+function setLorawanClassCfg(lorawan_class_cfg) {
+ var class_mode = lorawan_class_cfg.class_mode;
+ var class_map = {1: "CLASS_B", 2: "CLASS_C"};
+ var class_values = getValues(class_map);
+ if (class_values.indexOf(class_mode) === -1) {
+ throw new Error("lorawan_class_cfg.class_mode must be one of " + class_values.join(", "));
+ }
+
+ var buffer = new Buffer(10);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xa4);
+ buffer.writeUInt32LE(lorawan_class_cfg.timestamp);
+ buffer.writeUInt16LE(lorawan_class_cfg.continue);
+ buffer.writeUInt8(getValue(class_map, class_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * Time Synchronize
+ * @param {number} time_synchronize values: (0: "no", 1: "yes")
+ * @example { "time_synchronize": 0 }
+ */
+function setTimeSynchronize(time_synchronize) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(time_synchronize) === -1) {
+ throw new Error("time_synchronize must be one of: " + yes_no_values.join(", "));
+ }
+ if (getValue(yes_no_map, time_synchronize) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x4a, 0x00];
+}
+
+/**
+ * D2D Settings
+ * @param {object, object} d2d_global_enable
+ * @param {number} d2d_global_enable.master_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.master_enable_change values: (0: "no", 1: "yes")
+ * @param {number} d2d_global_enable.agent_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.agent_enable_change values: (0: "no", 1: "yes")
+ * @example { "d2d_global_enable": {"master_enable": 0, "master_enable_change": 0, "agent_enable": 0, "agent_enable_change": 0} }
+ */
+function setD2DGlobalEnable(d2d_global_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ var master_enable = d2d_global_enable.master_enable;
+ var agent_enable = d2d_global_enable.agent_enable;
+ var master_enable_change = d2d_global_enable.master_enable_change;
+ var agent_enable_change = d2d_global_enable.agent_enable_change;
+
+ if (enable_values.indexOf(master_enable) === -1) {
+ throw new Error("d2d_global_enable.master_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(agent_enable) === -1) {
+ throw new Error("d2d_global_enable.agent_enable must be one of " + enable_values.join(", "));
+ }
+ if (yes_no_values.indexOf(master_enable_change) === -1) {
+ throw new Error("d2d_global_enable.master_enable_change must be one of " + yes_no_values.join(", "));
+ }
+ if (yes_no_values.indexOf(agent_enable_change) === -1) {
+ throw new Error("d2d_global_enable.agent_enable_change must be one of " + yes_no_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xc7);
+ var data = 0x00;
+ data |= getValue(enable_map, master_enable);
+ data |= getValue(yes_no_map, master_enable_change) << 4;
+ data |= getValue(enable_map, agent_enable) << 1;
+ data |= getValue(yes_no_map, agent_enable_change) << 5;
+
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Agent Settings
+ * @param {object} d2d_agent_settings_array
+ * @param {number} d2d_agent_settings_array.d2d_agent_id range: [0, 15]
+ * @param {number} d2d_agent_settings_array.d2d_agent_enable values: (0: "disable", 1: "enable")
+ * @param {string} d2d_agent_settings_array.d2d_agent_command
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_object values: (1: "switch1", 2: "switch2", 3: "switch1, switch2")
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_status values: (0: "off", 1: "on", 2: "reversel")
+ * @example { "d2d_agent_settings_array": [{"d2d_agent_id": 0, "d2d_agent_enable": 1, "d2d_agent_command": "0000", "d2d_agent_action": {"switch_object": 1, "switch_status": 1}}] }
+ */
+function setD2DAgentSettings(d2d_agent_settings) {
+ var d2d_agent_id = d2d_agent_settings.d2d_agent_id;
+ var d2d_agent_enable = d2d_agent_settings.d2d_agent_enable;
+ var d2d_agent_command = d2d_agent_settings.d2d_agent_command;
+ var switch_object = d2d_agent_settings.d2d_agent_action.switch_object;
+ var switch_status = d2d_agent_settings.d2d_agent_action.switch_status;
+
+ if (typeof d2d_agent_id !== "number") {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be a number");
+ }
+ if (d2d_agent_id < 0 || d2d_agent_id > 15) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be in range [0, 15]");
+ }
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(d2d_agent_enable) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_enable must be one of " + enable_values.join(", "));
+ }
+ var switch_map = {1: "switch1", 2: "switch2", 3: "switch1, switch2"};
+ var switch_values = getValues(switch_map);
+ if (switch_values.indexOf(switch_object) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_object must be one of " + switch_values.join(", "));
+ }
+ var switch_status_map = {0: "off", 1: "on", 2: "reversel"};
+ var switch_status_values = getValues(switch_status_map);
+ if (switch_status_values.indexOf(switch_status) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_status must be one of " + switch_status_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x83);
+ buffer.writeUInt8(d2d_agent_id);
+ buffer.writeUInt8(getValue(enable_map, d2d_agent_enable));
+ buffer.writeD2DCommand(d2d_agent_command, "0000");
+ var data = 0x00;
+ data |= getValue(switch_status_map, switch_status);
+ data |= getValue(switch_map, switch_object) << 4;
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Controller Settings
+ * @param {object} d2d_controller_settings
+ * @param {number} d2d_controller_settings.key_contrl_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_controller_settings.uplink.lora_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_controller_settings.uplink.key_enable values: (0: "disable", 1: "enable")
+ * @param {string} d2d_controller_settings.contrl_cmd
+ * @param {number} id values: (0x00, 0x01)
+ * @example { "d2d_controller_settings": [{"key_contrl_enable": 1, "uplink": { "lora_enable": 1, "key_enable": 1 }, "contrl_cmd": "0000"}] }
+ */
+function setD2DControllerSettings(d2d_controller_settings, id) {
+ var enable = d2d_controller_settings.key_contrl_enable;
+ var lora_enable = d2d_controller_settings.uplink.lora_enable;
+ var key_enable = d2d_controller_settings.uplink.key_enable;
+ var contrl_cmd = d2d_controller_settings.contrl_cmd;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("d2d_controller_settings.key_contrl_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(lora_enable) === -1) {
+ throw new Error("d2d_controller_settings.uplink.lora_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(key_enable) === -1) {
+ throw new Error("d2d_controller_settings.uplink.key_enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xb8);
+ buffer.writeUInt8(id);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ var data = 0x00;
+ data |= getValue(enable_map, lora_enable);
+ data |= getValue(enable_map, key_enable) << 1;
+ buffer.writeUInt8(data);
+ buffer.writeD2DCommand(contrl_cmd);
+ return buffer.toBytes();
+}
+
+/**
+ * set time zone
+ * @param {number} time_zone unit: minute, convert: "hh:mm" -> "hh * 60 + mm", values: ( -720: UTC-12, -660: UTC-11, -600: UTC-10, -570: UTC-9:30, -540: UTC-9, -480: UTC-8, -420: UTC-7, -360: UTC-6, -300: UTC-5, -240: UTC-4, -210: UTC-3:30, -180: UTC-3, -120: UTC-2, -60: UTC-1, 0: UTC, 60: UTC+1, 120: UTC+2, 180: UTC+3, 240: UTC+4, 300: UTC+5, 360: UTC+6, 420: UTC+7, 480: UTC+8, 540: UTC+9, 570: UTC+9:30, 600: UTC+10, 660: UTC+11, 720: UTC+12, 765: UTC+12:45, 780: UTC+13, 840: UTC+14 )
+ * @example { "time_zone": 480 }
+ * @example { "time_zone": -240 }
+ */
+function setTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ var timezone_values = getValues(timezone_map);
+ if (timezone_values.indexOf(time_zone) === -1) {
+ throw new Error("time_zone must be one of " + timezone_values.join(", "));
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xbd);
+ buffer.writeInt16LE(getValue(timezone_map, time_zone));
+ return buffer.toBytes();
+}
+
+/**
+ * set daylight saving time
+ * @since v2.0
+ * @param {object} daylight_saving_time
+ * @param {number} daylight_saving_time.daylight_saving_time_enable values: (0: disable, 1: enable)
+ * @param {number} daylight_saving_time.daylight_saving_time_offset, unit: minute range: [1, 120]
+ * @param {number} daylight_saving_time.start_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.start_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.start_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.start_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @param {number} daylight_saving_time.end_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.end_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.end_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.end_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @example { "daylight_saving_time": { "daylight_saving_time_enable": 1, "daylight_saving_time_offset": 60, "start_month": 3, "start_week_num": 2, "start_week_day": 7, "start_hour_min": 120, "end_month": 1, "end_week_num": 4, "end_week_day": 1, "end_hour_min": 180 } } output: FFBA013C032778000141B400
+ */
+function setDaylightSavingTime(daylight_saving_time) {
+ var enable = daylight_saving_time.daylight_saving_time_enable;
+ var offset = daylight_saving_time.daylight_saving_time_offset;
+ var start_month = daylight_saving_time.start_month;
+ var start_week_num = daylight_saving_time.start_week_num;
+ var start_week_day = daylight_saving_time.start_week_day;
+ var start_hour_min = daylight_saving_time.start_hour_min;
+ var end_month = daylight_saving_time.end_month;
+ var end_week_num = daylight_saving_time.end_week_num;
+ var end_week_day = daylight_saving_time.end_week_day;
+ var end_hour_min = daylight_saving_time.end_hour_min;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("daylight_saving_time.daylight_saving_time_enable must be one of " + enable_values.join(", "));
+ }
+ if (typeof offset !== "number") {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be a number");
+ }
+ if (offset < 1 || offset > 120) {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be in range [1, 120]");
+ }
+
+ var week_values = [1, 2, 3, 4, 5, 6, 7];
+ var month_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+ var enable_value = getValue(enable_map, enable);
+ if (enable_value && month_values.indexOf(start_month) === -1) {
+ throw new Error("daylight_saving_time.start_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && month_values.indexOf(end_month) === -1) {
+ throw new Error("daylight_saving_time.end_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(start_week_day) === -1) {
+ throw new Error("daylight_saving_time.start_week_day must be one of " + week_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(end_week_day) === -1) {
+ throw new Error("daylight_saving_time.end_week_day must be one of " + week_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 7;
+ data |= offset;
+
+ var buffer = new Buffer(11);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x72);
+ buffer.writeUInt8(data);
+ buffer.writeUInt8(start_month);
+ buffer.writeUInt8((start_week_num << 4) | start_week_day);
+ buffer.writeUInt16LE(start_hour_min);
+ buffer.writeUInt8(end_month);
+ buffer.writeUInt8((end_week_num << 4) | end_week_day);
+ buffer.writeUInt16LE(end_hour_min);
+ return buffer.toBytes();
+}
+
+function getValues(map) {
+ var values = [];
+ for (var key in map) {
+ values.push(RAW_VALUE ? parseInt(key) : map[key]);
+ }
+ return values;
+}
+
+function getValue(map, value) {
+ if (RAW_VALUE) return value;
+
+ for (var key in map) {
+ if (map[key] === value) {
+ return parseInt(key);
+ }
+ }
+
+ throw new Error("not match in " + JSON.stringify(map));
+}
+
+function Buffer(size) {
+ this.buffer = new Array(size);
+ this.offset = 0;
+
+ for (var i = 0; i < size; i++) {
+ this.buffer[i] = 0;
+ }
+}
+
+Buffer.prototype._write = function (value, byteLength, isLittleEndian) {
+ var offset = 0;
+ for (var index = 0; index < byteLength; index++) {
+ offset = isLittleEndian ? index << 3 : (byteLength - 1 - index) << 3;
+ this.buffer[this.offset + index] = (value >> offset) & 0xff;
+ }
+};
+
+Buffer.prototype.writeUInt8 = function (value) {
+ this._write(value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeInt8 = function (value) {
+ this._write(value < 0 ? value + 0x100 : value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeUInt16LE = function (value) {
+ this._write(value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeInt16LE = function (value) {
+ this._write(value < 0 ? value + 0x10000 : value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeUInt32LE = function (value) {
+ this._write(value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeInt32LE = function (value) {
+ this._write(value < 0 ? value + 0x100000000 : value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeD2DCommand = function (value, defaultValue) {
+ if (typeof value !== "string") {
+ value = defaultValue;
+ }
+ if (value.length !== 4) {
+ throw new Error("d2d_agent_command length must be 4");
+ }
+ this.buffer[this.offset] = parseInt(value.substr(2, 2), 16);
+ this.buffer[this.offset + 1] = parseInt(value.substr(0, 2), 16);
+ this.offset += 2;
+};
+
+Buffer.prototype.toBytes = function () {
+ return this.buffer;
+};
diff --git a/ws-series/ws502-v4/ws502-v4.png b/ws-series/ws502-v4/ws502-v4.png
new file mode 100644
index 00000000..6bc85588
Binary files /dev/null and b/ws-series/ws502-v4/ws502-v4.png differ
diff --git a/ws-series/ws503-v4/README.md b/ws-series/ws503-v4/README.md
new file mode 100644
index 00000000..3d3dc1f3
--- /dev/null
+++ b/ws-series/ws503-v4/README.md
@@ -0,0 +1,248 @@
+# Smart Wall Switch - WS503
+
+
+
+For more detailed information, please visit [Milesight Official Website](https://www.milesight.com/iot/)
+
+## Payload
+
+```
++-------------------------------------------------------+
+| DEVICE UPLINK / DOWNLINK PAYLOAD |
++---------------------------+---------------------------+
+| DATA 1 | DATA 2 |
++--------+--------+---------+--------+--------+---------+
+| ID | TYPE | DATA | ID | TYPE | DATA |
++--------+--------+---------+--------+--------+---------+
+| 1 Byte | 1 Byte | N Bytes | 1 Byte | 1 Byte | N Bytes |
+|--------+--------+---------+--------+--------+---------+
+```
+
+### Attribute
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :-----------: | :--: | :--: | :----: | ------------------------------------------------------------------------------------------------ |
+| IPSO | 0xFF | 0x01 | 1 | ipso_version(1B) |
+| Hardware | 0xFF | 0x09 | 2 | hardware_version(2B)
hardware_version, e.g. 0110 -> v1.1 |
+| Firmware | 0xFF | 0x0A | 2 | firmware_version(2B)
firmware_version, e.g. 0110 -> v1.10 |
+| TSL | 0xFF | 0xFF | 2 | tsl_version(2B) |
+| Serial Number | 0xFF | 0x16 | 8 | sn(8B) |
+| LoRaWAN Class | 0xFF | 0x0F | 1 | lorawan_class(1B)
lorawan_class, values: (1: Class B, 2: Class C) |
+| Reset Event | 0xFF | 0xFE | 1 | reset_event(1B) |
+| Device Status | 0xFF | 0x0B | 1 | device_status(1B) |
+
+### Telemetry
+
+| CHANNEL | ID | TYPE | LENGTH | DESCRIPTION |
+| :----------------------: | :--: | :--: | :----: | -------------------------------------------------------------------------------------------------------------------------------------- |
+| Reporting Interval | 0xFF | 0x03 | 2 | reporting_interval(2B) |
+| Reboot | 0xFF | 0x10 | 1 | Reboot device |
+| Button Lock Config | 0xFF | 0x25 | 2 | enable(1B)
enable, values(0: disable, 1: enable) |
+| Report Status | 0xFF | 0x28 | 1 | report_status(1B) |
+| Switch Control | 0xFF | 0x29 | 1 | status_1(0..1)+status_2(1..2)+status_3(2..3)
status_1,status_2,status_3, values(0: off, 1: on) |
+| Report Attribute | 0xFF | 0x2C | 1 | report_attribute(1B) |
+| LED Mode | 0xFF | 0x2F | 1 | led_mode(1B)
led_mode, values(0: disable, 1: Enable (relay closed indicator off)) |
+| Button Reset Config | 0xFF | 0x5E | 1 | button_reset_config(1B)
button_reset_config, values(0: disable, 1: enable) |
+| Power Consumption Enable | 0xFF | 0x26 | 1 | power_consumption_enable(1B)
power_consumption_enable, values(0: disable, 1: enable) |
+| Power Consumption Clear | 0xFF | 0x27 | 1 | power_consumption_clear(1B) |
+| Overcurrent Alarm Config | 0xFF | 0x24 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| Overcurrent Protection | 0xFF | 0x30 | 2 | enable(1B)+threshold(1B)
enable, values(0: disable, 1: enable)
threshold, range[1, 10], unit: A |
+| High Current Config | 0xFF | 0x8D | 1 | highcurrent_config(1B)
highcurrent_config, values(0: disable, 1: enable) |
+| Power Switch Mode | 0xFF | 0x67 | 1 | power_switch_mode(1B)
power_switch_mode, values(0: off, 1: on, 2: keep) |
+| Time Synchronize | 0xFF | 0x4A | 1 | time_synchronize(1B) |
+| D2D Enable | 0xFF | 0xC7 | 1 | master_enable(0..1)+agent_enable(1..2)+master_enable_change(4..5)+agent_enable_change(5..6)
master_enable, values(0: disable, 1: enable)
agent_enable, values(0: disable, 1: enable)
master_enable_change, values(0: no, 1: yes)
agent_enable_change, values(0: no, 1: yes) |
+| D2D Agent Settings | 0xFF | 0x83 | 5 | d2d_agent_id(1B)+d2d_agent_enable(1B)+d2d_agent_command(2B)+d2d_agent_action(1B)
d2d_agent_id, range[0, 15]
d2d_agent_enable, values(0: disable, 1: enable)
d2d_agent_action.switch_object, values(1: switch1, 2: switch2, 3: switch1,switch2, 4: switch3, 5: switch1,switch3, 6: switch2,switch3, 7: switch1,switch2,switch3)
d2d_agent_action.switch_status, values(0: off, 1: on, 2: reversal) |
+| Time Zone | 0xFF | 0xBD | 2 | time_zone(2B) |
+| Schedule Settings | 0xF9 | 0x64 | 7 | channel(1B), range[1, 16]
enable(0..4), values(1: enable, 2: disable), use_config(4..8), values(0, no, 1: yes)
read: bits, (bit1: monday, bit2: tuesday, bit3: wednesday, bit4: thursday, bit5: friday, bit6: saturday, bit7: sunday)
execut_hour(1B)
execut_min(1B)
switch_1_state(0..2),switch_2_state(2..4),switch_3_state(4..6), values(0: keep, 1: on, 2: off, 3: reversal)
lock_state(1B), values(0: keep, 1: lock, 2: unlock) |
+| Get Local Rule | 0xF9 | 0x65 | 1 | task_id(1B), range[1, 16] & 255 |
+| Anti Flash Mode | 0xF9 | 0xAA | 1 | enable(0..1), values(0: disable, 1: enable) |
+| Load Power | 0xF9 | 0xAB | 6 | power_1(2B)+power_2(2B)+power_3(2B)
power_1,power_2,power_3, range[0, 1100] |
+| LoRaWAN Class Config | 0xF9 | 0xA4 | 8 | timestamp(4B)+continue(2B)+class_mode(1B)
class_mode, values(1: CLASS_B, 2: CLASS_C) |
+| D2D Controller Settings | 0xF9 | 0xB8 | 5 | keyid(1B)+key_contrl_enable(1B)+uplink(1B)+contrl_cmd(2B)
keyid, values(0: key1)
key_contrl_enable, values(0: disable, 1: enable)
uplink.lora_enable(0..1), values(0: disable, 1: enable), uplink.key_enable(1..2), values(0: disable, 1: enable) |
+| Daylight Saving Time | 0xF9 | 0x72 | 9 | daylight_saving_time_offset(0..6)+daylight_saving_time_enable(6..7)+start_month(1B)+start_week_num(0..3)+start_week_day(4..7)+start_hour_min(2B)+end_month(1B)+end_week_num(0..3)+end_week_day(4..7)+end_hour_min(2B)
daylight_saving_time_offset, range[1, 120]
daylight_saving_time_enable, values(0: disable, 1: enable)
start_month,end_month, values(1: Jan., 2: Feb., 3: Mar., 4: Apr., 5: May, 6: Jun., 7: Jul., 8: Aug. 9: Sep., 10: Oct., 11: Nov., 12: Dec.)
start_week_num,end_week_num, values(1: 1st, 2: 2nd, 3: 3rd, 4: 4th, 5: last)
start_week_day,end_week_day, values(1:Mon., 2:Tues., 3:Wed., 4:Thurs., 5:Fri., 6:Sat., 7:Sun.)
start_hour_min,end_hour_min, range[0, 1440] |
+
+### Status Definition
+
+| bits | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+| :--: | :-: | :-------------: | :-------------: | :-------------: | :-: | :------: | :------: | :------: |
+| | - | switch_3_change | switch_2_change | switch_1_change | - | switch_3 | switch_2 | switch_1 |
+
+## Example
+
+```json
+// Reporting Interval (FF033C00)
+{
+ "reporting_interval": 60
+}
+
+// Reboot (FF10FF)
+{
+ "reboot": "yes"
+}
+
+// Button Lock Config (FF250080)
+{
+ "button_lock_config": {
+ "enable": "enable"
+ }
+}
+
+// Report Status (FF28FF)
+{
+ "report_status": "yes"
+}
+
+// Switch Control (FF2974)
+{
+ "switch_control": {
+ "status_1": "off",
+ "status_2": "off",
+ "status_3": "on"
+ }
+}
+
+// Report Attribute (FF2CFF)
+{
+ "report_attribute": "yes"
+}
+
+// LED Mode (FF2F00)
+{
+ "led_mode":"disable"
+}
+
+// Button Reset Config (FF5E01)
+{
+ "button_reset_config": "enable"
+}
+
+// Power Consumption Enable (FF2601)
+{
+ "power_consumption_enable": "enable"
+}
+
+// Power Consumption Clear (FF2701)
+{
+ "power_consumption_clear": "yes"
+}
+
+// Overcurrent Alarm (FF24010A)
+{
+ "overcurrent_alarm_config": {
+ "enable": "enable",
+ "threshold": 10
+ }
+}
+
+// Overcurrent Protection (FF300101)
+{
+ "overcurrent_protection": {
+ "enable": "enable",
+ "threshold": 1
+ }
+}
+
+// High Current Config (FF8D01)
+{
+ "highcurrent_config": "enable"
+}
+
+// Schedule Settings (F96401117F01011502)
+{
+ "schedule_settings": [
+ {
+ "channel": 1,
+ "enable": "enable",
+ "execut_hour": 1,
+ "execut_min": 1,
+ "friday": "enable",
+ "lock_state": "unlock",
+ "monday": "enable",
+ "saturday": "enable",
+ "sunday": "enable",
+ "switch_1_state": "on",
+ "switch_2_state": "on",
+ "switch_3_state": "on",
+ "thursday": "enable",
+ "tuesday": "enable",
+ "use_config": "yes",
+ "wednesday": "enable"
+ }
+ ]
+}
+
+// D2D Agent Settings (FF830201AAAA72)
+{
+ "d2d_agent_settings_array": [
+ {
+ "d2d_agent_action": {
+ "switch_object": "switch1, switch2, switch3",
+ "switch_status": "reversel"
+ },
+ "d2d_agent_command": "aaaa",
+ "d2d_agent_enable": "enable",
+ "d2d_agent_id": 2
+ }
+ ]
+}
+
+// Get Local Rule (F96501)
+{
+ "get_local_rule": {
+ "task_id": 1
+ }
+}
+
+// Anti Flash Mode (F9AA01)
+{
+ "anti_flash_mode": {
+ "enable": "enable"
+ }
+}
+
+// Load Power (F9AB580158015801)
+{
+ "load_power": {
+ "power_1": 344,
+ "power_2": 344,
+ "power_3": 344
+ }
+}
+
+// LoRaWAN Class Config(F9A40000000000000100)
+{
+ "lorawan_class_cfg": {
+ "timestamp": 0,
+ "continue": 0,
+ "class_mode": "CLASS_B"
+ }
+}
+
+// D2D Controller Settings (F9B80201023412)
+{
+ "key3_d2d_controller_settings": {
+ "contrl_cmd": "1234",
+ "key_contrl_enable": "enable",
+ "uplink": {
+ "key_enable": "enable",
+ "lora_enable": "disable"
+ }
+ }
+}
+
+// Daylight Saving Time (F9726F011126020111FF00)
+{
+ "daylight_saving_time": {
+ "daylight_saving_time_enable": "disable",
+ "end_month": 1,
+ "end_hour_min": 255,
+ "end_week_day": 1,
+ "end_week_num": 1,
+ "daylight_saving_time_offset": 111,
+ "start_month": 1,
+ "start_hour_min": 550,
+ "start_week_day": 1,
+ "start_week_num": 1
+ }
+}
+```
diff --git a/ws-series/ws503-v4/ws503-v4-codec.json b/ws-series/ws503-v4/ws503-v4-codec.json
new file mode 100644
index 00000000..0a1467be
--- /dev/null
+++ b/ws-series/ws503-v4/ws503-v4-codec.json
@@ -0,0 +1,1496 @@
+{
+ "version": "1.0.0",
+ "object": [
+ {
+ "id": "voltage",
+ "name": "Voltage",
+ "value": "",
+ "unit": "V",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "FLOAT",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 5,
+ "bacnet_unit_type": "UNITS_VOLTS"
+ },
+ {
+ "id": "electric_power",
+ "name": "Electric Power",
+ "value": "",
+ "unit": "W",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 47,
+ "bacnet_unit_type": "UNITS_WATTS"
+ },
+ {
+ "id": "power_factor",
+ "name": "Power Factor",
+ "value": "",
+ "unit": "%",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 0,
+ 100
+ ],
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 98,
+ "bacnet_unit_type": "UNITS_PERCENT"
+ },
+ {
+ "id": "power_consumption",
+ "name": "Power Consumption",
+ "value": "",
+ "unit": "Wh",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 18,
+ "bacnet_unit_type": "UNITS_WATT_HOURS"
+ },
+ {
+ "id": "current",
+ "name": "Current Rating",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES"
+ },
+ {
+ "id": "switch_reading.status_1",
+ "name": "Switch 1",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_reading.status_2",
+ "switch_reading.status_3"
+ ]
+ },
+ {
+ "id": "switch_reading.status_2",
+ "name": "Switch 2",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_reading.status_1",
+ "switch_reading.status_3"
+ ]
+ },
+ {
+ "id": "switch_reading.status_3",
+ "name": "Switch 3",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_reading.status_1",
+ "switch_reading.status_2"
+ ]
+ },
+ {
+ "id": "reporting_interval",
+ "name": "Reporting Interval",
+ "value": "1200",
+ "unit": "s",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 60,
+ 64800
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 73,
+ "bacnet_unit_type": "UNITS_SECONDS"
+ },
+ {
+ "id": "power_consumption_enable",
+ "name": "Power Consumption Enable",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "led_mode",
+ "name": "LED Indicator",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "Enable (relay closed indicator off)",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_reset_config",
+ "name": "Reset Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "button_lock_config.enable",
+ "name": "Switch Lock Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "overcurrent_alarm_config.enable",
+ "name": "Overcurrent Alarm Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm_config.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm_config.threshold",
+ "name": "Overcurrent Alarm Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_alarm_config.enable"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.enable",
+ "name": "Overcurrent Protection Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_protection.threshold"
+ ]
+ },
+ {
+ "id": "overcurrent_protection.threshold",
+ "name": "Overcurrent Protection Value",
+ "value": "10",
+ "unit": "A",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 10
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 3,
+ "bacnet_unit_type": "UNITS_AMPERES",
+ "reference": [
+ "overcurrent_protection.enable"
+ ]
+ },
+ {
+ "id": "highcurrent_config",
+ "name": "Highcurrent Protection",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "switch_control.status_1",
+ "name": "Switch 1",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_control.status_2",
+ "switch_control.status_3"
+ ]
+ },
+ {
+ "id": "switch_control.status_2",
+ "name": "Switch 2",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_control.status_1",
+ "switch_control.status_3"
+ ]
+ },
+ {
+ "id": "switch_control.status_3",
+ "name": "Switch 3",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "switch_control.status_1",
+ "switch_control.status_2"
+ ]
+ },
+ {
+ "id": "power_switch_mode",
+ "name": "Power Switch Mode",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "off",
+ "value": 0
+ },
+ {
+ "name": "on",
+ "value": 1
+ },
+ {
+ "name": "keep",
+ "value": 2
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS"
+ },
+ {
+ "id": "d2d_global_enable.master_enable",
+ "name": "D2D Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.agent_enable"
+ ]
+ },
+ {
+ "id": "d2d_global_enable.agent_enable",
+ "name": "D2D Agent Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "d2d_global_enable.master_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink",
+ "key1_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key1_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key1_d2d_controller_settings.key_contrl_enable",
+ "key1_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink",
+ "key2_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key2_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key2_d2d_controller_settings.key_contrl_enable",
+ "key2_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "key3_d2d_controller_settings.key_contrl_enable",
+ "name": "Key Controller Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key3_d2d_controller_settings.uplink",
+ "key3_d2d_controller_settings.contrl_cmd"
+ ]
+ },
+ {
+ "id": "key3_d2d_controller_settings.uplink.lora_enable",
+ "name": "Controller Lora Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key3_d2d_controller_settings.uplink.key_enable"
+ ]
+ },
+ {
+ "id": "key3_d2d_controller_settings.uplink.key_enable",
+ "name": "Controller Key Status Uplink Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key3_d2d_controller_settings.uplink.lora_enable"
+ ]
+ },
+ {
+ "id": "key3_d2d_controller_settings.contrl_cmd",
+ "name": "Control Command",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "key3_d2d_controller_settings.key_contrl_enable",
+ "key3_d2d_controller_settings.uplink"
+ ]
+ },
+ {
+ "id": "time_zone",
+ "name": "Time Zone",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "INT16",
+ "values": [
+ {
+ "name": "UTC-12(IDLW)",
+ "value": -720
+ },
+ {
+ "name": "UTC-11(SST)",
+ "value": -660
+ },
+ {
+ "name": "UTC-10(HST)",
+ "value": -600
+ },
+ {
+ "name": "UTC-9:30(MIT)",
+ "value": -570
+ },
+ {
+ "name": "UTC-9(AKST)",
+ "value": -540
+ },
+ {
+ "name": "UTC-8(PST)",
+ "value": -480
+ },
+ {
+ "name": "UTC-7(MST)",
+ "value": -420
+ },
+ {
+ "name": "UTC-6(CST)",
+ "value": -360
+ },
+ {
+ "name": "UTC-5(EST)",
+ "value": -300
+ },
+ {
+ "name": "UTC-4(AST)",
+ "value": -240
+ },
+ {
+ "name": "UTC-3:30(NST)",
+ "value": -210
+ },
+ {
+ "name": "UTC-3(BRT)",
+ "value": -180
+ },
+ {
+ "name": "UTC-2(FNT)",
+ "value": -120
+ },
+ {
+ "name": "UTC-1(CVT)",
+ "value": -60
+ },
+ {
+ "name": "UTC(WET)",
+ "value": 0
+ },
+ {
+ "name": "UTC+1(CET)",
+ "value": 60
+ },
+ {
+ "name": "UTC+2(EET)",
+ "value": 120
+ },
+ {
+ "name": "UTC+3(MSK)",
+ "value": 180
+ },
+ {
+ "name": "UTC+3:30(IRST)",
+ "value": 210
+ },
+ {
+ "name": "UTC+4(GST)",
+ "value": 240
+ },
+ {
+ "name": "UTC+4:30(AFT)",
+ "value": 270
+ },
+ {
+ "name": "UTC+5(PKT)",
+ "value": 300
+ },
+ {
+ "name": "UTC+5:30(IST)",
+ "value": 330
+ },
+ {
+ "name": "UTC+5:45(NPT)",
+ "value": 345
+ },
+ {
+ "name": "UTC+6(BHT)",
+ "value": 360
+ },
+ {
+ "name": "UTC+6:30(MMT)",
+ "value": 390
+ },
+ {
+ "name": "UTC+7(ICT)",
+ "value": 420
+ },
+ {
+ "name": "UTC+8(CT/CST)",
+ "value": 480
+ },
+ {
+ "name": "UTC+9(JST)",
+ "value": 540
+ },
+ {
+ "name": "UTC+9:30(ACST)",
+ "value": 570
+ },
+ {
+ "name": "UTC+10(AEST)",
+ "value": 600
+ },
+ {
+ "name": "UTC+10:30(LHST)",
+ "value": 630
+ },
+ {
+ "name": "UTC+11(VUT)",
+ "value": 660
+ },
+ {
+ "name": "UTC+12(NZST)",
+ "value": 720
+ },
+ {
+ "name": "UTC+12:45(CHAST)",
+ "value": 765
+ },
+ {
+ "name": "UTC+13(PHOT)",
+ "value": 780
+ },
+ {
+ "name": "UTC+14(LINT)",
+ "value": 840
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES"
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_enable",
+ "name": "DST Enable",
+ "value": "0",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "disable",
+ "value": 0
+ },
+ {
+ "name": "enable",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_output_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.daylight_saving_time_offset",
+ "name": "DST Bias",
+ "value": "1",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "range": [
+ 1,
+ 120
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.start_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_month",
+ "name": "Month",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Jan.",
+ "value": 1
+ },
+ {
+ "name": "Feb.",
+ "value": 2
+ },
+ {
+ "name": "Mar.",
+ "value": 3
+ },
+ {
+ "name": "Apr.",
+ "value": 4
+ },
+ {
+ "name": "May",
+ "value": 5
+ },
+ {
+ "name": "Jun.",
+ "value": 6
+ },
+ {
+ "name": "Jul.",
+ "value": 7
+ },
+ {
+ "name": "Aug.",
+ "value": 8
+ },
+ {
+ "name": "Sep.",
+ "value": 9
+ },
+ {
+ "name": "Oct.",
+ "value": 10
+ },
+ {
+ "name": "Nov.",
+ "value": 11
+ },
+ {
+ "name": "Dec.",
+ "value": 12
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_num",
+ "name": "Number of Week",
+ "value": "1",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "1st",
+ "value": 1
+ },
+ {
+ "name": "2nd",
+ "value": 2
+ },
+ {
+ "name": "3rd",
+ "value": 3
+ },
+ {
+ "name": "4th",
+ "value": 4
+ },
+ {
+ "name": "last",
+ "value": 5
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_day",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_week_day",
+ "name": "Week",
+ "value": "7",
+ "unit": "",
+ "access_mode": "W",
+ "data_type": "ENUM",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "Mon.",
+ "value": 1
+ },
+ {
+ "name": "Tues.",
+ "value": 2
+ },
+ {
+ "name": "Wed.",
+ "value": 3
+ },
+ {
+ "name": "Thurs.",
+ "value": 4
+ },
+ {
+ "name": "Fri.",
+ "value": 5
+ },
+ {
+ "name": "Sat.",
+ "value": 6
+ },
+ {
+ "name": "Sun.",
+ "value": 7
+ }
+ ],
+ "bacnet_type": "multistate_value_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_hour_min"
+ ]
+ },
+ {
+ "id": "daylight_saving_time.end_hour_min",
+ "name": "Time",
+ "value": "0",
+ "unit": "min",
+ "access_mode": "W",
+ "data_type": "NUMBER",
+ "value_type": "UINT16",
+ "range": [
+ 0,
+ 1440
+ ],
+ "bacnet_type": "analog_output_object",
+ "bacnet_unit_type_id": 72,
+ "bacnet_unit_type": "UNITS_MINUTES",
+ "reference": [
+ "daylight_saving_time.daylight_saving_time_enable",
+ "daylight_saving_time.daylight_saving_time_offset",
+ "daylight_saving_time.start_month",
+ "daylight_saving_time.start_week_num",
+ "daylight_saving_time.start_week_day",
+ "daylight_saving_time.start_hour_min",
+ "daylight_saving_time.end_month",
+ "daylight_saving_time.end_week_num",
+ "daylight_saving_time.end_week_day"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.current",
+ "name": "Current",
+ "value": "",
+ "unit": "mA",
+ "access_mode": "R",
+ "data_type": "NUMBER",
+ "value_type": "UINT8",
+ "bacnet_type": "analog_input_object",
+ "bacnet_unit_type_id": 2,
+ "bacnet_unit_type": "UNITS_MILLIAMPERES",
+ "reference": [
+ "overcurrent_alarm.status"
+ ]
+ },
+ {
+ "id": "overcurrent_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overcurrent",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": [
+ "overcurrent_alarm.current"
+ ]
+ },
+ {
+ "id": "device_abnormal_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "abnormal",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ },
+ {
+ "id": "temperature_alarm.status",
+ "name": "Alarm Status",
+ "value": "",
+ "unit": "",
+ "access_mode": "R",
+ "data_type": "BOOL",
+ "value_type": "UINT8",
+ "values": [
+ {
+ "name": "normal",
+ "value": 0
+ },
+ {
+ "name": "overtemperature",
+ "value": 1
+ }
+ ],
+ "bacnet_type": "binary_input_object",
+ "bacnet_unit_type_id": 95,
+ "bacnet_unit_type": "UNITS_NO_UNITS",
+ "reference": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/ws-series/ws503-v4/ws503-v4-decoder.js b/ws-series/ws503-v4/ws503-v4-decoder.js
new file mode 100644
index 00000000..d9304314
--- /dev/null
+++ b/ws-series/ws503-v4/ws503-v4-decoder.js
@@ -0,0 +1,769 @@
+/**
+ * Payload Decoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS503
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function decodeUplink(input) {
+ var decoded = milesightDeviceDecode(input.bytes);
+ return { data: decoded };
+}
+
+// Chirpstack v3
+function Decode(fPort, bytes) {
+ return milesightDeviceDecode(bytes);
+}
+
+// The Things Network
+function Decoder(bytes, port) {
+ return milesightDeviceDecode(bytes);
+}
+/* eslint-enable */
+
+function milesightDeviceDecode(bytes) {
+ var decoded = {};
+
+ for (var i = 0; i < bytes.length;) {
+ var channel_id = bytes[i++];
+ var channel_type = bytes[i++];
+
+ // IPSO VERSION
+ if (channel_id === 0xff && channel_type === 0x01) {
+ decoded.ipso_version = readProtocolVersion(bytes[i]);
+ i += 1;
+ }
+ // HARDWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x09) {
+ decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // FIRMWARE VERSION
+ else if (channel_id === 0xff && channel_type === 0x0a) {
+ decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // TSL VERSION
+ else if (channel_id === 0xff && channel_type === 0xff) {
+ decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // SERIAL NUMBER
+ else if (channel_id === 0xff && channel_type === 0x16) {
+ decoded.sn = readSerialNumber(bytes.slice(i, i + 8));
+ i += 8;
+ }
+ // LORAWAN CLASS TYPE
+ else if (channel_id === 0xff && channel_type === 0x0f) {
+ decoded.lorawan_class = readLoRaWANClass(bytes[i]);
+ i += 1;
+ }
+ // RESET EVENT
+ else if (channel_id === 0xff && channel_type === 0xfe) {
+ decoded.reset_event = readResetEvent(1);
+ i += 1;
+ }
+ // DEVICE STATUS
+ else if (channel_id === 0xff && channel_type === 0x0b) {
+ decoded.device_status = readDeviceStatus(1);
+ i += 1;
+ }
+ // SWITCH STATE
+ else if (channel_id === 0x08 && channel_type === 0x29) {
+ // payload (0 0 0 0 0 0 0 0)
+ // Switch 3 2 1 3 2 1
+ // ------- -------
+ // bit mask change state
+ var value = bytes[i];
+ var switch_bit_offset = { status_1: 0, status_2: 1, status_3: 2 };
+ decoded.switch_reading = {};
+ for (var key in switch_bit_offset) {
+ decoded.switch_reading[key] = readOnOffStatus(value >>> switch_bit_offset[key] & 0x01);
+ decoded.switch_reading[key + '_change'] = readYesNoStatus((value >>> (switch_bit_offset[key] + 4)) & 0x01);
+ }
+ i += 1;
+ }
+ // Voltage
+ else if (channel_id === 0x03 && channel_type === 0x74) {
+ decoded.voltage = readUInt16LE(bytes.slice(i, i + 2)) / 10;
+ i += 2;
+ }
+ // Electric Power
+ else if (channel_id === 0x04 && channel_type === 0x80) {
+ decoded.electric_power = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Power Factor
+ else if (channel_id === 0x05 && channel_type === 0x81) {
+ decoded.power_factor = readUInt8(bytes.slice(i, i + 1));
+ i += 1;
+ }
+ // Power Consumption
+ else if (channel_id === 0x06 && channel_type === 0x83) {
+ decoded.power_consumption = readUInt32LE(bytes.slice(i, i + 4));
+ i += 4;
+ }
+ // Current Rating
+ else if (channel_id === 0x07 && channel_type === 0xc9) {
+ decoded.current = readUInt16LE(bytes.slice(i, i + 2));
+ i += 2;
+ }
+ // Over Current Alarm
+ else if (channel_id === 0x87 && channel_type === 0xc9) {
+ decoded.overcurrent_alarm = {};
+ decoded.overcurrent_alarm.current = readUInt16LE(bytes.slice(i, i + 2));
+ decoded.overcurrent_alarm.status = readOverCurrentStatus(bytes.slice(i + 2, i + 3))
+ i += 3;
+ }
+ // Device Abnormal Alarm
+ else if (channel_id === 0x88 && channel_type === 0x29) {
+ decoded.device_abnormal_alarm = {};
+ decoded.device_abnormal_alarm.status = readAlarmStatus(bytes[i]);
+ i += 1;
+ }
+ // Temperature Alarm
+ else if (channel_id === 0x89 && channel_type === 0xdf) {
+ decoded.temperature_alarm = {};
+ decoded.temperature_alarm.status = readTemperatureAlarmStatus(bytes.slice(i, i + 1));
+ i += 1;
+ }
+ // RULE CONFIG
+ else if (channel_id === 0xf9 && channel_type === 0x64) {
+ var schedule_settings = readScheduleSettings(bytes.slice(i, i + 7));
+ i += 7;
+
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ }
+ // DOWNLINK RESPONSE
+ else if (channel_id === 0xfe || channel_id === 0xff) {
+ var result = handle_downlink_response(channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else if (channel_id === 0xf8 || channel_id === 0xf9) {
+ var result = handle_downlink_response_ext(channel_id, channel_type, bytes, i);
+ decoded = Object.assign(decoded, result.data);
+ i = result.offset;
+ } else {
+ break;
+ }
+ }
+
+ return decoded;
+}
+
+function handle_downlink_response(channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x03:
+ decoded.reporting_interval = readUInt16LE(bytes.slice(offset, offset + 2));
+ offset += 2;
+ break;
+ case 0x10:
+ decoded.reboot = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x25:
+ var data = readUInt16LE(bytes.slice(offset, offset + 2));
+ decoded.button_lock_config = {};
+ decoded.button_lock_config.enable = readEnableStatus((data >>> 15) & 0x01);
+ offset += 2;
+ break;
+ case 0x28:
+ decoded.report_status = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x29:
+ var data = readUInt8(bytes[offset]);
+ var switch_bit_offset = { status_1: 0, status_2: 1, status_3: 2 };
+ decoded.switch_control = {};
+ for (var key in switch_bit_offset) {
+ if ((data >>> (switch_bit_offset[key] + 4)) & 0x01) {
+ decoded.switch_control[key] = readOnOffStatus(data >>> switch_bit_offset[key] & 0x01);
+ }
+ }
+ offset += 1;
+ break;
+ case 0x2c:
+ decoded.report_attribute = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0x2f:
+ decoded.led_mode = readLedMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x5e:
+ decoded.button_reset_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x26:
+ decoded.power_consumption_enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x27:
+ decoded.power_consumption_clear = readYesNoStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x24:
+ decoded.overcurrent_alarm_config = {};
+ decoded.overcurrent_alarm_config.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_alarm_config.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x30:
+ decoded.overcurrent_protection = {};
+ decoded.overcurrent_protection.enable = readEnableStatus(bytes[offset]);
+ decoded.overcurrent_protection.threshold = readUInt8(bytes.slice(offset + 1, offset + 2));
+ offset += 2;
+ break;
+ case 0x8d:
+ decoded.highcurrent_config = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x67:
+ decoded.power_switch_mode = readPowerSwitchMode(bytes[offset]);
+ offset += 1;
+ break;
+ case 0x4a:
+ decoded.time_synchronize = readYesNoStatus(1);
+ offset += 1;
+ break;
+ case 0xc7:
+ var d2d_enable_data = readUInt8(bytes[offset]);
+ offset += 1;
+
+ decoded.d2d_global_enable = {};
+ decoded.d2d_global_enable.master_enable = readEnableStatus((d2d_enable_data >>> 0) & 1);
+ decoded.d2d_global_enable.master_enable_change = readYesNoStatus((d2d_enable_data >>> 4) & 1);
+ decoded.d2d_global_enable.agent_enable = readEnableStatus((d2d_enable_data >>> 1) & 1);
+ decoded.d2d_global_enable.agent_enable_change = readYesNoStatus((d2d_enable_data >>> 5) & 1);
+ break;
+ case 0x83:
+ var d2d_agent_settings = readD2DAgentSettings(bytes.slice(offset, offset + 5));
+ offset += 5;
+ decoded.d2d_agent_settings_array = decoded.d2d_agent_settings_array || [];
+ decoded.d2d_agent_settings_array.push(d2d_agent_settings);
+ break;
+ case 0xbd:
+ decoded.time_zone = readTimeZone(readInt16LE(bytes.slice(offset, offset + 2)));
+ offset += 2;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function handle_downlink_response_ext(code, channel_type, bytes, offset) {
+ var decoded = {};
+
+ switch (channel_type) {
+ case 0x64:
+ var schedule_settings_result = readUInt8(bytes[offset + 7]);
+ if (schedule_settings_result === 0) {
+ var schedule_settings = readScheduleSettings(bytes.slice(offset, offset + 7));
+ decoded.schedule_settings = decoded.schedule_settings || [];
+ decoded.schedule_settings.push(schedule_settings);
+ } else {
+ decoded.schedule_settings_result = readScheduleSettingsResult(schedule_settings_result);
+ }
+ offset += 7;
+ break;
+ case 0x65:
+ var data = readUInt8(bytes[offset]);
+ decoded.get_local_rule = {};
+ if (data === 0xff) {
+ decoded.get_local_rule.task_id = 'all schedules';
+ } else {
+ decoded.get_local_rule.task_id = data;
+ }
+ offset += 1;
+ break;
+ case 0xaa:
+ decoded.anti_flash_mode = {};
+ decoded.anti_flash_mode.enable = readEnableStatus(bytes[offset]);
+ offset += 1;
+ break;
+ case 0xab:
+ decoded.load_power = {};
+ var power_bit_offset = [ "power_1", "power_2", "power_3" ];
+ for(var i= 0; i < power_bit_offset.length; i++) {
+ decoded.load_power[power_bit_offset[i]] = readUInt16LE(bytes.slice(offset + i * 2, offset + (i + 1) * 2));
+ }
+ offset += 6;
+ break;
+ case 0xa4:
+ decoded.lorawan_class_cfg = readLorawanClassCfg(bytes.slice(offset, offset + 8));
+ offset += 8;
+ break;
+ case 0xb8:
+ var keyid = readUInt8(bytes[offset]);
+ switch (keyid) {
+ case 0x00:
+ decoded.key1_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ break;
+ case 0x01:
+ decoded.key2_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ break;
+ case 0x02:
+ decoded.key3_d2d_controller_settings = readD2DControllerSettings(bytes.slice(offset, offset + 5));
+ break;
+ default:
+ throw new Error("unknown keyid");
+ }
+ offset += 5;
+ break;
+ case 0x72:
+ decoded.daylight_saving_time = readDstConfig(bytes.slice(offset, offset + 9));
+ offset += 9;
+ break;
+ default:
+ throw new Error("unknown downlink response");
+ }
+
+ if (hasResultFlag(code)) {
+ var result_value = readUInt8(bytes[offset]);
+ offset += 1;
+
+ if (result_value !== 0) {
+ var request = decoded;
+ decoded = {};
+ decoded.device_response_result = {};
+ decoded.device_response_result.channel_type = channel_type;
+ decoded.device_response_result.result = readResultStatus(result_value);
+ decoded.device_response_result.request = request;
+ }
+ }
+
+ return { data: decoded, offset: offset };
+}
+
+function readDstConfig(bytes) {
+ var offset = 0;
+
+ var data = bytes[offset];
+ var enable_value = (data >> 7) & 0x01;
+ var offset_value = data & 0x7f;
+
+ var daylight_saving_time = {};
+ daylight_saving_time.daylight_saving_time_enable = readEnableStatus(enable_value);
+ daylight_saving_time.daylight_saving_time_offset = offset_value;
+ daylight_saving_time.start_month = readUInt8(bytes[offset + 1]);
+ var start_week_value = readUInt8(bytes[offset + 2]);
+ daylight_saving_time.start_week_num = start_week_value >> 4;
+ daylight_saving_time.start_week_day = start_week_value & 0x0f;
+ daylight_saving_time.start_hour_min = readUInt16LE(bytes.slice(offset + 3, offset + 5));
+ daylight_saving_time.end_month = readUInt8(bytes[offset + 5]);
+ var end_week_value = readUInt8(bytes[offset + 6]);
+ daylight_saving_time.end_week_num = end_week_value >> 4;
+ daylight_saving_time.end_week_day = end_week_value & 0x0f;
+ daylight_saving_time.end_hour_min = readUInt16LE(bytes.slice(offset + 7, offset + 9));
+ offset += 9;
+
+ return daylight_saving_time;
+}
+
+function hasResultFlag(code) {
+ return code === 0xf8;
+}
+
+function readProtocolVersion(bytes) {
+ var major = (bytes & 0xf0) >> 4;
+ var minor = bytes & 0x0f;
+ return "v" + major + "." + minor;
+}
+
+function readResultStatus(status) {
+ var status_map = {
+ 0: "success",
+ 1: "forbidden",
+ 2: "out of range",
+ 16: "continue is 0",
+ 17: "The continue is greater than the maximum value allowed by the device",
+ 18: "Command expires (start time + continue <= current time)"
+ };
+ return getValue(status_map, status);
+}
+
+function readHardwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff) >> 4;
+ return "v" + major + "." + minor;
+}
+
+function readFirmwareVersion(bytes) {
+ var major = (bytes[0] & 0xff).toString(16);
+ var minor = (bytes[1] & 0xff).toString(16);
+ return "v" + major + "." + minor;
+}
+
+function readTslVersion(bytes) {
+ var major = bytes[0] & 0xff;
+ var minor = bytes[1] & 0xff;
+ return "v" + major + "." + minor;
+}
+
+function readSerialNumber(bytes) {
+ var temp = [];
+ for (var idx = 0; idx < bytes.length; idx++) {
+ temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2));
+ }
+ return temp.join("");
+}
+
+function readLoRaWANClass(type) {
+ var class_map = {
+ 1: "Class B",
+ 2: "Class C",
+ };
+ return getValue(class_map, type);
+}
+
+function readResetEvent(status) {
+ var status_map = { 0: "normal", 1: "reset" };
+ return getValue(status_map, status);
+}
+
+function readDeviceStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readOnOffStatus(status) {
+ var status_map = { 0: "off", 1: "on" };
+ return getValue(status_map, status);
+}
+
+function readYesNoStatus(status) {
+ var status_map = { 0: "no", 1: "yes" };
+ return getValue(status_map, status);
+}
+
+function readLedMode(type) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ return getValue(led_mode_map, type);
+}
+
+function readTemperatureAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "overtemperature" };
+ return getValue(status_map, status);
+}
+
+function readAlarmStatus(status) {
+ var status_map = { 0: "normal", 1: "abnormal" };
+ return getValue(status_map, status);
+}
+
+function readOverCurrentStatus(status) {
+ var status_map = { 0: "normal", 1: "overcurrent" };
+ return getValue(status_map, status);
+}
+
+function readScheduleSettings(bytes) {
+ var offset = 0;
+
+ var schedule_settings = {};
+ schedule_settings.channel = readUInt8(bytes[offset]);
+ schedule_settings.enable = readEnableStatus2(bytes[offset + 1] & 0x03);
+ schedule_settings.use_config = readYesNoStatus(bytes[offset + 1] >> 4 & 0x01);
+ // condition
+ var day_bit_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var day = readUInt8(bytes[offset + 2]);
+ for (var key in day_bit_offset) {
+ schedule_settings[key] = readEnableStatus((day >> day_bit_offset[key]) & 0x01);
+ }
+ schedule_settings.execut_hour = readUInt8(bytes[offset + 3]);
+ schedule_settings.execut_min = readUInt8(bytes[offset + 4]);
+
+ // action
+ var switch_bit_offset = { switch_1_state: 0, switch_2_state: 2, switch_3_state: 4 };
+ var switch_raw_data = readUInt8(bytes[offset + 5]);
+ for (var key in switch_bit_offset) {
+ schedule_settings[key] = readSwitchStatus((switch_raw_data >> switch_bit_offset[key]) & 0x03);
+ }
+ schedule_settings.lock_state = readChildLockStatus(bytes[offset + 6]);
+
+ return schedule_settings;
+}
+
+function readScheduleSettingsResult(result) {
+ var schedule_settings_result_map = {
+ 0: "success",
+ 2: "failed, out of range",
+ 17: "success, conflict with channel=1",
+ 18: "success, conflict with channel=2",
+ 19: "success, conflict with channel=3",
+ 20: "success, conflict with channel=4",
+ 21: "success, conflict with channel=5",
+ 22: "success, conflict with channel=6",
+ 23: "success, conflict with channel=7",
+ 24: "success, conflict with channel=8",
+ 25: "success, conflict with channel=9",
+ 26: "success, conflict with channel=10",
+ 27: "success, conflict with channel=11",
+ 28: "success, conflict with channel=12",
+ 29: "success, conflict with channel=13",
+ 30: "success, conflict with channel=14",
+ 31: "success, conflict with channel=15",
+ 32: "success, conflict with channel=16",
+ 49: "failed, conflict with channel=1",
+ 50: "failed, conflict with channel=2",
+ 51: "failed, conflict with channel=3",
+ 52: "failed, conflict with channel=4",
+ 53: "failed, conflict with channel=5",
+ 54: "failed, conflict with channel=6",
+ 55: "failed, conflict with channel=7",
+ 56: "failed, conflict with channel=8",
+ 57: "failed, conflict with channel=9",
+ 58: "failed, conflict with channel=10",
+ 59: "failed, conflict with channel=11",
+ 60: "failed, conflict with channel=12",
+ 61: "failed, conflict with channel=13",
+ 62: "failed, conflict with channel=14",
+ 63: "failed, conflict with channel=15",
+ 64: "failed, conflict with channel=16",
+ 81: "failed,rule config empty",
+ };
+ return getValue(schedule_settings_result_map, result);
+}
+
+function readD2DCommand(bytes) {
+ return ("0" + (bytes[1] & 0xff).toString(16)).slice(-2) + ("0" + (bytes[0] & 0xff).toString(16)).slice(-2);
+}
+
+function readSwitchObject(switchs) {
+ var switch_object_map = {
+ 1: "switch1",
+ 2: "switch2",
+ 3: "switch1, switch2",
+ 4: "switch3",
+ 5: "switch1, switch3",
+ 6: "switch2, switch3",
+ 7: "switch1, switch2, switch3"
+ };
+ return getValue(switch_object_map, switchs);
+}
+
+function readActionSwitchStatus(status) {
+ var switch_status_map = {
+ 0: "off",
+ 1: "on",
+ 2: "reversel"
+ };
+ return getValue(switch_status_map, status);
+}
+
+function readD2DAgentSettings(bytes) {
+ var offset = 0;
+
+ var d2d_agent_settings = {};
+ d2d_agent_settings.d2d_agent_id = readUInt8(bytes[offset]);
+ d2d_agent_settings.d2d_agent_enable = readEnableStatus(bytes[offset + 1] & 0x01);
+ d2d_agent_settings.d2d_agent_command = readD2DCommand(bytes.slice(offset + 2, offset + 4));
+
+ d2d_agent_settings.d2d_agent_action = {};
+ var switchs = bytes[offset + 4] >> 4;
+ var status = bytes[offset + 4] & 0x0f;
+ d2d_agent_settings.d2d_agent_action.switch_object = readSwitchObject(switchs);
+ d2d_agent_settings.d2d_agent_action.switch_status = readActionSwitchStatus(status);
+
+ return d2d_agent_settings;
+}
+
+function readLorawanClassCfg(bytes) {
+ var offset = 0;
+
+ var lorawan_class_cfg = {};
+ lorawan_class_cfg.timestamp = readUInt32LE(bytes.slice(offset, offset + 4));
+ lorawan_class_cfg.continue = readUInt16LE(bytes.slice(offset + 4, offset + 6));
+ lorawan_class_cfg.class_mode = readClassMode(readUInt8(bytes.slice(offset + 6, offset + 7)));
+
+ return lorawan_class_cfg;
+}
+
+function readD2DControllerSettings(bytes) {
+ var offset = 0;
+
+ var d2d_controller_settings = {};
+ var keyid = readUInt8(bytes[offset]);
+ if(keyid !== 0 && keyid !== 1 && keyid !== 2) {
+ return d2d_controller_settings;
+ }
+ d2d_controller_settings.key_contrl_enable = readEnableStatus(bytes[offset + 1]);
+ d2d_controller_settings.uplink = {};
+ d2d_controller_settings.uplink.lora_enable = readEnableStatus(bytes[offset + 2] & 0x01);
+ d2d_controller_settings.uplink.key_enable = readEnableStatus(bytes[offset + 2] >> 1 & 0x01);
+ d2d_controller_settings.contrl_cmd = readD2DCommand(bytes.slice(offset + 3, offset + 5));
+
+ return d2d_controller_settings;
+}
+
+function readSwitchStatus(status) {
+ var switch_status_map = { 0: "keep", 1: "on", 2: "off", 3: "reversal" };
+ return getValue(switch_status_map, status);
+}
+
+function readChildLockStatus(status) {
+ var child_lock_status_map = { 0: "keep", 1: "lock", 2: "unlock" };
+ return getValue(child_lock_status_map, status);
+}
+
+function readTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ return getValue(timezone_map, time_zone);
+}
+
+function readEnableStatus(status) {
+ var status_map = { 0: "disable", 1: "enable" };
+ return getValue(status_map, status);
+}
+
+function readEnableStatus2(status) {
+ var status_map = { 1: "enable", 2: "disable" };
+ return getValue(status_map, status);
+}
+
+function readPowerSwitchMode(mode) {
+ var mode_map = { 0: "off", 1: "on", 2: "keep" };
+ return getValue(mode_map, mode);
+}
+
+function readEnableMask(mask) {
+ var mask_map = { 0: "keep", 1: "set" };
+ return getValue(mask_map, mask);
+}
+
+function readClassMode(mode) {
+ var mode_map = { 1: "CLASS_B", 2: "CLASS_C" };
+ return getValue(mode_map, mode);
+}
+
+function readMonth(month) {
+ var month_map = {
+ 1:"Jan.",
+ 2:"Feb.",
+ 3: "Mar.",
+ 4: "Apr.",
+ 5: "May.",
+ 6: "Jun.",
+ 7: "Jul.",
+ 8: "Aug.",
+ 9: "Sep.",
+ 10: "Oct.",
+ 11: "Nov.",
+ 12: "Dec.",
+ };
+ return getValue(month_map, month);
+}
+
+function readWeek(week) {
+ var weeks_map = {
+ 1: "1st",
+ 2: "2nd",
+ 3: "3rd",
+ 4: "4th",
+ 5: "last"
+ };
+ return getValue(weeks_map, week);
+}
+
+function readWeekDay(day) {
+ var week_map = {
+ 1: "Mon.",
+ 2: "Tues.",
+ 3: "Wed.",
+ 4: "Thurs.",
+ 5: "Fri.",
+ 6: "Sat.",
+ 7: "Sun."
+ };
+ return getValue(week_map, day);
+}
+
+function readKeyNum(key_num) {
+ var key_map = { 0: "key1", 1: "key2", 2: "key3" };
+ return getValue(key_map, key_num);
+}
+
+/* eslint-disable */
+function readUInt8(bytes) {
+ return bytes & 0xff;
+}
+
+function readInt8(bytes) {
+ var ref = readUInt8(bytes);
+ return ref > 0x7f ? ref - 0x100 : ref;
+}
+
+function readUInt16LE(bytes) {
+ var value = (bytes[1] << 8) + bytes[0];
+ return value & 0xffff;
+}
+
+function readInt16LE(bytes) {
+ var ref = readUInt16LE(bytes);
+ return ref > 0x7fff ? ref - 0x10000 : ref;
+}
+
+function readUInt32LE(bytes) {
+ var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
+ return (value & 0xffffffff) >>> 0;
+}
+
+function readInt32LE(bytes) {
+ var ref = readUInt32LE(bytes);
+ return ref > 0x7fffffff ? ref - 0x100000000 : ref;
+}
+
+function getValue(map, key) {
+ if (RAW_VALUE) return key;
+
+ var value = map[key];
+ if (!value) value = "unknown";
+ return value;
+}
+
+if (!Object.assign) {
+ Object.defineProperty(Object, "assign", {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function (target) {
+ "use strict";
+ if (target == null) {
+ throw new TypeError("Cannot convert first argument to object");
+ }
+
+ var to = Object(target);
+ for (var i = 1; i < arguments.length; i++) {
+ var nextSource = arguments[i];
+ if (nextSource == null) {
+ continue;
+ }
+ nextSource = Object(nextSource);
+
+ var keysArray = Object.keys(Object(nextSource));
+ for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+ var nextKey = keysArray[nextIndex];
+ var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+ if (desc !== undefined && desc.enumerable) {
+ // concat array
+ if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {
+ to[nextKey] = to[nextKey].concat(nextSource[nextKey]);
+ } else {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ },
+ });
+}
diff --git a/ws-series/ws503-v4/ws503-v4-encoder.js b/ws-series/ws503-v4/ws503-v4-encoder.js
new file mode 100644
index 00000000..d890f17e
--- /dev/null
+++ b/ws-series/ws503-v4/ws503-v4-encoder.js
@@ -0,0 +1,940 @@
+/**
+ * Payload Encoder
+ *
+ * Copyright 2025 Milesight IoT
+ *
+ * @product WS503
+ */
+var RAW_VALUE = 0x00;
+
+/* eslint no-redeclare: "off" */
+/* eslint-disable */
+// Chirpstack v4
+function encodeDownlink(input) {
+ var encoded = milesightDeviceEncode(input.data);
+ return { bytes: encoded };
+}
+
+// Chirpstack v3
+function Encode(fPort, obj) {
+ return milesightDeviceEncode(obj);
+}
+
+// The Things Network
+function Encoder(obj, port) {
+ return milesightDeviceEncode(obj);
+}
+/* eslint-enable */
+
+function milesightDeviceEncode(payload) {
+ var encoded = [];
+
+ if ("reboot" in payload) {
+ encoded = encoded.concat(reboot(payload.reboot));
+ }
+ if ("reporting_interval" in payload) {
+ encoded = encoded.concat(setReportingInterval(payload.reporting_interval));
+ }
+ if ("report_status" in payload) {
+ encoded = encoded.concat(reportStatus(payload.report_status));
+ }
+ if ("report_attribute" in payload) {
+ encoded = encoded.concat(reportAttribute(payload.report_attribute));
+ }
+ if ("led_mode" in payload) {
+ encoded = encoded.concat(setLedMode(payload.led_mode));
+ }
+ if ("button_lock_config" in payload) {
+ encoded = encoded.concat(setButtonLockConfig(payload.button_lock_config));
+ }
+ if("switch_control" in payload) {
+ encoded = encoded.concat(setSwitchControl(payload.switch_control));
+ }
+ if("button_reset_config" in payload) {
+ encoded = encoded.concat(setButtonResetConfig(payload.button_reset_config));
+ }
+ if("power_consumption_enable" in payload) {
+ encoded = encoded.concat(setPowerConsumptionEnable(payload.power_consumption_enable));
+ }
+ if("load_power" in payload) {
+ encoded = encoded.concat(setLoadPower(payload.load_power));
+ }
+ if("power_consumption_clear" in payload) {
+ encoded = encoded.concat(setPowerConsumptionClear(payload.power_consumption_clear));
+ }
+ if("schedule_settings" in payload) {
+ for (var schedule_index = 0; schedule_index < payload.schedule_settings.length; schedule_index++) {
+ var schedule = payload.schedule_settings[schedule_index];
+ encoded = encoded.concat(setScheduleSettings(schedule));
+ }
+ }
+ if("get_local_rule" in payload) {
+ encoded = encoded.concat(setGetLocalRule(payload.get_local_rule));
+ }
+ if("anti_flash_mode" in payload) {
+ encoded = encoded.concat(setAntiFlashMode(payload.anti_flash_mode));
+ }
+ if("overcurrent_alarm_config" in payload) {
+ encoded = encoded.concat(setOvercurrentAlarmConfig(payload.overcurrent_alarm_config));
+ }
+ if("overcurrent_protection" in payload) {
+ encoded = encoded.concat(setOvercurrentProtection(payload.overcurrent_protection));
+ }
+ if("highcurrent_config" in payload) {
+ encoded = encoded.concat(setHighcurrentConfig(payload.highcurrent_config));
+ }
+ if("power_switch_mode" in payload) {
+ encoded = encoded.concat(setPowerSwitchMode(payload.power_switch_mode));
+ }
+ if("lorawan_class_cfg" in payload) {
+ encoded = encoded.concat(setLorawanClassCfg(payload.lorawan_class_cfg));
+ }
+ if("time_synchronize" in payload) {
+ encoded = encoded.concat(setTimeSynchronize(payload.time_synchronize));
+ }
+ if("d2d_global_enable" in payload) {
+ encoded = encoded.concat(setD2DGlobalEnable(payload.d2d_global_enable));
+ }
+ if("d2d_agent_settings_array" in payload) {
+ for (var agent_index = 0; agent_index < payload.d2d_agent_settings_array.length; agent_index++) {
+ var agent = payload.d2d_agent_settings_array[agent_index];
+ encoded = encoded.concat(setD2DAgentSettings(agent));
+ }
+ }
+ if("key1_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key1_d2d_controller_settings, 0x00));
+ }
+ if("key2_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key2_d2d_controller_settings, 0x01));
+ }
+ if("key3_d2d_controller_settings" in payload) {
+ encoded = encoded.concat(setD2DControllerSettings(payload.key3_d2d_controller_settings, 0x02));
+ }
+ if("time_zone" in payload) {
+ encoded = encoded.concat(setTimeZone(payload.time_zone));
+ }
+ if("daylight_saving_time" in payload) {
+ encoded = encoded.concat(setDaylightSavingTime(payload.daylight_saving_time));
+ }
+
+ return encoded;
+}
+
+/**
+ * reboot
+ * @param {number} reboot values: (0: "no", 1: "yes")
+ * @example { "reboot": 1 }
+ */
+function reboot(reboot) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(reboot) === -1) {
+ throw new Error("reboot must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, reboot) === 0) {
+ return [];
+ }
+ return [0xff, 0x10, 0xff];
+}
+
+/**
+ * report interval configuration
+ * @param {number} reporting_interval uint: second, range: [60, 64800]
+ * @example { "reporting_interval": 1200 }
+ */
+function setReportingInterval(reporting_interval) {
+ if (typeof reporting_interval !== "number") {
+ throw new Error("reporting_interval must be a number");
+ }
+ if (reporting_interval < 60 || reporting_interval > 64800) {
+ throw new Error("reporting_interval must be in the range of [60, 64800]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x03);
+ buffer.writeUInt16LE(reporting_interval);
+ return buffer.toBytes();
+}
+
+/**
+ * report status
+ * @param {number} report_status values: (0: "no", 1: "yes")
+ * @example { "report_status": 1 }
+ */
+function reportStatus(report_status) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_status) === -1) {
+ throw new Error("report_status must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_status) === 0) {
+ return [];
+ }
+ return [0xff, 0x28, 0xff];
+}
+
+/**
+ * report attribute
+ * @param {number} report_attribute values: (0: "no", 1: "yes")
+ * @example { "report_attribute": 1 }
+ */
+function reportAttribute(report_attribute) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(report_attribute) === -1) {
+ throw new Error("report_attribute must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, report_attribute) === 0) {
+ return [];
+ }
+ return [0xff, 0x2c, 0xff];
+}
+
+/**
+ * set led mode
+ * @param {number} led_mode, values: (0: "disable", 1: "Enable (relay closed indicator off)")
+ * @example { "led_mode": 1 }
+ */
+function setLedMode(led_mode) {
+ var led_mode_map = { 0: "disable", 1: "Enable (relay closed indicator off)" };
+ var led_mode_values = getValues(led_mode_map);
+ if (led_mode_values.indexOf(led_mode) === -1) {
+ throw new Error("led_mode must be one of: " + led_mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x2f);
+ buffer.writeUInt8(getValue(led_mode_map, led_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * button lock configuration
+ * @param {object} button_lock_config
+ * @param {number} button_lock_config.enable values: (0: "disable", 1: "enable")
+ * @example { "button_lock_config": { "enable": 1 } }
+ */
+function setButtonLockConfig(button_lock_config) {
+ var enable = button_lock_config.enable;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("button_lock_config.enable must be one of: " + enable_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 15;
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x25);
+ buffer.writeUInt16LE(data);
+ return buffer.toBytes();
+}
+
+/**
+ * switch control
+ * @param {object} switch_control
+ * @param {number} switch_control.status_1 values: (0: "off", 1: "on")
+ * @param {number} switch_control.status_2 values: (0: "off", 1: "on")
+ * @param {number} switch_control.status_3 values: (0: "off", 1: "on")
+ * @example { "switch_control": { "status_1": 1, "status_2": 1, "status_3": 1 } }
+ */
+function setSwitchControl(switch_control) {
+ var status_map = {0: "off", 1: "on"};
+ var status_values = getValues(status_map);
+
+ var data = 0x00;
+ var switch_bit_offset = { status_1: 0, status_2: 1, status_3: 2 };
+ for (var key in switch_bit_offset) {
+ if (key in switch_control) {
+ if (status_values.indexOf(switch_control[key]) === -1) {
+ throw new Error("switch_control." + key + " must be one of: " + status_values.join(", "));
+ }
+
+ data |= 1 << (switch_bit_offset[key] + 4);
+ data |= getValue(status_map, switch_control[key]) << switch_bit_offset[key];
+ }
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x29);
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * button reset configuration
+ * @param {number} button_reset_config values: (0: "disable", 1: "enable")
+ * @example { "button_reset_config": 1 }
+ */
+function setButtonResetConfig(button_reset_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(button_reset_config) === -1) {
+ throw new Error("button_reset_config must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x5e, getValue(enable_map, button_reset_config)];
+}
+
+/**
+ * power consumption enable
+ * @param {number} power_consumption_enable values: (0: "disable", 1: "enable")
+ * @example { "power_consumption_enable": 1 }
+ */
+function setPowerConsumptionEnable(power_consumption_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(power_consumption_enable) === -1) {
+ throw new Error("power_consumption_enable must be one of: " + enable_values.join(", "));
+ }
+
+ return [0xff, 0x26, getValue(enable_map, power_consumption_enable)];
+}
+
+/**
+ * load power
+ * @param {object} load_power
+ * @param {number} load_power.power_1
+ * @param {number} load_power.power_2
+ * @param {number} load_power.power_3
+ * @example { "load_power": {"power_1": 0, "power_2": 0, "power_3": 1100 } }
+ */
+function setLoadPower(load_power) {
+ var powers = [ "power_1", "power_2", "power_3" ];
+ var buffer = new Buffer(8);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xab);
+ for(var i = 0; i < powers.length; i++) {
+ if (typeof load_power[powers[i]] !== "number") {
+ throw new Error("load_power." + powers[i] + " must be a number");
+ }
+ if (load_power[powers[i]] < 0 || load_power[powers[i]] > 1100) {
+ throw new Error("load_power." + powers[i] + " must be in the range of [0, 1100]");
+ }
+ buffer.writeUInt16LE(load_power[powers[i]]);
+ }
+
+ return buffer.toBytes();
+}
+
+/**
+ * power consumption clear
+ * @param {number} power_consumption_clear values: (0: "no", 1: "yes")
+ * @example { "power_consumption_clear": 1 }
+ */
+function setPowerConsumptionClear(power_consumption_clear) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(power_consumption_clear) === -1) {
+ throw new Error("power_consumption_clear must be one of: " + yes_no_values.join(", "));
+ }
+
+ if (getValue(yes_no_map, power_consumption_clear) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x27, 0x01];
+}
+
+/**
+ * set schedule settings
+ * @param {object} schedule_settings
+ * @param {number} schedule_settings.channel range: [1, 16]
+ * @param {number} schedule_settings.enable values: (1: "enable", 2: "disable")
+ * @param {number} schedule_settings.use_config values: (0: "no", 1: "yes")
+ * @param {number} schedule_settings.monday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.tuesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.wednesday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.thursday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.friday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.saturday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.sunday values: (0: "disable", 1: "enable")
+ * @param {number} schedule_settings.execut_hour range: [0, 23]
+ * @param {number} schedule_settings.execut_min range: [0, 59]
+ * @param {number} schedule_settings.switch_1_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.switch_2_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.switch_3_state values: (0: "keep", 1: "on", 2: "off", 3: "reversal")
+ * @param {number} schedule_settings.lock_state values: (0: "keep", 1: "lock", 2: "unlock")
+ * @example { "schedule_settings": [{ "channel": 1, "enable": 1, "use_config": 1, "monday": 1, "tuesday": 0, "wednesday": 1, "thursday": 0, "friday": 1, "saturday": 0, "sunday": 1, "execut_hour": 10, "execut_min": 5, "switch_1_state": 1, "switch_2_state": 1, "switch_3_state": 1, "lock_state": 1 }] }
+ */
+function setScheduleSettings(schedule) {
+ var channel = schedule.channel;
+ var enable = schedule.enable;
+ var use_config = schedule.use_config;
+ var lock_state = schedule.lock_state;
+ var execut_hour = schedule.execut_hour;
+ var execut_min = schedule.execut_min;
+
+ if (typeof channel !== "number") {
+ throw new Error("schedule_settings._item.channel must be a number");
+ }
+ if (channel < 1 || channel > 16) {
+ throw new Error("schedule_settings._item.channel must be in range [1, 16]");
+ }
+ var enable_map = { 1: "enable", 2: "disable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("schedule_settings._item.enable must be one of " + enable_values.join(", "));
+ }
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(use_config) === -1) {
+ throw new Error("schedule_settings._item.use_config must be one of " + yes_no_values.join(", "));
+ }
+ var week_day_bits_offset = { monday: 0, tuesday: 1, wednesday: 2, thursday: 3, friday: 4, saturday: 5, sunday: 6 };
+ var days = 0x00;
+ var enable_map_2 = { 0: "disable", 1: "enable" };
+ var enable_values_2 = getValues(enable_map_2);
+ for (var day in week_day_bits_offset) {
+ if (enable_values_2.indexOf(schedule[day]) === -1) {
+ throw new Error("schedule_settings._item." + day + " must be one of " + enable_values_2.join(", "));
+ }
+ days |= getValue(enable_map_2, schedule[day]) << week_day_bits_offset[day];
+ }
+ var switch_bits_offset = { switch_1_state: 0, switch_2_state: 2, switch_3_state: 4 };
+ var switch_state_map = {0: "keep", 1: "on", 2: "off", 3: "reversal"};
+ var switch_state_values = getValues(switch_state_map);
+ var switchs = 0x00;
+ for (var switch_state in switch_bits_offset) {
+ if (switch_state_values.indexOf(schedule[switch_state]) === -1) {
+ throw new Error("schedule_settings._item." + switch_state + " must be one of " + switch_state_values.join(", "));
+ }
+ switchs |= getValue(switch_state_map, schedule[switch_state]) << switch_bits_offset[switch_state];
+ }
+ var lock_state_map = {0: "keep", 1: "lock", 2: "unlock"};
+ var lock_state_values = getValues(lock_state_map);
+ if (lock_state_values.indexOf(lock_state) === -1) {
+ throw new Error("schedule_settings._item.lock_state must be one of: " + lock_state_values.join(", "));
+ }
+ if(typeof execut_hour !== "number") {
+ throw new Error("schedule_settings._item.execut_hour must be a number");
+ }
+ if(execut_hour < 0 || execut_hour > 23) {
+ throw new Error("schedule_settings._item.execut_hour must be in range [0, 23]");
+ }
+ if(typeof execut_min !== "number") {
+ throw new Error("schedule_settings._item.execut_min must be a number");
+ }
+ if(execut_min < 0 || execut_min > 59) {
+ throw new Error("schedule_settings._item.execut_min must be in range [0, 59]");
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x64);
+ buffer.writeUInt8(channel);
+ var schedule_option = 0x00;
+ schedule_option |= getValue(enable_map, enable);
+ schedule_option |= getValue(yes_no_map, use_config) << 4;
+ buffer.writeUInt8(schedule_option);
+ buffer.writeUInt8(days);
+ buffer.writeUInt8(execut_hour);
+ buffer.writeUInt8(execut_min);
+ buffer.writeUInt8(switchs);
+ buffer.writeUInt8(getValue(lock_state_map, lock_state));
+ return buffer.toBytes();
+}
+
+/**
+ * get local rule
+ * @param {object} get_local_rule
+ * @param {number | string} get_local_rule.task_id range: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"]
+ * @example { "get_local_rule": {"task_id": 1} }
+ */
+function setGetLocalRule(get_local_rule) {
+ var task_id = get_local_rule.task_id;
+ var task_id_values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,"all schedules"];
+
+ if(task_id_values.indexOf(task_id) === -1) {
+ throw new Error("get_local_rule.task_id must be one of: " + task_id_values.join(", "));
+ }
+
+ var data = (task_id === "all schedules") ? 0xff : (task_id & 0xff);
+ return [0xf9, 0x65, data];
+}
+
+/**
+ * Anti-flash mode
+ * @param {object} anti_flash_mode
+ * @param {number} anti_flash_mode.enable values: (0: "disable", 1: "enable")
+ * @example { "anti_flash_mode": {"enable": 1} }
+ */
+function setAntiFlashMode(anti_flash_mode) {
+ var enable = anti_flash_mode.enable;
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xaa);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent alarm configuration
+ * @param {object} overcurrent_alarm_config
+ * @param {number} overcurrent_alarm_config.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_alarm_config.threshold range: [1, 10]
+ * @example { "overcurrent_alarm_config": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentAlarmConfig(overcurrent_alarm_config) {
+ var enable = overcurrent_alarm_config.enable;
+ var threshold = overcurrent_alarm_config.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("anti_flash_mode.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_alarm_config.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_alarm_config.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x24);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * overcurrent protection
+ * @param {object} overcurrent_protection
+ * @param {number} overcurrent_protection.enable values: (0: "disable", 1: "enable")
+ * @param {number} overcurrent_protection.threshold range: [1, 10]
+ * @example { "overcurrent_protection": {"enable": 1, "threshold": 10} }
+ */
+function setOvercurrentProtection(overcurrent_protection) {
+ var enable = overcurrent_protection.enable;
+ var threshold = overcurrent_protection.threshold;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("overcurrent_protection.enable must be one of " + enable_values.join(", "));
+ }
+ if(typeof threshold !== "number") {
+ throw new Error("overcurrent_protection.threshold must be a number");
+ }
+ if(threshold < 1 || threshold > 10) {
+ throw new Error("overcurrent_protection.threshold must be in range [1, 10]");
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x30);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ buffer.writeUInt8(threshold);
+ return buffer.toBytes();
+}
+
+/**
+ * highcurrent configuration
+ * @param {number} highcurrent_config values: (0: "disable", 1: "enable")
+ * @example { "highcurrent_config": 1 }
+ */
+function setHighcurrentConfig(highcurrent_config) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(highcurrent_config) === -1) {
+ throw new Error("highcurrent_config must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x8d);
+ buffer.writeUInt8(getValue(enable_map, highcurrent_config));
+ return buffer.toBytes();
+}
+
+/**
+ * power switch mode
+ * @param {number} power_switch_mode values: (0: "off", 1: "on", 2: "keep")
+ * @example { "power_switch_mode": 1 }
+ */
+function setPowerSwitchMode(power_switch_mode) {
+ var mode_map = {0: "off", 1: "on", 2: "keep"};
+ var mode_values = getValues(mode_map);
+ if (mode_values.indexOf(power_switch_mode) === -1) {
+ throw new Error("power_switch_mode must be one of " + mode_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x67);
+ buffer.writeUInt8(getValue(mode_map, power_switch_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * class mode
+ * @param {object} lorawan_class_cfg
+ * @param {number} lorawan_class_cfg.timestamp
+ * @param {number} lorawan_class_cfg.continue
+ * @param {number} lorawan_class_cfg.class_mode values: ("CLASS_B", 2: "CLASS_C")
+ * @example { "lorawan_class_cfg": {"timestamp": 1758768956, "continue": 600, "class_mode": 1} }
+ */
+function setLorawanClassCfg(lorawan_class_cfg) {
+ var class_mode = lorawan_class_cfg.class_mode;
+ var class_map = {1: "CLASS_B", 2: "CLASS_C"};
+ var class_values = getValues(class_map);
+ if (class_values.indexOf(class_mode) === -1) {
+ throw new Error("lorawan_class_cfg.class_mode must be one of " + class_values.join(", "));
+ }
+
+ var buffer = new Buffer(10);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xa4);
+ buffer.writeUInt32LE(lorawan_class_cfg.timestamp);
+ buffer.writeUInt16LE(lorawan_class_cfg.continue);
+ buffer.writeUInt8(getValue(class_map, class_mode));
+ return buffer.toBytes();
+}
+
+/**
+ * Time Synchronize
+ * @param {number} time_synchronize values: (0: "no", 1: "yes")
+ * @example { "time_synchronize": 0 }
+ */
+function setTimeSynchronize(time_synchronize) {
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ if (yes_no_values.indexOf(time_synchronize) === -1) {
+ throw new Error("time_synchronize must be one of: " + yes_no_values.join(", "));
+ }
+ if (getValue(yes_no_map, time_synchronize) === 0) {
+ return [];
+ }
+
+ return [0xff, 0x4a, 0x00];
+}
+
+/**
+ * D2D Settings
+ * @param {object, object} d2d_global_enable
+ * @param {number} d2d_global_enable.master_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.master_enable_change values: (0: "no", 1: "yes")
+ * @param {number} d2d_global_enable.agent_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_global_enable.agent_enable_change values: (0: "no", 1: "yes")
+ * @example { "d2d_global_enable": {"master_enable": 0, "master_enable_change": 0, "agent_enable": 0, "agent_enable_change": 0} }
+ */
+function setD2DGlobalEnable(d2d_global_enable) {
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ var yes_no_map = { 0: "no", 1: "yes" };
+ var yes_no_values = getValues(yes_no_map);
+ var master_enable = d2d_global_enable.master_enable;
+ var agent_enable = d2d_global_enable.agent_enable;
+ var master_enable_change = d2d_global_enable.master_enable_change;
+ var agent_enable_change = d2d_global_enable.agent_enable_change;
+
+ if (enable_values.indexOf(master_enable) === -1) {
+ throw new Error("d2d_global_enable.master_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(agent_enable) === -1) {
+ throw new Error("d2d_global_enable.agent_enable must be one of " + enable_values.join(", "));
+ }
+ if (yes_no_values.indexOf(master_enable_change) === -1) {
+ throw new Error("d2d_global_enable.master_enable_change must be one of " + yes_no_values.join(", "));
+ }
+ if (yes_no_values.indexOf(agent_enable_change) === -1) {
+ throw new Error("d2d_global_enable.agent_enable_change must be one of " + yes_no_values.join(", "));
+ }
+
+ var buffer = new Buffer(3);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xc7);
+ var data = 0x00;
+ data |= getValue(enable_map, master_enable);
+ data |= getValue(yes_no_map, master_enable_change) << 4;
+ data |= getValue(enable_map, agent_enable) << 1;
+ data |= getValue(yes_no_map, agent_enable_change) << 5;
+
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Agent Settings
+ * @param {object} d2d_agent_settings_array
+ * @param {number} d2d_agent_settings_array.d2d_agent_id range: [0, 15]
+ * @param {number} d2d_agent_settings_array.d2d_agent_enable values: (0: "disable", 1: "enable")
+ * @param {string} d2d_agent_settings_array.d2d_agent_command
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_object values: (1: "switch1", 2: "switch2", 3: "switch1, switch2", 4: "switch3", 5: "switch1, switch3", 6: "switch2, switch3", 7: "switch1, switch2, switch3")
+ * @param {number} d2d_agent_settings_array.d2d_agent_action.switch_status values: (0: "off", 1: "on", 2: "reversel")
+ * @example { "d2d_agent_settings_array": [{"d2d_agent_id": 0, "d2d_agent_enable": 1, "d2d_agent_command": "0000", "d2d_agent_action": {"switch_object": 1, "switch_status": 1}}] }
+ */
+function setD2DAgentSettings(d2d_agent_settings) {
+ var d2d_agent_id = d2d_agent_settings.d2d_agent_id;
+ var d2d_agent_enable = d2d_agent_settings.d2d_agent_enable;
+ var d2d_agent_command = d2d_agent_settings.d2d_agent_command;
+ var switch_object = d2d_agent_settings.d2d_agent_action.switch_object;
+ var switch_status = d2d_agent_settings.d2d_agent_action.switch_status;
+
+ if (typeof d2d_agent_id !== "number") {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be a number");
+ }
+ if (d2d_agent_id < 0 || d2d_agent_id > 15) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_id must be in range [0, 15]");
+ }
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(d2d_agent_enable) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_enable must be one of " + enable_values.join(", "));
+ }
+ var switch_map = {1: "switch1", 2: "switch2", 3: "switch1, switch2", 4: "switch3", 5: "switch1, switch3", 6: "switch2, switch3", 7: "switch1, switch2, switch3"};
+ var switch_values = getValues(switch_map);
+ if (switch_values.indexOf(switch_object) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_object must be one of " + switch_values.join(", "));
+ }
+ var switch_status_map = {0: "off", 1: "on", 2: "reversel"};
+ var switch_status_values = getValues(switch_status_map);
+ if (switch_status_values.indexOf(switch_status) === -1) {
+ throw new Error("d2d_agent_settings_array._item.d2d_agent_action.switch_status must be one of " + switch_status_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0x83);
+ buffer.writeUInt8(d2d_agent_id);
+ buffer.writeUInt8(getValue(enable_map, d2d_agent_enable));
+ buffer.writeD2DCommand(d2d_agent_command, "0000");
+ var data = 0x00;
+ data |= getValue(switch_status_map, switch_status);
+ data |= getValue(switch_map, switch_object) << 4;
+ buffer.writeUInt8(data);
+ return buffer.toBytes();
+}
+
+/**
+ * D2D Controller Settings
+ * @param {object} d2d_controller_settings
+ * @param {number} d2d_controller_settings.key_contrl_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_controller_settings.uplink.lora_enable values: (0: "disable", 1: "enable")
+ * @param {number} d2d_controller_settings.uplink.key_enable values: (0: "disable", 1: "enable")
+ * @param {string} d2d_controller_settings.contrl_cmd
+ * @param {number} id values: (0x00, 0x01, 0x02)
+ * @example { "d2d_controller_settings": [{"key_contrl_enable": 1, "uplink": { "lora_enable": 1, "key_enable": 1 }, "contrl_cmd": "0000"}] }
+ */
+function setD2DControllerSettings(d2d_controller_settings, id) {
+ var enable = d2d_controller_settings.key_contrl_enable;
+ var lora_enable = d2d_controller_settings.uplink.lora_enable;
+ var key_enable = d2d_controller_settings.uplink.key_enable;
+ var contrl_cmd = d2d_controller_settings.contrl_cmd;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("d2d_controller_settings.key_contrl_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(lora_enable) === -1) {
+ throw new Error("d2d_controller_settings.uplink.lora_enable must be one of " + enable_values.join(", "));
+ }
+ if (enable_values.indexOf(key_enable) === -1) {
+ throw new Error("d2d_controller_settings.uplink.key_enable must be one of " + enable_values.join(", "));
+ }
+
+ var buffer = new Buffer(7);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0xb8);
+ buffer.writeUInt8(id);
+ buffer.writeUInt8(getValue(enable_map, enable));
+ var data = 0x00;
+ data |= getValue(enable_map, lora_enable);
+ data |= getValue(enable_map, key_enable) << 1;
+ buffer.writeUInt8(data);
+ buffer.writeD2DCommand(contrl_cmd);
+ return buffer.toBytes();
+}
+
+/**
+ * set time zone
+ * @param {number} time_zone unit: minute, convert: "hh:mm" -> "hh * 60 + mm", values: ( -720: UTC-12, -660: UTC-11, -600: UTC-10, -570: UTC-9:30, -540: UTC-9, -480: UTC-8, -420: UTC-7, -360: UTC-6, -300: UTC-5, -240: UTC-4, -210: UTC-3:30, -180: UTC-3, -120: UTC-2, -60: UTC-1, 0: UTC, 60: UTC+1, 120: UTC+2, 180: UTC+3, 240: UTC+4, 300: UTC+5, 360: UTC+6, 420: UTC+7, 480: UTC+8, 540: UTC+9, 570: UTC+9:30, 600: UTC+10, 660: UTC+11, 720: UTC+12, 765: UTC+12:45, 780: UTC+13, 840: UTC+14 )
+ * @example { "time_zone": 480 }
+ * @example { "time_zone": -240 }
+ */
+function setTimeZone(time_zone) {
+ var timezone_map = { "-720": "UTC-12", "-660": "UTC-11", "-600": "UTC-10", "-570": "UTC-9:30", "-540": "UTC-9", "-480": "UTC-8", "-420": "UTC-7", "-360": "UTC-6", "-300": "UTC-5", "-240": "UTC-4", "-210": "UTC-3:30", "-180": "UTC-3", "-120": "UTC-2", "-60": "UTC-1", 0: "UTC", 60: "UTC+1", 120: "UTC+2", 180: "UTC+3", 210: "UTC+3:30", 240: "UTC+4", 270: "UTC+4:30", 300: "UTC+5", 330: "UTC+5:30", 345: "UTC+5:45", 360: "UTC+6", 390: "UTC+6:30", 420: "UTC+7", 480: "UTC+8", 540: "UTC+9", 570: "UTC+9:30", 600: "UTC+10", 630: "UTC+10:30", 660: "UTC+11", 720: "UTC+12", 765: "UTC+12:45", 780: "UTC+13", 840: "UTC+14" };
+ var timezone_values = getValues(timezone_map);
+ if (timezone_values.indexOf(time_zone) === -1) {
+ throw new Error("time_zone must be one of " + timezone_values.join(", "));
+ }
+
+ var buffer = new Buffer(4);
+ buffer.writeUInt8(0xff);
+ buffer.writeUInt8(0xbd);
+ buffer.writeInt16LE(getValue(timezone_map, time_zone));
+ return buffer.toBytes();
+}
+
+/**
+ * set daylight saving time
+ * @since v2.0
+ * @param {object} daylight_saving_time
+ * @param {number} daylight_saving_time.daylight_saving_time_enable values: (0: disable, 1: enable)
+ * @param {number} daylight_saving_time.daylight_saving_time_offset, unit: minute range: [1, 120]
+ * @param {number} daylight_saving_time.start_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.start_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.start_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.start_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @param {number} daylight_saving_time.end_month, values: (1: January, 2: February, 3: March, 4: April, 5: May, 6: June, 7: July, 8: August, 9: September, 10: October, 11: November, 12: December)
+ * @param {number} daylight_saving_time.end_week_num, range: [1, 5]
+ * @param {number} daylight_saving_time.end_week_day, range: [1, 7]
+ * @param {number} daylight_saving_time.end_hour_min, unit: minute, convert: "hh:mm" -> "hh * 60 + mm"
+ * @example { "daylight_saving_time": { "daylight_saving_time_enable": 1, "daylight_saving_time_offset": 60, "start_month": 3, "start_week_num": 2, "start_week_day": 7, "start_hour_min": 120, "end_month": 1, "end_week_num": 4, "end_week_day": 1, "end_hour_min": 180 } } output: FFBA013C032778000141B400
+ */
+function setDaylightSavingTime(daylight_saving_time) {
+ var enable = daylight_saving_time.daylight_saving_time_enable;
+ var offset = daylight_saving_time.daylight_saving_time_offset;
+ var start_month = daylight_saving_time.start_month;
+ var start_week_num = daylight_saving_time.start_week_num;
+ var start_week_day = daylight_saving_time.start_week_day;
+ var start_hour_min = daylight_saving_time.start_hour_min;
+ var end_month = daylight_saving_time.end_month;
+ var end_week_num = daylight_saving_time.end_week_num;
+ var end_week_day = daylight_saving_time.end_week_day;
+ var end_hour_min = daylight_saving_time.end_hour_min;
+
+ var enable_map = { 0: "disable", 1: "enable" };
+ var enable_values = getValues(enable_map);
+ if (enable_values.indexOf(enable) === -1) {
+ throw new Error("daylight_saving_time.daylight_saving_time_enable must be one of " + enable_values.join(", "));
+ }
+ if (typeof offset !== "number") {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be a number");
+ }
+ if (offset < 1 || offset > 120) {
+ throw new Error("daylight_saving_time.daylight_saving_time_offset must be in range [1, 120]");
+ }
+
+ var week_values = [1, 2, 3, 4, 5, 6, 7];
+ var month_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+ var enable_value = getValue(enable_map, enable);
+ if (enable_value && month_values.indexOf(start_month) === -1) {
+ throw new Error("daylight_saving_time.start_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && month_values.indexOf(end_month) === -1) {
+ throw new Error("daylight_saving_time.end_month must be one of " + month_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(start_week_day) === -1) {
+ throw new Error("daylight_saving_time.start_week_day must be one of " + week_values.join(", "));
+ }
+ if (enable_value && week_values.indexOf(end_week_day) === -1) {
+ throw new Error("daylight_saving_time.end_week_day must be one of " + week_values.join(", "));
+ }
+
+ var data = 0x00;
+ data |= getValue(enable_map, enable) << 7;
+ data |= offset;
+
+ var buffer = new Buffer(11);
+ buffer.writeUInt8(0xf9);
+ buffer.writeUInt8(0x72);
+ buffer.writeUInt8(data);
+ buffer.writeUInt8(start_month);
+ buffer.writeUInt8((start_week_num << 4) | start_week_day);
+ buffer.writeUInt16LE(start_hour_min);
+ buffer.writeUInt8(end_month);
+ buffer.writeUInt8((end_week_num << 4) | end_week_day);
+ buffer.writeUInt16LE(end_hour_min);
+ return buffer.toBytes();
+}
+
+function getValues(map) {
+ var values = [];
+ for (var key in map) {
+ values.push(RAW_VALUE ? parseInt(key) : map[key]);
+ }
+ return values;
+}
+
+function getValue(map, value) {
+ if (RAW_VALUE) return value;
+
+ for (var key in map) {
+ if (map[key] === value) {
+ return parseInt(key);
+ }
+ }
+
+ throw new Error("not match in " + JSON.stringify(map));
+}
+
+function Buffer(size) {
+ this.buffer = new Array(size);
+ this.offset = 0;
+
+ for (var i = 0; i < size; i++) {
+ this.buffer[i] = 0;
+ }
+}
+
+Buffer.prototype._write = function (value, byteLength, isLittleEndian) {
+ var offset = 0;
+ for (var index = 0; index < byteLength; index++) {
+ offset = isLittleEndian ? index << 3 : (byteLength - 1 - index) << 3;
+ this.buffer[this.offset + index] = (value >> offset) & 0xff;
+ }
+};
+
+Buffer.prototype.writeUInt8 = function (value) {
+ this._write(value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeInt8 = function (value) {
+ this._write(value < 0 ? value + 0x100 : value, 1, true);
+ this.offset += 1;
+};
+
+Buffer.prototype.writeUInt16LE = function (value) {
+ this._write(value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeInt16LE = function (value) {
+ this._write(value < 0 ? value + 0x10000 : value, 2, true);
+ this.offset += 2;
+};
+
+Buffer.prototype.writeUInt32LE = function (value) {
+ this._write(value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeInt32LE = function (value) {
+ this._write(value < 0 ? value + 0x100000000 : value, 4, true);
+ this.offset += 4;
+};
+
+Buffer.prototype.writeD2DCommand = function (value, defaultValue) {
+ if (typeof value !== "string") {
+ value = defaultValue;
+ }
+ if (value.length !== 4) {
+ throw new Error("d2d_agent_command length must be 4");
+ }
+ this.buffer[this.offset] = parseInt(value.substr(2, 2), 16);
+ this.buffer[this.offset + 1] = parseInt(value.substr(0, 2), 16);
+ this.offset += 2;
+};
+
+Buffer.prototype.toBytes = function () {
+ return this.buffer;
+};
diff --git a/ws-series/ws503-v4/ws503-v4.png b/ws-series/ws503-v4/ws503-v4.png
new file mode 100644
index 00000000..958c2ae8
Binary files /dev/null and b/ws-series/ws503-v4/ws503-v4.png differ