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 + +![WS501](ws501-v4.png) + +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 + +![WS502](ws502-v4.png) + +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 + +![WS503](ws503-v4.png) + +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