For the complete documentation index, see llms.txt. This page is also available as Markdown.

⬇️T-Valve Downlink encoder

Universal Decoder:

Supports: The Thinks Network, Milesight, DataCake, Chirpstack

// --- Milesight ---
function Encode(port, obj) {
  return encodeDownlink({ fPort: port, data: obj }).bytes;
}

// --- DataCake / generic ---
function Encoder(port, obj) {
  return encodeDownlink({ fPort: port, data: obj }).bytes;
}

// --- The Things Stack v3 / TTN V3 / TTI / ChirpStack v4 / ThingPark / Main ---
function encodeDownlink(input) {
  var bytes = [];
  for (var key in input.data) {
    switch (key) {
      // ---------- valve control ----------
      case "setValveState":
        // 0: open, 1: close
        bytes.push(0x0c);
        bytes.push(input.data.setValveState);
        break;
      case "setSingleTimeValveState":
        // { state: 0|1, time: 1..65535 }
        bytes.push(0x14);
        bytes.push(input.data.setSingleTimeValveState.state);
        bytes.push((input.data.setSingleTimeValveState.time >> 8) & 0xff);
        bytes.push(input.data.setSingleTimeValveState.time & 0xff);
        break;
      case "setOpenCloseTime":
        // { openingTime: 1..255, closingTime: 1..255 } (seconds)
        bytes.push(0x01);
        bytes.push(input.data.setOpenCloseTime.openingTime);
        bytes.push(input.data.setOpenCloseTime.closingTime);
        break;
      case "setOpenCloseTimeExtended":
        // { openingTime: 1..65535, closingTime: 1..65535 } (ms)
        bytes.push(0x0d);
        bytes.push((input.data.setOpenCloseTimeExtended.openingTime >> 8) & 0xff);
        bytes.push(input.data.setOpenCloseTimeExtended.openingTime & 0xff);
        bytes.push((input.data.setOpenCloseTimeExtended.closingTime >> 8) & 0xff);
        bytes.push(input.data.setOpenCloseTimeExtended.closingTime & 0xff);
        break;
      case "getOpenCloseTimeExtended":
        bytes.push(0x0e);
        break;

      // ---------- manual control / emergency ----------
      case "setManualControl":
        // { enableOpen: bool, enableClose: bool } -> bit1 close, bit0 open
        bytes.push(0x05);
        bytes.push((input.data.setManualControl.enableClose ? 2 : 0) | (input.data.setManualControl.enableOpen ? 1 : 0));
        break;
      case "setEmergencyOpenings":
        // { maxOpenings: 1..15 }
        bytes.push(0x04);
        bytes.push(input.data.setEmergencyOpenings.maxOpenings);
        break;
      case "getEmergencyOpenings":
        bytes.push(0x0f);
        break;

      // ---------- flood ----------
      case "setFloodAlarmTime":
        // { time: 1..255 }
        bytes.push(0x06);
        bytes.push(input.data.setFloodAlarmTime.time);
        break;
      case "getFloodAlarmTime":
        bytes.push(0x10);
        break;
      case "setDeviceFloodSensor":
        // { enabled: bool }
        bytes.push(0x0a);
        bytes.push(input.data.setDeviceFloodSensor.enabled ? 1 : 0);
        break;
      case "getDeviceFloodSensor":
        bytes.push(0x13);
        break;

      // ---------- working voltage ----------
      case "setWorkingVoltage":
        // { voltage: 1840..2500 } mV -> (voltage - 1600) / 8
        bytes.push(0x09);
        bytes.push(Math.round((input.data.setWorkingVoltage.voltage - 1600) / 8));
        break;
      case "getWorkingVoltage":
        bytes.push(0x11);
        break;

      // ---------- LED / buzzer ----------
      case "setLED":
        // { ledId: 1..4, behavior: 0..4, seconds: 1..255 }
        bytes.push(0x02);
        bytes.push(input.data.setLED.ledId);
        bytes.push(input.data.setLED.behavior);
        bytes.push(input.data.setLED.seconds);
        break;
      case "setBuzzer":
        // { volume, frequency, activeTime, onTime, offTime }; volume/freq packed into one byte
        bytes.push(0x03);
        bytes.push(((input.data.setBuzzer.volume << 4) | input.data.setBuzzer.frequency) & 0xff);
        bytes.push(input.data.setBuzzer.activeTime);
        bytes.push(Math.round(input.data.setBuzzer.onTime / 10));
        bytes.push(Math.round(input.data.setBuzzer.offTime / 10));
        break;

      // ---------- data requests / lifecycle ----------
      case "requestFullData":
        bytes.push(0x08);
        break;
      case "deactivateDevice":
        bytes.push(0x0b);
        break;

      // ---------- shared general commands (T-Valve-specific IDs) ----------
      case "setKeepAlive":
        // { time: 1..255 } minutes
        bytes.push(0x07);
        bytes.push(input.data.setKeepAlive);
        break;
      case "getKeepAliveTime":
        bytes.push(0x12);
        break;
      case "getDeviceVersions":
        bytes.push(0x04);
        break;
      case "setJoinRetryPeriod":
        // value in minutes (1..21) -> minutes*60/5
        bytes.push(0x15);
        bytes.push(Math.round((input.data.setJoinRetryPeriod * 60) / 5));
        break;
      case "getJoinRetryPeriod":
        bytes.push(0x16);
        break;
      case "setUplinkType":
        // "00": unconfirmed, "01": confirmed (or numeric 0/1)
        bytes.push(0x17);
        bytes.push(parseInt(input.data.setUplinkType, 16) || 0);
        break;
      case "getUplinkType":
        bytes.push(0x18);
        break;
      case "setWatchDogParams":
        // { confirmedUplinks: 0..255, unconfirmedUplinks: 0..255 }
        bytes.push(0x19);
        bytes.push(input.data.setWatchDogParams.confirmedUplinks);
        bytes.push(input.data.setWatchDogParams.unconfirmedUplinks);
        break;
      case "getWatchDogParams":
        bytes.push(0x1a);
        break;

      // ---------- always keep this passthrough ----------
      case "sendCustomHexCommand": {
        var hex = input.data.sendCustomHexCommand;
        for (var i = 0; i < hex.length; i += 2) {
          bytes.push(parseInt(hex.substr(i, 2), 16));
        }
        break;
      }
      default:
        break;
    }
  }

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

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

// example downlink commands
// {"setValveState":1}                                  --> 0x0C01      (close)
// {"setValveState":0}                                  --> 0x0C00      (open)
// {"setSingleTimeValveState":{"state":1,"time":3600}}  --> 0x14010E10
// {"setOpenCloseTime":{"openingTime":30,"closingTime":30}}            --> 0x011E1E
// {"setOpenCloseTimeExtended":{"openingTime":5000,"closingTime":5000}}--> 0x0D138813 88
// {"getOpenCloseTimeExtended":""}                      --> 0x0E
// {"setManualControl":{"enableOpen":true,"enableClose":true}}        --> 0x0503
// {"setEmergencyOpenings":{"maxOpenings":5}}           --> 0x0405
// {"getEmergencyOpenings":""}                          --> 0x0F
// {"setFloodAlarmTime":{"time":10}}                    --> 0x060A
// {"getFloodAlarmTime":""}                             --> 0x10
// {"setDeviceFloodSensor":{"enabled":true}}            --> 0x0A01
// {"getDeviceFloodSensor":""}                          --> 0x13
// {"setWorkingVoltage":{"voltage":2000}}               --> 0x0932      ((2000-1600)/8 = 50)
// {"getWorkingVoltage":""}                             --> 0x11
// {"setLED":{"ledId":1,"behavior":2,"seconds":5}}      --> 0x02010205
// {"setBuzzer":{"volume":5,"frequency":3,"activeTime":10,"onTime":100,"offTime":100}} --> 0x03530A0A0A
// {"requestFullData":""}                               --> 0x08
// {"deactivateDevice":""}                              --> 0x0B
// {"setKeepAlive":5}                                   --> 0x0705
// {"getKeepAliveTime":""}                              --> 0x12
// {"getDeviceVersions":""}                             --> 0x04
// {"setJoinRetryPeriod":10}                            --> 0x1578      (minutes*60/5)
// {"getJoinRetryPeriod":""}                            --> 0x16
// {"setUplinkType":"01"}                               --> 0x1701      (confirmed)
// {"getUplinkType":""}                                 --> 0x18
// {"setWatchDogParams":{"confirmedUplinks":20,"unconfirmedUplinks":10}} --> 0x19140A
// {"getWatchDogParams":""}                             --> 0x1A
// {"sendCustomHexCommand":"080F15"}                    --> 0x080F15
//

Last updated

Was this helpful?