//Parser version 1.3.2, 02-06-2025 /* Parser can used for Stella R and Stella Pro devices from Eurotronic Technology GmbH Supported Software Versions: Base Software 1.43 and radio software 0.4.20 */ /* Changelog: 24-01-2025 - TTN is able to work with this parser now 06-02-2025 - change wording read to get - correct spelling issues 15-04-2025 - It is possible to change the wakeup interval of the thermostat. - Format OFFSET_LOOKUP_TABLE changed. - Remove energy saving data from temperature package 27-05-2025 - Parser is more stable - Wording correction - reduced complexity 02-06-2025 - change wording: child protectio = key lock - change wording: windowOpenDetectio => windowDetection */ const OFFSET_LOOKUP_TABLE = { "-5.0" : 0xf6, "-4.5" : 0xf7, "-4.0" : 0xf8, "-3.5" : 0xf9, "-3.0" : 0xfa, "-2.5" : 0xfb, "-2.0" : 0xfc, "-1.5" : 0xfd, "-1.0" : 0xfe, "-0.5" : 0xff, "0.0" : 0x00, "0.5" : 0x01, "1.0" : 0x02, "1.5" : 0x03, "2.0" : 0x04, "2.5" : 0x05, "3.0" : 0x06, "3.5" : 0x07, "4.0" : 0x08, "4.5" : 0x09, "5.0" : 0x0a, }; function setExternalTemperature(input){ let externalTempData = input.externalTemperatureValue; let output = []; if(externalTempData !== null){ output.push(0xe4); if(externalTempData=='disabled' || externalTempData == 0xff){ output.push(0xff); } else if(externalTempData != 0xff){ if(externalTempData >= 0 && externalTempData <= 50 ){ externalTempData = Math.floor(externalTempData*2); output.push(externalTempData); } else{ output.push(0xff); } } } return output; } function setFlags(input){ let flagByte1 = 0x00; let flagByte2 = 0x00; let output = []; //it is not allowed to set both child lock protection options to the same time if(input.keylock && input.advancedKeylock){ flagByte1 |= 0x01; }else if(input.advancedKeylock){ flagByte1 |= 0x02; }else if(input.keylock){ flagByte1 |= 0x01; } if(input.displayOrientationChanged){ flagByte1 |= 0x04; } flagByte1 |= 0x08; flagByte1 |= 0x10; flagByte2 |= 0x08; output.push(0xf7); output.push(0x80); output.push(flagByte1); output.push(flagByte2); output.push(0x80); output.push(0x80); return output; } function setValvePosition(input){ let currentPosition = input.currentValvePosition; let output = []; if(currentPosition !== null && currentPosition !== undefined && !isNaN(currentPosition)){ output.push(0xff); if(currentPosition > 0 && currentPosition < 256){ output.push(currentPosition.toString(16)); } else{ output.push(0x00); } output.push(0x00); } return output; } function setSetpointLimitation(input){ let lowTempOutput = 0x80; let maxTempOutput = 0x80; let lowTempLimit = Math.floor(input.minimumTemperatureSetpoint * 2); let maxTempLimit = Math.floor(input.maximumTemperatureSetpoint *2); let output = []; if(lowTempLimit !== null && lowTempLimit !== undefined && !isNaN(lowTempLimit)){ if(lowTempLimit < 15 || lowTempLimit > 57 || lowTempLimit > maxTempLimit){ lowTempLimit = 0x80; } }else{ lowTempLimit = 0x80; } lowTempOutput = lowTempLimit; if(maxTempLimit !== null && maxTempLimit !== undefined && !isNaN(maxTempLimit)){ if(maxTempLimit < 15 || maxTempLimit > 57 || lowTempLimit > maxTempLimit){ maxTempLimit = 0x80; } }else{ maxTempLimit = 0x80; } maxTempOutput = maxTempLimit; output.push(0xf9); output.push(lowTempOutput); output.push(maxTempOutput); return output; } function setTemperature(input){ let output = []; output.push(0xf5); output.push(0x80); output.push(0x80); let heatingTemperature = Math.floor(input.heatingTemperature*2); if(heatingTemperature == null || heatingTemperature === undefined || heatingTemperature < 14 || heatingTemperature > 57){ heatingTemperature = 0x80; } output.push(heatingTemperature); let temperatureOffset = OFFSET_LOOKUP_TABLE[input.temperatureOffset]; if(input.temperatureOffset === undefined || input.temperatureOffset === null){ temperatureOffset = 0x80; } output.push(temperatureOffset); let windowOpenDetectionThreshold = Math.floor(input.windowDetectionThreshold); if(windowOpenDetectionThreshold === null || windowOpenDetectionThreshold === undefined || isNaN(windowOpenDetectionThreshold)){ windowOpenDetectionThreshold = 0x80; }else { if(windowOpenDetectionThreshold != 0xff && (windowOpenDetectionThreshold < 4 || windowOpenDetectionThreshold> 12)){ windowOpenDetectionThreshold = 0x80; } } output.push(windowOpenDetectionThreshold); let windowOpenDetectionDuration = input.windowDetectionDuration; if(windowOpenDetectionDuration=== null || windowOpenDetectionDuration === undefined || windowOpenDetectionDuration < 1 || windowOpenDetectionDuration > 30){ windowOpenDetectionDuration = 0x80; } output.push(windowOpenDetectionDuration); output.push(0x80); return output; } function setMaxValvePosition(input){ let output = []; let value = input.maxValvePositionLimitInPercent; output.push(0xE1); if(value >= 0 && value <= 100 && value !== null && !isNaN(value) && value !== undefined){ output.push(value); } else{ output.push(0x80); } return output; } function setWakeupInterval(input){ let intervall = input.wakeupIntervalInSeconds; let output = []; if(intervall >= 60 && intervall <= 7200 && intervall !== null && !isNaN(intervall) && intervall !== undefined){ output.push(0xA3); let lsb = (parseInt(intervall.toString(16),16) & 0xFF); let msb = (parseInt(intervall.toString(16),16) & 0xFF00) >> 8; output.push(lsb); output.push(msb); } return output; } function encodeDownlink(input) { let bytes = []; for(let key of Object.keys(input.data)){ switch(key){ case "getRadioSoftwareVersion": bytes.push(0xa0); break; case "getWakeupInterval": bytes.push(0xa4); break; case "setWakeupInterval": bytes = setWakeupInterval(input.data.setWakeupInterval); break; case "getMaxValvePositionLimit": bytes.push(0xe2); break; case "maxValvePositionLimit": bytes = setMaxValvePosition(input.data.maxValvePositionLimit); break; case "getExternalTemperatureValue": bytes.push(0xe3); break; case "externalTemperatureValue": bytes = setExternalTemperature(input.data); break; case "getSummary": bytes.push(0xe9); break; case "getBaseSoftwareVersion": bytes.push(0xef); break; case "getBatteryValue": bytes.push(0xf3); break; case "getTemperature": bytes.push(0xf4); break; case "temperature": bytes = setTemperature(input.data.temperature); break; case "getFlags": bytes.push(0xf6); break; case "flags": bytes = setFlags(input.data.flags); break; case "getSetpointLimitation": bytes.push(0xf8); break; case "setpointLimitation": bytes = setSetpointLimitation(input.data.setpointLimitation); break; case "getValvePosition": bytes.push(0xfe); break; case "valvePosition": bytes = setValvePosition(input.data.valvePosition); break; default: break; } } return { bytes: bytes, fPort: 2, warnings: [], errors: [] }; } function decodeDownlink(input) { return { data: { bytes: input.bytes }, warnings: [], errors: [] } }