MClimate LoRaWAN Devices
  • Overview
  • FAQ
  • Firmware Upgrade Over The Air (FUOTA)
  • Devices
    • 🆕МClimate 16A Dry Switch (16ADS)
      • 🥳Release notes
      • 🛠️How to use
      • ⬆️MClimate 16ADS Uplink decoder
      • ⬇️MClimate 16ADS Downlink encoder
      • 📖MClimate 16ADS Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • Overheating protection
        • Relay state 16ADS
        • LED indication mode
        • Network-related settings
        • Get Firmware & Hardware version
        • Restart device
    • 🆕МClimate 16A Switch & Power Meter LoRaWAN (16ASPM)
      • 🥳Release notes
      • 🛠️How to use
      • ⬆️MClimate 16ASPM Uplink decoder
      • ⬇️MClimate 16ASPM Downlink encoder
      • 📖MClimate 16ASPM Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • Protections
        • Relay state 16ASPM
        • Clear accumulated energy
        • LED indication mode
        • Network-related settings
        • Get Firmware & Hardware version
        • Restart device
    • 😲MClimate Fan Coil Thermostat (FCT)
      • 🥳Release notes
      • ⭐Getting started
      • ⚡Wiring Diagrams (Applications) & Operational Modes
      • ⬆️MClimate Fan Coil Thermostat Uplink decoder
      • ⬇️MClimate Fan Coil Thermostat Downlink encoder
      • 📖MClimate Fan Coil Thermostat Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Keep-alive
        • ON/OFF & Target temperature
          • Valve(s) operation
          • Target temperature ranges
        • Fan Settings
          • Auto Fan - Δ settings
        • 🔓Keys lock
        • External temperature measurement
        • Power module communication status
        • Function of digital input/output (IO1 and IO2 ports)
          • Automatic changeover
          • Occupancy sensor
        • General, Display & Power recovery
          • Hiding data from the display & settings
          • Frost Protection
          • Temperature sensor errors
          • Network-related settings
            • Uplink types
          • User interface language
        • Restart device
    • ♨️MClimate Vicki LoRaWAN
      • 🥳Release notes
      • ⬆️Vicki Uplink Decoder
      • ⬇️Vicki Downlink Encoder
      • 📖Vicki LoRaWAN Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • Manual target temperature change
        • Operational modes & temperature control algorithms
          • Algorithm 1 - Equal directional control
          • Algorithm 2 - Proportional control
          • Algorithm 3 - Proportional Integral
        • External temperature measurement and internal temperature offset
        • Control target temperature and/or motor position and range
        • Recalibrate motor command explanation
        • Read device hardware and software version command explanation.
        • Anti-freeze functionality
        • Open window detection
        • Child lock
        • Target temperature ranges
        • Temperature units
        • Force-close & Force-attach
        • Network-related settings
        • Appendix (examples)
      • 📺Technical Deepdive Webinar
    • 🆕MClimate CO2 Display lite
      • 🥳Release notes
      • ⬆️MClimate CO2 Display lite Uplink decoder
      • 📖CO2 Display lite Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • CO2 measurement settings
        • Hiding data from the display
        • Child lock
        • Network-related settings & Others
        • Get Firmware & Hardware version
        • Restart device
    • MClimate CO2 Display
      • 🥳Release notes
      • ⬆️MClimate CO2 Display Uplink decoder
      • 📖CO2 Display Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • CO2 measurement settings
        • Hiding data from the display
        • Child lock
        • PIR (Motion sensor)
        • Network-related settings & Others
        • Get Firmware & Hardware version
    • MClimate Wireless Thermostat
      • 🥳Release notes
      • ⬆️MClimate Wireless Thermostat Uplink decoder
      • ⬇️MClimate Wireless Thermostat Uplink encoder
      • 📖Wireless Thermostat Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • Target Temperature & Temperature range
        • Sensor mode & hiding data from the display
        • Heating status flag
        • Child lock
        • PIR (Motion sensor)
        • Get Firmware & Hardware version
        • Network-related settings & Others
    • MClimate HT Sensor LoRaWAN
      • 🥳Release notes
      • ⬆️HT Sensor Uplink Decoder
      • 📖HT Sensor LoRaWAN Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Temperature and humidity compensation
        • Keep-alive
        • Read device hardware and software version command explanation.
        • Network-related settings
        • Uplink types
        • Appendix (examples)
    • MClimate CO2 Sensor and Notifier LoRaWAN
      • 🥳Release notes
      • ⬆️CO2 Sensor Uplink Decoder
      • 📖CO2 Sensor LoRaWAN Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Keep-alive
        • Read device hardware and software version command explanation.
        • Network-related settings
        • Uplink types
        • CO2 boundary levels
        • CO2 auto-zero value
        • CO2 auto-zero period
        • CO2 Measurement period
        • Notifications configuration
    • MClimate Open/Close Sensor LoRaWAN
      • 🥳Release notes
      • ⬆️Open/Close Sensor uplink decoder
      • 📖Open/Close sensor LoRaWAN communication protocol
        • Commands cheat sheet
        • Keep-alive
        • Read device hardware and software version command explanation
        • Uplink types
        • Network-related settings
        • Event notification
        • Notification Blind Time
        • LED control command explanation
        • Restart device
    • MClimate Multipurpose Button LoRaWAN
      • 🥳Release notes
      • ⬆️Multipurpose Button Uplink decoder
      • 📖MClimate Button LoRaWAN Device communication protocol
        • Commands cheat sheet
        • Keep-alive
        • LEDs, button press types and behaviour
        • Read device hardware and software version command explanation
        • Network-related settings
        • Uplink types
        • Button press event counters
        • LED control command explanation
        • Restart device
    • MClimate T-Valve LoRaWAN
      • 🥳Release notes
      • ⬆️T-Valve Uplink Decoder
      • T-Valve LoRaWAN communication protocol
        • Commands cheat sheet
        • Uplink types
        • Keep-alive
        • Valve state control
        • Set LED behavior
        • Buzzer control
        • Emergency openings
        • Enable/disable manual valve open/close
        • Flood alarm time
        • Keep-alive period
        • Request Long data packet
        • Device allowed working voltage
        • Enable/Disable device flood sensor
        • Network related settings
        • Deactivate device (non-operational mode, save power)
    • MClimate Flood Sensor LoRaWAN
      • 🥳Release notes
      • ⬆️Flood Sensor Uplink Decoder
      • 📖Flood Sensor LoRaWAN communication protocol
        • Commands cheat sheet
        • Keep-alive
        • Flood event - Available configurations
        • Uplink types
        • Network-related settings
        • Read Firmware & Hardware version
        • Custom control of LED and Acoustic Buzzer
        • Read device parameters command
        • Restart device
    • MClimate AQI Sensor and Notifier LoRaWAN
      • 🥳Release notes
      • AQI Sensor Uplink Decoder
      • AQI Sensor Downlink encoder
      • AQI Sensor LoRaWAN Device communication protocol
        • Communication concepts
        • Commands cheat sheet
        • Keep-alive
        • Read device hardware and software version command explanation.
        • Network-related settings
        • Uplink types
        • Device buzzer control command
        • Device LED’s control
        • Appendix (examples)
  • Others
    • Application of MClimate Vicki to One-pipe steam heating systems
    • Battery Lifetime Estimation Methodology
    • Discover Smart Buildings solutions
    • How to solve Large space heating issues
    • Device Firmware upgrade via a Field Programming Kit (FPK)
      • Vicki Firmware Upgrade
  • Integrations
    • The Things Industries / TTN V3
    • ThingPark Enterprise
    • ThingPark Community
    • Tektelic
    • Helium
    • Chirpstack
    • Loriot
    • Kerlink
    • Melita
    • MachineQ
    • Orbiwise
    • Firefly
    • B-One
    • Milesight
    • Akenza
    • Element-IoT
    • Senet
    • Wattsense
    • RAK WisGateOS2
    • Netmore
