// Decode decodes an array of bytes into an object. // - fPort contains the LoRaWAN fPort number // - bytes is an array of bytes, e.g. [225, 230, 255, 0] // - variables contains the device variables e.g. {"calibration": "3.5"} (both the key / value are of type string) // The function must return an object, e.g. {"temperature": 22.5} // for Yunhorn SmarToilets STS-O7 Occupancy/Fall Detection/Over stay sensor function Decode(fPort, data, variables) { var data = {}; if ((fPort === 10)) { // STS_O2_O6 V3 version 2023,pixel-network version switch (bytes[0]) { case 0x00: data.LEDcolor = "Dark"; break; case 0x01: data.LEDcolor = "Green"; data.cubicleOccupyStatus = "Vacant"; break; case 0x02: data.LEDcolor = "Red"; data.cubicleOccupyStatus = "Occupied"; break; case 0x03: data.LEDcolor = "Blue"; data.cubicleOccupyStatus = "Maintenance"; break; case 0x04: data.LEDcolor = "Yellow"; data.cubicleOccupyStatus = "TBD"; break; case 0x05: data.LEDcolor = "Pink"; data.cubicleOccupyStatus = "TBD"; break; case 0x06: data.LEDcolor = "Cyan"; data.cubicleOccupyStatus = "TBD"; break; case 0x07: data.LEDcolor = "White"; data.cubicleOccupyStatus = "TBD"; break; case 0x08: data.LEDcolor = "Red_Blue"; data.cubicleOccupyStatus = "EMERGENCY"; break; case 0x23: data.LEDcolor = "Red_Blue"; data.cubicleOccupyStatus = "EMERGENCY"; break; case 0x20: data.LEDcolor = "Red_Flash"; data.cubicleOccupyStatus = "EMERGENCY"; break; default: data.LEDcolor = "TBD_COLOR"; data.cubicleOccupyStatus = "TBD_status"; break; } switch (bytes[1]) { case 0x0: data.workmode = "Network_mode"; break; case 0x01: data.workmode = "Wired_Mode"; break; case 0x02: data.workmode = "Hall_element_mode"; break; case 0x03: data.workmode = "MotionDetect_mode"; break; case 0x04: data.workmode = "Dual_mode"; break; case 0x05: data.workmode = "Uni_Mode"; break; default: data.workmode = "Unknown Mode"; break; } // select only one below // For NC(Normal Closed states data.Sensor1_Door_Contact_Open = bytes[2] === 0 ? "Door Closed" : "Door Open"; // For NC(Normal Closed states //data.Sensor1_Door_Contact_Open = bytes[3]===1?"Door Closed":"Door Open"; data.Sensor2_Motion_Detected = bytes[3] === 0 ? "No Motion" : "Motion Detected"; return { "Yunhorn_SmarToilets_data": data }; } else if ((fPort === 17) || (fPort === 19) || (fPort === 21)) { data.BoardLED = ((bytes[0] & 0x7F) === 0x01) ? "ON" : "OFF"; switch (bytes[1]) { case 0x00: data.LEDcolor = "Dark"; break; case 0x01: data.LEDcolor = "Green"; data.cubicleOccupyStatus = "Vacant"; break; case 0x02: data.LEDcolor = "Red"; data.cubicleOccupyStatus = "Occupied"; break; case 0x03: data.LEDcolor = "Blue"; data.cubicleOccupyStatus = "Maintenance"; break; case 0x04: data.LEDcolor = "Yellow"; data.cubicleOccupyStatus = "TBD"; break; case 0x05: data.LEDcolor = "Pink"; data.cubicleOccupyStatus = "TBD"; break; case 0x06: data.LEDcolor = "Cyan"; data.cubicleOccupyStatus = "TBD"; break; case 0x07: data.LEDcolor = "White"; data.cubicleOccupyStatus = "TBD"; break; case 0x08: data.LEDcolor = "Red_Blue"; data.cubicleOccupyStatus = "EMERGENCY"; break; case 0x23: data.LEDcolor = "Red_Blue"; data.cubicleOccupyStatus = "EMERGENCY"; break; case 0x20: data.LEDcolor = "Red_Flash"; data.cubicleOccupyStatus = "EMERGENCY"; break; default: data.LEDcolor = "TBD_COLOR"; data.cubicleOccupyStatus = "TBD_status"; break; } switch (bytes[2]) { case 0x0: data.workmode = "Network_mode"; break; case 0x01: data.workmode = "Wired_Mode"; break; case 0x02: data.workmode = "Hall_element_mode"; break; case 0x03: data.workmode = "MotionDetect_mode"; break; case 0x04: data.workmode = "Dual_mode"; break; case 0x05: data.workmode = "Uni_Mode"; break; default: data.workmode = "Unknown Mode"; break; } // select only one below // For NC(Normal Closed states data.Sensor1_Door_Contact_Open = bytes[3] === 0 ? "Door Closed" : "Door Open"; // For NC(Normal Closed states //data.Sensor1_Door_Contact_Open = bytes[3]===1?"Door Closed":"Door Open"; data.Sensor2_Emergency_Button = bytes[4] === 0 ? "Alarm Push Down" : "No Alarm, Released"; data.Sensor3_Motion_Detected = bytes[5] === 0 ? "No Motion" : "Motion Detected"; data.length = bytes.length if (data.length === 9) { data.Over_stay_state = (bytes[6] === 0) ? "False" : "True"; data.Over_Stay_duration_in_Seconds = (bytes[7] << 8 | bytes[8]); return { "Yunhorn_SmarToilets_data": data }; } else if (data.length > 9) { data.Sensor4 = (bytes[6] === 0) ? "No" : "Yes"; data.Distance_in_mm = (bytes[7] << 8 | bytes[8]); data.MotionLevel = (bytes[9] << 8 | bytes[10]); data.Unconcious_State = (bytes[11] == 0) ? "False" : "True"; switch (bytes[12]) { case 0x0: data.Fall_Down_Detected_State = "Presence_Normal"; break; case 0x01: data.Fall_Down_Detected_State = "Presence_Fall_Down"; break; case 0x02: data.Fall_Down_Detected_State = "Presence_Rising_Up"; break; case 0x03: data.Fall_Down_Detected_State = "Presence_LayDown"; break; case 0x04: data.Fall_Down_Detected_State = "Presence_Unconcious"; break; case 0x05: data.Fall_Down_Detected_State = "Presence_Stay_Still"; break; default: data.Fall_Down_Detected_State = "Presence_Normal"; break; } data.OverStay_Detected_State = (bytes[13] == 0x0) ? "False" : "True"; data.OverStay_Duration_in_Seconds = (bytes[14] << 8 | bytes[15]); data.No_Movement_Duration_in_Seconds = (bytes[16] << 8 | bytes[17]); data.Unconcious_Duration_in_Seconds = (bytes[16] << 8 | bytes[17]); data.Fall_Down_Speed_in_m_per_s = (bytes[18]); data.Fall_Down_Gravity_in_g = (bytes[19]); data.SOS_PushDown_Stamp = (bytes[20] << 24 | bytes[21] << 16 | bytes[22] << 8 | bytes[23]); if (data.SOS_PushDown_Stamp != 0) { var sos_start = new Date(1000 * data.SOS_PushDown_Stamp); data.SOS_PushDown_Time = "[" + sos_start.getDate() + "." + (sos_start.getMonth() + 1) + "." + (sos_start.getFullYear()) + "] " + sos_start.getHours() + ":" + sos_start.getMinutes() + ":" + sos_start.getSeconds(); } else data.SOS_PushDown_Time = "N/A"; data.SOS_ReleaseUP_Stamp = (bytes[24] << 24 | bytes[25] << 16 | bytes[26] << 8 | bytes[27]); if (data.SOS_ReleaseUP_Stamp != 0) { var sos_stop = new Date(1000 * data.SOS_ReleaseUP_Stamp); data.SOS_ReleaseUP_Time = "[" + sos_stop.getDate() + "." + (sos_stop.getMonth() + 1) + "." + (sos_stop.getFullYear()) + "] " + sos_stop.getHours() + ":" + sos_stop.getMinutes() + ":" + sos_stop.getSeconds(); } else data.SOS_ReleaseUP_Time = "N/A"; data.Fall_Down_Stamp = (bytes[28] << 24 | bytes[29] << 16 | bytes[30] << 8 | bytes[31]); if (data.Fall_Down_Stamp != 0) { var fall_start = new Date(1000 * data.Fall_Down_Stamp); data.Fall_Down_Time = "[" + fall_start.getDate() + "." + (fall_start.getMonth() + 1) + "." + (fall_start.getFullYear()) + "] " + fall_start.getHours() + ":" + fall_start.getMinutes() + ":" + fall_start.getSeconds(); } else data.Fall_RiseUp_Stamp = "N/A"; data.Fall_RiseUp_Stamp = (bytes[32] << 24 | bytes[33] << 16 | bytes[34] << 8 | bytes[35]); if (data.Fall_RiseUp_Stamp != 0) { var fall_stop = new Date(1000 * data.Fall_RiseUp_Stamp); data.Fall_RiseUp_Time = "[" + fall_stop.getDate() + "." + (fall_stop.getMonth() + 1) + "." + (fall_stop.getFullYear()) + "] " + fall_stop.getHours() + ":" + fall_stop.getMinutes() + ":" + fall_stop.getSeconds(); } else data.Fall_RiseUp_Time = "N/A"; } return { "Yunhorn_SmarToilets_data": data }; } // Heart Beat else if ((fPort === 20) || (fPort === 18) || (fPort === 5)) { var data = {}; //data.led_state=(bytes[0] & 0x7f) === 0 ? "Off" : "On"; data.BoardLED = ((bytes[0] & 0x7F) === 0x01) ? "ON" : "OFF"; data.battery_level = bytes[1] + " %"; return { "Yunhorn_SmarToilets_data": data }; } // UPLINK, RFAC else if (fPort === 1) { var data = {}; data.length = bytes.length; if (data.length === 4) { data.RFAC = "OK"; data.AC0 = bytes[0]; data.AC1 = bytes[1]; } if ((bytes[0] === 0x59) && (bytes[1] === 0x44)) //Duration interval { data.Heart_beat_Duration = (bytes[2] - 0x30) * 10 + (bytes[3] - 0x30); data.Unit = String.fromCharCode(bytes[4]); } else if ((bytes[0] === 0x59) && (bytes[1] === 0x53)) //Sampling interval or Heart-beat interval { data.Wakeup_sampling_interval = (bytes[2] - 0x30) * 10 + (bytes[3] - 0x30); data.Unit = String.fromCharCode(bytes[4]); } else if (bytes[0] === 0x43) { // report current nvm config data.mtm_code1 = bytes[1]; data.mtm_code2 = bytes[2]; data.sts_verion = bytes[3]; data.sts_hw_ver = bytes[4]; data.sts_uplink_interval = bytes[5]; data.sts_uplink_interval_unit = String.fromCharCode(bytes[6]); data.sts_sampling_interval = bytes[7]; data.sts_sampling_interval_unit = String.fromCharCode(bytes[8]); data.sts_work_mode = bytes[9]; data.sts_service_mask = bytes[10]; data.sts_reserve01 = bytes[11]; data.sts_payload_length = bytes[12]; data.rss_start_distance = bytes[13] * 0.1 + " meter"; data.rss_range_length = bytes[14] * 0.1 + " meter"; data.rss_threshold = bytes[15] * 0.1; data.rss_receiving_gain = bytes[16] + " %"; data.rss_profile = bytes[17]; data.rss_rate_tracking = bytes[18]; data.rss_rate_presence = bytes[19]; data.rss_HWAAS = bytes[20]; data.rss_nbr_removed_pc = bytes[21]; data.rss_inter_frame_deviation_time_const = bytes[22] * 0.1; data.rss_inter_frame_fast_cutoff = bytes[23]; data.rss_inter_frame_slow_cutoff = bytes[24]; data.rss_intra_frame_time_const = bytes[25]; data.rss_intra_frame_weight = bytes[26] * 0.1; data.rss_output_time_const = bytes[27] * 0.1; data.rss_downsampling_factor = bytes[28]; data.rss_power_saving_mode_active = bytes[29]; data.rss_reserve02 = bytes[30]; data.rss_reserve03 = bytes[31]; data.rss_reserve04 = bytes[32]; data.reserve2 = bytes[33]; data.reserve3 = bytes[34]; data.reserve4 = bytes[35]; data.alarm_parameter05 = bytes[36]; data.alarm_mute_expire_timer = bytes[37]; data.alarm_lamp_bar_flashing_color = bytes[38]; data.occupancy_overtime_threshold = bytes[39]; data.motionless_duration_threshold = bytes[40]; data.unconcious_threshold = bytes[41]; data.fall_detection_acc_threshold = bytes[42]; data.fall_detection_depth_threshold = bytes[43]; data.fall_down_confirm_threshold = bytes[44]; } else if (bytes[0] === 0x53) { // SELF TEST FUNCTION data.mtm_code1 = bytes[1]; data.mtm_code2 = bytes[2]; data.sts_verion = bytes[3]; data.sts_hw_ver = bytes[4]; data.battery_level = bytes[5]; data.sts_test_result_length = bytes[6]; data.sts_rss_sub_code1 = bytes[7]; data.sts_rss_sub_code2 = bytes[8]; data.sts_rss_sub_code3 = bytes[9]; data.sts_rss_sub_code4 = bytes[10]; data.sts_rss_sub_code5 = bytes[11]; data.sts_rss_sub_code6 = bytes[12]; data.sts_rss_sub_code7 = bytes[13]; data.sts_rss_sub_code8 = bytes[14]; data.sts_rss_sub_code9 = bytes[15]; data.sts_rss_sub_code10 = bytes[16]; data.sts_sensor_install_height = String.fromCharCode(bytes[17]) + String.fromCharCode(bytes[18]) + String.fromCharCode(bytes[19]) + String.fromCharCode(bytes[20]) + " mm"; } else if (bytes[0] === 0x44) { // MEASURE INSTALLATION HEIGHT data.mtm_code1 = bytes[1]; data.mtm_code2 = bytes[2]; data.sts_verion = bytes[3]; data.sts_hw_ver = bytes[4]; data.battery_level = bytes[5]; data.payload_length = bytes[6]; data.Measure_Distance = String.fromCharCode(bytes[7]) + String.fromCharCode(bytes[8]) + String.fromCharCode(bytes[9]) + String.fromCharCode(bytes[10]); data.Distance_unit = "mm"; } else if (bytes[0] === 0x4c) { data.mtm_code1 = bytes[1]; data.mtm_code2 = bytes[2]; data.sts_verion = bytes[3]; data.LoRaWAN_Class = String.fromCharCode(bytes[4]); } else if (bytes[0] === 0x56) { // FIRMWARE VERSION data.mtm_code1 = bytes[1]; data.mtm_code2 = bytes[2]; data.sts_verion = bytes[3]; data.sts_hw_ver = bytes[4]; data.sts_major = bytes[5]; data.sts_minor = bytes[6]; data.subminor = bytes[7]; if (data.length === 15) { data.L_year = (bytes[8] << 8 | bytes[9]); data.L_mon = bytes[10]; data.L_day = bytes[11]; data.L_hour = bytes[12]; data.L_min = bytes[13]; data.L_sec = bytes[14]; data.LocalTime_UTC = "UTC: " + data.L_year + "/" + data.L_mon + "/" + data.L_day + " " + data.L_hour + ":" + data.L_min + ":" + data.L_sec; data.LocalTime_EST8 = "GMT+8: " + data.L_year + "/" + data.L_mon + "/" + data.L_day + " " + (data.L_hour + 8) + ":" + data.L_min + ":" + data.L_sec; } } return { "Yunhorn_SmarToilets_data": data }; } }