// Thermokon Sensortechnik GmbH // Payload Decoder for Thermokon JOY LoRaWAN devices // Template: Adaption to corresponding NS/AS necessary // -Revision A- // Questions or remarks:marco.dietz@thermokon.de const NR_OF_HEARTBEAT_VARIABLES = 5; var LPP_PARSER = 0x0000; var LPP_DUMMY = 0x0001; var LPP_HEARTBEAT = 0xC106; var LPP_HEARTBEAT_UPLINK = 0xC000; var LPP_FORCED_UPLINK = 0xC230; //JOY var LPP_SETPOINT_HEAT = 0x0200; var LPP_SETPOINT_COOL = 0x0201; var LPP_SETPOINT_OFFSET = 0x0202; var LPP_SENSOR_INTERN = 0x0203; var LPP_SENSOR_EXTERN = 0x0204; var LPP_OUTPUT_HEATING = 0x0205; var LPP_OUTPUT_COOLING = 0x0206; var LPP_OUTPUT_FAN_6WV = 0x0207; var LPP_INPUT_1 = 0x0208; var LPP_INPUT_2 = 0x0209; var LPP_OCCUPANCY = 0x020A; var LPP_DEWPOINT = 0x020B; var LPP_WINDOW = 0x020C; var LPP_CTR_Y = 0x020D; var LPP_CTR_STATE = 0x020E; var LPP_ECO_MODE = 0x0229; var LPP_SETPOINT_ABSOLUTE = 0x022A; var LPP_CHANGEOVER_STATE = 0x0236; var LPP_HUMIDITY = 0x0237; var LPP_CO2_INTERNAL_SENSOR = 0x023B; var LPP_CO2_ALARM = 0x023C; //LORA var LPP_LRW_HEARTBEAT_INTERVAL = 0xC106; var LPP_LRW_HYTERESE = 0xC107; var LPP_LRW_PORT = 0xC216; var LPP_LRW_ADR = 0xC217; var LPP_LRW_DATARATE = 0xC218; var LPP_LRW_CONFIRMATION = 0xC21D; function u16_to_s16(u16) { var s16 = u16 & 0xFFFF; if (0x8000 & s16) { s16 = -(0x010000 - s16); } return s16; } function convertFanStage(u16) { if(u16 >= 0xFF00) { u16 = (u16 - 0xFF00); } return u16; } function DecodeLPPPayload(data) { var obj = {}; var i = 0; while (i < data.length) { var lpp = (data[i] << 8) | data[i + 1]; // Identifier auslesen i += 2; if (lpp === LPP_FORCED_UPLINK) { // Forced Uplink erkannt, weiter alle Tupel auslesen while (i < data.length) { var subLpp = (data[i] << 8) | data[i + 1]; // Identifier des Tupels i += 2; if (i >= data.length) break; // Falls keine Daten mehr übrig sind, Abbruch switch (subLpp) { case LPP_SETPOINT_HEAT: obj.SETPOINT_HEAT = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_COOL: obj.SETPOINT_COOL = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_OFFSET: obj.SETPOINT_OFFSET = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SENSOR_INTERN: obj.SENSOR_INTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SENSOR_EXTERN: obj.SENSOR_EXTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_OUTPUT_HEATING: obj.OUTPUT_HEATING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OUTPUT_COOLING: obj.OUTPUT_COOLING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OUTPUT_FAN_6WV: obj.OUTPUT_FAN_6WV = convertFanStage((data[i] << 8 | data[i+1])) / 1; break; case LPP_INPUT_1: obj.INPUT_1 = (data[i] << 8 | data[i+1]) / 1; break; case LPP_INPUT_2: obj.INPUT_2 = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OCCUPANCY: obj.OCCUPANCY = (data[i] << 8 | data[i+1]) / 1; break; case LPP_DEWPOINT: obj.DEWPOINT = (data[i] << 8 | data[i+1]) / 1; break; case LPP_WINDOW: obj.WINDOW = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_Y: obj.CTR_Y = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_STATE: obj.CTR_STATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_ECO_MODE: obj.ECO_MODE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_SETPOINT_ABSOLUTE: obj.SETPOINT_ABSOLUTE = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_CHANGEOVER_STATE: obj.CHANGEOVER_STATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_HUMIDITY: obj.HUMIDITY = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_CO2_INTERNAL_SENSOR: obj.CO2_INTERNAL_SENSOR = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CO2_ALARM: obj.CO2_ALARM = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_HEARTBEAT_INTERVAL: obj.LRW_HEARTBEAT_INTERVAL = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_HYTERESE: obj.LRW_HYTERESE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_PORT: obj.LRW_PORT = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_ADR: obj.LRW_ADR = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_DATARATE: obj.LRW_DATARATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_DATARATE: obj.ECO_MODE = (data[i] << 8 | data[i+1]) / 1; break; default: // Unbekannter Identifier, ggf. ignorieren oder Fehlerbehandlung break; } i += 2; } } else if (lpp === LPP_HEARTBEAT_UPLINK) { // Heartbeat Uplink erkannt, weiter alle Tupel auslesen obj.HB_DEVICE_TYPE= (data[i]<<8 | data[i+1]) / 1; i += 2; while (i < data.length) { var subLpp = (data[i] << 8) | data[i + 1]; // Identifier des Tupels i += 2; if (i >= data.length) break; // Falls keine Daten mehr übrig sind, Abbruch switch (subLpp) { case LPP_SETPOINT_HEAT: obj.SETPOINT_HEAT = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_COOL: obj.SETPOINT_COOL = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SENSOR_INTERN: obj.SENSOR_INTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_OFFSET: obj.SETPOINT_OFFSET = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_ABSOLUTE: obj.SETPOINT_ABSOLUTE = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_OUTPUT_FAN_6WV: obj.OUTPUT_FAN_6WV = convertFanStage((data[i] << 8 | data[i+1])) / 1; break; case LPP_SENSOR_INTERN: obj.SENSOR_INTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_HUMIDITY: obj.HUMIDITY = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_CO2_INTERNAL_SENSOR: obj.CO2_INTERNAL_SENSOR = (data[i] << 8 | data[i+1]) / 1; break; case LPP_SENSOR_EXTERN: obj.SENSOR_EXTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_OUTPUT_HEATING: obj.OUTPUT_HEATING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OUTPUT_COOLING: obj.OUTPUT_COOLING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_Y: obj.CTR_Y = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_STATE: obj.CTR_STATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OCCUPANCY: obj.OCCUPANCY = (data[i] << 8 | data[i+1]) / 1; break; case LPP_DEWPOINT: obj.DEWPOINT = (data[i] << 8 | data[i+1]) / 1; break; case LPP_WINDOW: obj.WINDOW = (data[i] << 8 | data[i+1]) / 1; case LPP_ECO_MODE: obj.ECO_MODE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CHANGEOVER_STATE: obj.CHANGEOVER_STATE = (data[i] << 8 | data[i+1]) / 1; break; default: // Unbekannter Identifier, ggf. ignorieren oder Fehlerbehandlung break; } i += 2; } } else { // Einzelwert-Verarbeitung (falls es kein Forced Uplink ist) if (i >= data.length) break; switch (lpp) { case LPP_SETPOINT_HEAT: obj.SETPOINT_HEAT = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_COOL: obj.SETPOINT_COOL = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SETPOINT_OFFSET: obj.SETPOINT_OFFSET = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_SENSOR_INTERN: obj.SENSOR_INTERN = u16_to_s16((data[i] << 8) | data[i + 1])/ 10; break; case LPP_SENSOR_EXTERN: obj.SENSOR_EXTERN = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_OUTPUT_HEATING: obj.OUTPUT_HEATING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OUTPUT_COOLING: obj.OUTPUT_COOLING = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OUTPUT_FAN_6WV: obj.OUTPUT_FAN_6WV = convertFanStage((data[i] << 8 | data[i+1])) / 1; break; case LPP_INPUT_1: obj.INPUT_1 = (data[i] << 8 | data[i+1]) / 1; break; case LPP_INPUT_2: obj.INPUT_2 = (data[i] << 8 | data[i+1]) / 1; break; case LPP_OCCUPANCY: obj.OCCUPANCY = (data[i] << 8 | data[i+1]) / 1; break; case LPP_DEWPOINT: obj.DEWPOINT = (data[i] << 8 | data[i+1]) / 1; break; case LPP_WINDOW: obj.WINDOW = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_Y: obj.CTR_Y = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CTR_STATE: obj.CTR_STATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_ECO_MODE: obj.ECO_MODE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_SETPOINT_ABSOLUTE: obj.SETPOINT_ABSOLUTE = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_CHANGEOVER_STATE: obj.CHANGEOVER_STATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_HUMIDITY: obj.HUMIDITY = u16_to_s16((data[i] << 8) | data[i + 1]) / 10; break; case LPP_CO2_INTERNAL_SENSOR: obj.CO2_INTERNAL_SENSOR = (data[i] << 8 | data[i+1]) / 1; break; case LPP_CO2_ALARM: obj.CO2_ALARM = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_HEARTBEAT_INTERVAL: obj.LRW_HEARTBEAT_INTERVAL = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_HYTERESE: obj.LRW_HYTERESE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_PORT: obj.LRW_PORT = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_ADR: obj.LRW_ADR = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_DATARATE: obj.LRW_DATARATE = (data[i] << 8 | data[i+1]) / 1; break; case LPP_LRW_CONFIRMATION: obj.LRW_CONFIRMATION = (data[i] << 8 | data[i+1]) / 1; break; default: // Unbekannter Identifier break; } i += 2; } } return obj; } function decodeUplink(input) { var warnings = []; var data = DecodeLPPPayload(input.bytes); return { data, warnings }; }