Powered by GitBook
On this page
  • TTN V3 Encoder:
  • Milesight Encoder:

Was this helpful?

  1. Devices
  2. MClimate Vicki LoRaWAN

Vicki Downlink Encoder

PreviousVicki Uplink DecoderNextVicki LoRaWAN Device communication protocol

Last updated 6 months ago

Was this helpful?

Use the following script to start controlling Vicki LoRaWAN from your application in settings. Thanks to this script, you can skip understanding the hex device communication protocol of Vicki and go straight to development and testing.

If you will be using Vicki in large installations, we urge you to read the following points from the Device communication protocol document, so you can better understand the device:

TTN V3 Encoder:

function encodeDownlink(input) {
  var bytes = [];
  for (let key of Object.keys(input.data)) {
    switch (key) {
      case "setKeepAlive": {
        bytes.push(0x02);
        bytes.push(input.data.setKeepAlive);
        break;
      }
      case "getKeepAliveTime": {
        bytes.push(0x12);
        break;
      }
      case "recalibrateMotor": {
        bytes.push(0x03);
        break;
      }
      case "getDeviceVersions": {
        bytes.push(0x04);
        break;
      }
      case "setOpenWindow": {
        let enabled = Number(input.data.setOpenWindow.enabled);
        let closeTime = parseInt(input.data.setOpenWindow.closeTime / 5);
        let delta = parseInt(input.data.setOpenWindow.delta, 8);
        let motorPosition = input.data.setOpenWindow.motorPosition;
        let motorPositionFirstPart = motorPosition & 0xff;
        let motorPositionSecondPart = (motorPosition >> 8) & 0xff;
        bytes.push(0x06);
        bytes.push(enabled);
        bytes.push(closeTime);
        bytes.push(motorPositionFirstPart);
        bytes.push((motorPositionSecondPart << 4) | delta);
        break;
      }

      case "getOpenWindowParams": {
        bytes.push(0x13);
        break;
      }
      case "setChildLock": {
        bytes.push(0x07);
        bytes.push(Number(input.data.setChildLock));
        break;
      }
      case "getChildLock": {
        bytes.push(0x14);
        break;
      }
      case "setTemperatureRange": {
        bytes.push(0x08);
        bytes.push(input.data.setTemperatureRange.min);
        bytes.push(input.data.setTemperatureRange.max);
        break;
      }
      case "getTemperatureRange": {
        bytes.push(0x15);
        break;
      }
      case "forceClose": {
        bytes.push(0x0b);
        break;
      }
      case "setInternalAlgoParams": {
        bytes.push(0x0c);
        bytes.push(input.data.setInternalAlgoParams.pFirstLast);
        bytes.push(input.data.setInternalAlgoParams.pNext);
        break;
      }
      case "getInternalAlgoParams": {
        bytes.push(0x16);
        break;
      }
      case "setInternalAlgoTdiffParams": {
        bytes.push(0x1a);
        bytes.push(input.data.setInternalAlgoTdiffParams.cold);
        bytes.push(input.data.setInternalAlgoTdiffParams.warm);
        break;
      }
      case "getInternalAlgoTdiffParams": {
        bytes.push(0x17);
        break;
      }
      case "setOperationalMode": {
        bytes.push(0x0d);
        bytes.push(input.data.setOperationalMode);
        break;
      }
      case "getOperationalMode": {
        bytes.push(0x18);
        break;
      }
      case "setTargetTemperature": {
        bytes.push(0x0e);
        bytes.push(input.data.setTargetTemperature);
        break;
      }
      case "setExternalTemperature": {
        bytes.push(0x0f);
        bytes.push(input.data.setExternalTemperature);
        break;
      }
      case "setJoinRetryPeriod": {
        // period should be passed in minutes
        let periodToPass = (input.data.setJoinRetryPeriod * 60) / 5;
        periodToPass = int(periodToPass);
        bytes.push(0x10);
        bytes.push(periodToPass);
        break;
      }
      case "getJoinRetryPeriod": {
        bytes.push(0x19);
        break;
      }
      case "setUplinkType": {
        bytes.push(0x11);
        bytes.push(input.data.setUplinkType);
        break;
      }
      case "getUplinkType": {
        bytes.push(0x1b);
        break;
      }
      case "setTargetTemperatureAndMotorPosition": {
        bytes.push(0x31);
        bytes.push(
          input.data.setTargetTemperatureAndMotorPosition.motorPosition
        );
        bytes.push(
          input.data.setTargetTemperatureAndMotorPosition.targetTemperature
        );
        break;
      }
      case "setWatchDogParams": {
        bytes.push(0x1c);
        bytes.push(input.data.setWatchDogParams.confirmedUplinks);
        bytes.push(input.data.setWatchDogParams.unconfirmedUplinks);
        break;
      }
      case "getWatchDogParams": {
        bytes.push(0x1d);
        break;
      }
      case "setPrimaryOperationalMode": {
        bytes.push(0x1e);
        bytes.push(input.data.setPrimaryOperationalMode);
        break;
      }
      case "getPrimaryOperationalMode": {
        bytes.push(0x1f);
        break;
      }
      case "setProportionalAlgorithmParameters": {
        bytes.push(0x2a);
        bytes.push(input.data.setProportionalAlgorithmParameters.coefficient);
        bytes.push(input.data.setProportionalAlgorithmParameters.period);
        break;
      }
      case "getProportionalAlgorithmParameters": {
        bytes.push(0x29);
        break;
      }
      case "setTemperatureControlAlgorithm": {
        bytes.push(0x2c);
        bytes.push(input.data.setTemperatureControlAlgorithm);
        break;
      }
      case "getTemperatureControlAlgorithm": {
        bytes.push(0x2b);
        break;
      }
      case "setMotorPositionOnly": {
        let motorPosition = input.data.setMotorPositionOnly;
        let motorPositionFirstPart = motorPosition & 0xff;
        let motorPositionSecondPart = (motorPosition >> 8) & 0xff;
        bytes.push(0x2d);
        bytes.push(motorPositionSecondPart);
        bytes.push(motorPositionFirstPart);
        break;
      }
      case "deviceReset": {
        bytes.push(0x30);
        break;
      }
      case "setChildLockBehavior": {
        bytes.push(0x35);
        bytes.push(input.data.setChildLockBehavior);
        break;
      }
      case "getChildLockBehavior": {
        bytes.push(0x34);
        break;
      }
      case "setProportionalGain": {
        let kp = Math.round(input.data.setProportionalGain * 131072);
        let kpFirstPart = kp & 0xff;
        let kpSecondPart = (kp >> 8) & 0xff;
        let kpThirdPart = (kp >> 16) & 0xff;
        bytes.push(0x37);
        bytes.push(kpThirdPart);
        bytes.push(kpSecondPart);
        bytes.push(kpFirstPart);
        break;
      }
      case "getProportionalGain": {
        bytes.push(0x36);
        break;
      }
      case "setExternalTemperatureFloat": {
        let temp = input.data.setExternalTemperatureFloat * 10;
        let tempFirstPart = temp & 0xff;
        let tempSecondPart = (temp >> 8) & 0xff;
        bytes.push(0x3c);
        bytes.push(tempSecondPart);
        bytes.push(tempFirstPart);
        break;
      }
      case "setIntegralGain": {
        let ki = Math.round(input.data.setIntegralGain * 131072);

        let kiFirstPart = ki & 0xff;
        let kiSecondPart = (ki >> 8) & 0xff;
        let kiThirdPart = (ki >> 16) & 0xff;
        bytes.push(0x3e);
        bytes.push(kiThirdPart);
        bytes.push(kiSecondPart);
        bytes.push(kiFirstPart);
        break;
      }
      case "getIntegralGain": {
        bytes.push(0x3d);
        break;
      }
      case "setPiRunPeriod": {
        bytes.push(0x41);
        bytes.push(input.data.setPiRunPeriod);
        break;
      }
      case "getPiRunPeriod": {
        bytes.push(0x40);
        break;
      }
      case "setTempHysteresis": {
        let tempHysteresis = input.data.setTempHysteresis * 10;
        bytes.push(0x43);
        bytes.push(tempHysteresis);
        break;
      }
      case "getTempHysteresis": {
        bytes.push(0x42);
        break;
      }
      case "setOpenWindowPrecisely": {
        let enabledValue = input.data.setOpenWindowPrecisely.enabled ? 1 : 0;
        let duration = parseInt(input.data.setOpenWindowPrecisely.duration) / 5;
        let delta = input.data.setOpenWindowPrecisely.delta * 10

        bytes.push(0x45);
        bytes.push(enabledValue);
        bytes.push(duration);
        bytes.push(delta);
        break;
      }
      case "getOpenWindowPrecisely": {
        bytes.push(0x46);
        break;
      }
      case "setForceAttach": {
        bytes.push(0x47);
        bytes.push(input.data.setForceAttach);
        break;
      }
      case "getForceAttach": {
        bytes.push(0x48);
        break;
      }
      case "sendCustomHexCommand": {
        let sendCustomHexCommand = input.data.sendCustomHexCommand;
        for (let i = 0; i < sendCustomHexCommand.length; i += 2) {
          const byte = parseInt(sendCustomHexCommand.substr(i, 2), 16);
          bytes.push(byte);
        }
        break;
      }
      default: {
      }
    }
  }

  return {
    bytes: bytes,
    fPort: 1,
    warnings: [],
    errors: [],
  };
}

