# Vicki Downlink Encoder

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:

{% content-ref url="/pages/-McOG5\_81zd9GO2yimcb" %}
[Operational modes & temperature control algorithms](/mclimate-lorawan-devices/devices/mclimate-vicki-lorawan/pre-46-vicki-lorawan-device-communication-protocol/operational-modes-and-temperature-control-algorithm.md)
{% endcontent-ref %}

## 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 "setValveOpenness": {
        bytes.push(0x4E);
        bytes.push(input.data.setValveOpenness);
        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
// {"setValveOpenness": 50} --> 0x4E32
// {"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 "setValveOpenness":
                    bytes.push(0x4E);
                    bytes.push(input.data.setValveOpenness);
                    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
// {"setValveOpenness": 50} --> 0x4E32
// {"sendCustomHexCommand":"080F15"} --> 0x080F15
// {"setOpenWindow":{"enabled": true, "closeTime": 20 , "delta": 3, "motorPosition": 540}}  --> 0x0601041C23

```

###


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mclimate.eu/mclimate-lorawan-devices/devices/mclimate-vicki-lorawan/decoding-and-bacnet-object-mapping/downlink-encoder.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