function decodeDownlink(input) {
  return {
    data: {
      bytes: input.bytes,
    },
    warnings: [],
    errors: [],
  };
}

// example downlink commands
// {"getOperationalMode":""} --> 0x18
// {"setTargetTemperature":20} --> 0x0E14
// {"setTemperatureRange":{"min":15,"max":21}} --> 0x080F15
// {"setChildLock":true} --> 0701
// {"sendCustomHexCommand":"080F15"} --> 0x080F15
// {"setOpenWindow":{"enabled": true, "closeTime": 20 , "delta": 3, "motorPosition": 540}}  --> 0x0601041C23

// example Node Red mqtt message
// msg.topic = 'v3/<Application ID>@ttn/devices/<End device ID>/down/push'
// msg.payload = {"downlinks":[{f_port:1,decoded_payload:{setTargetTemperature:20},priority:'NORMAL',confirmed:false}]}

Milesight Encoder:

function Encode(port, input) {
    var bytes = [];
    var key, i;
    if(!input.hasOwnProperty('data')){
        input.data = input
    }

    for (key in input.data) {
        if (input.data.hasOwnProperty(key)) {
            switch (key) {
                case "setKeepAlive":
                    bytes.push(0x02);
                    bytes.push(input.data.setKeepAlive);
                    break;
                case "getKeepAliveTime":
                    bytes.push(0x12);
                    break;
                case "recalibrateMotor":
                    bytes.push(0x03);
                    break;
                case "getDeviceVersions":
                    bytes.push(0x04);
                    break;
                case "setOpenWindow":
                    var enabled = Number(input.data.setOpenWindow.enabled);
                    var closeTime = parseInt(input.data.setOpenWindow.closeTime / 5, 10);
                    var delta = parseInt(input.data.setOpenWindow.delta, 8);
                    var motorPosition = input.data.setOpenWindow.motorPosition;
                    var motorPositionFirstPart = motorPosition & 0xff;
                    var motorPositionSecondPart = (motorPosition >> 8) & 0xff;
                    bytes.push(0x06);
                    bytes.push(enabled);
                    bytes.push(closeTime);
                    bytes.push(motorPositionFirstPart);
                    bytes.push((motorPositionSecondPart << 4) | delta);
                    break;
                case "getOpenWindowParams":
                    bytes.push(0x13);
                    break;
                case "setChildLock":
                    bytes.push(0x07);
                    bytes.push(Number(input.data.setChildLock));
                    break;
                case "getChildLock":
                    bytes.push(0x14);
                    break;
                case "setTemperatureRange":
                    bytes.push(0x08);
                    bytes.push(input.data.setTemperatureRange.min);
                    bytes.push(input.data.setTemperatureRange.max);
                    break;
                case "getTemperatureRange":
                    bytes.push(0x15);
                    break;
                case "forceClose":
                    bytes.push(0x0b);
                    break;
                case "setInternalAlgoParams":
                    bytes.push(0x0c);
                    bytes.push(input.data.setInternalAlgoParams.pFirstLast);
                    bytes.push(input.data.setInternalAlgoParams.pNext);
                    break;
                case "getInternalAlgoParams":
                    bytes.push(0x16);
                    break;
                case "setInternalAlgoTdiffParams":
                    bytes.push(0x1a);
                    bytes.push(input.data.setInternalAlgoTdiffParams.cold);
                    bytes.push(input.data.setInternalAlgoTdiffParams.warm);
                    break;
                case "getInternalAlgoTdiffParams":
                    bytes.push(0x17);
                    break;
                case "setOperationalMode":
                    bytes.push(0x0d);
                    bytes.push(input.data.setOperationalMode);
                    break;
                case "getOperationalMode":
                    bytes.push(0x18);
                    break;
                case "setTargetTemperature":
                    bytes.push(0x0e);
                    bytes.push(input.data.setTargetTemperature);
                    break;
                case "setExternalTemperature":
                    bytes.push(0x0f);
                    bytes.push(input.data.setExternalTemperature);
                    break;
                case "setJoinRetryPeriod":
                    var periodToPass = Math.floor((input.data.setJoinRetryPeriod * 60) / 5);
                    bytes.push(0x10);
                    bytes.push(periodToPass);
                    break;
                case "getJoinRetryPeriod":
                    bytes.push(0x19);
                    break;
                case "setUplinkType":
                    bytes.push(0x11);
                    bytes.push(input.data.setUplinkType);
                    break;
                case "getUplinkType":
                    bytes.push(0x1b);
                    break;
                case "setTargetTemperatureAndMotorPosition":
                    bytes.push(0x31);
                    bytes.push(input.data.setTargetTemperatureAndMotorPosition.motorPosition);
                    bytes.push(input.data.setTargetTemperatureAndMotorPosition.targetTemperature);
                    break;
                case "setWatchDogParams":
                    bytes.push(0x1c);
                    bytes.push(input.data.setWatchDogParams.confirmedUplinks);
                    bytes.push(input.data.setWatchDogParams.unconfirmedUplinks);
                    break;
                case "getWatchDogParams":
                    bytes.push(0x1d);
                    break;
                case "setPrimaryOperationalMode":
                    bytes.push(0x1e);
                    bytes.push(input.data.setPrimaryOperationalMode);
                    break;
                case "getPrimaryOperationalMode":
                    bytes.push(0x1f);
                    break;
                case "setProportionalAlgorithmParameters":
                    bytes.push(0x2a);
                    bytes.push(input.data.setProportionalAlgorithmParameters.coefficient);
                    bytes.push(input.data.setProportionalAlgorithmParameters.period);
                    break;
                case "getProportionalAlgorithmParameters":
                    bytes.push(0x29);
                    break;
                case "setTemperatureControlAlgorithm":
                    bytes.push(0x2c);
                    bytes.push(input.data.setTemperatureControlAlgorithm);
                    break;
                case "getTemperatureControlAlgorithm":
                    bytes.push(0x2b);
                    break;
                case "setMotorPositionOnly":
                    var motorPosition = input.data.setMotorPositionOnly;
                    var motorPositionFirstPart = motorPosition & 0xff;
                    var motorPositionSecondPart = (motorPosition >> 8) & 0xff;
                    bytes.push(0x2d);
                    bytes.push(motorPositionSecondPart);
                    bytes.push(motorPositionFirstPart);
                    break;
                case "deviceReset":
                    bytes.push(0x30);
                    break;
                case "setChildLockBehavior":
                    bytes.push(0x35);
                    bytes.push(input.data.setChildLockBehavior);
                    break;
                case "getChildLockBehavior":
                    bytes.push(0x34);
                    break;
                case "setProportionalGain":
                    var kp = Math.round(input.data.setProportionalGain * 131072);
                    var kpFirstPart = kp & 0xff;
                    var kpSecondPart = (kp >> 8) & 0xff;
                    var kpThirdPart = (kp >> 16) & 0xff;
                    bytes.push(0x37);
                    bytes.push(kpThirdPart);
                    bytes.push(kpSecondPart);
                    bytes.push(kpFirstPart);
                    break;
                case "getProportionalGain":
                    bytes.push(0x36);
                    break;
                case "setExternalTemperatureFloat":
                    var temp = Math.round(input.data.setExternalTemperatureFloat * 10);
                    var tempFirstPart = temp & 0xff;
                    var tempSecondPart = (temp >> 8) & 0xff;
                    bytes.push(0x3c);
                    bytes.push(tempSecondPart);
                    bytes.push(tempFirstPart);
                    break;
                case "setIntegralGain":
                    var ki = Math.round(input.data.setIntegralGain * 131072);
                    var kiFirstPart = ki & 0xff;
                    var kiSecondPart = (ki >> 8) & 0xff;
                    var kiThirdPart = (ki >> 16) & 0xff;
                    bytes.push(0x3e);
                    bytes.push(kiThirdPart);
                    bytes.push(kiSecondPart);
                    bytes.push(kiFirstPart);
                    break;
                case "getIntegralGain":
                    bytes.push(0x3d);
                    break;
                case "setPiRunPeriod":
                    bytes.push(0x41);
                    bytes.push(input.data.setPiRunPeriod);
                    break;
                case "getPiRunPeriod":
                    bytes.push(0x40);
                    break;
                case "setTempHysteresis":
                    var tempHysteresis = Math.round(input.data.setTempHysteresis * 10);
                    bytes.push(0x43);
                    bytes.push(tempHysteresis);
                    break;
                case "getTempHysteresis":
                    bytes.push(0x42);
                    break;
                case "setOpenWindowPrecisely":
                    var enabledValue = input.data.setOpenWindowPrecisely.enabled ? 1 : 0;
                    var duration = parseInt(input.data.setOpenWindowPrecisely.duration, 10) / 5;
                    var delta = Math.round(input.data.setOpenWindowPrecisely.delta * 10);
                    bytes.push(0x45);
                    bytes.push(enabledValue);
                    bytes.push(duration);
                    bytes.push(delta);
                    break;
                case "getOpenWindowPrecisely":
                    bytes.push(0x46);
                    break;
                case "setForceAttach":
                    bytes.push(0x47);
                    bytes.push(input.data.setForceAttach);
                    break;
                case "getForceAttach":
                    bytes.push(0x48);
                    break;
                case "sendCustomHexCommand":
                    var sendCustomHexCommand = input.data.sendCustomHexCommand;
                    for (i = 0; i < sendCustomHexCommand.length; i += 2) {
                        var byte = parseInt(sendCustomHexCommand.substr(i, 2), 16);
                        bytes.push(byte);
                    }
                    break;
                default:
                    break;
            }
        }
    }

    return bytes
}
// example downlink commands
// {"getOperationalMode":""} --> 0x18
// {"setTargetTemperature":20} --> 0x0E14
// {"setTemperatureRange":{"min":15,"max":21}} --> 0x080F15
// {"setChildLock":true} --> 0701
// {"sendCustomHexCommand":"080F15"} --> 0x080F15
// {"setOpenWindow":{"enabled": true, "closeTime": 20 , "delta": 3, "motorPosition": 540}}  --> 0x0601041C23

♨️
⬇️
Operational modes & temperature control algorithms