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

⬆️T-Valve Uplink decoder

Universal Decoder:

Supports: The Thinks Network, Milesight, DataCake, Chirpstack

// --- DataCake / Loriot / ChirpStack v3 / 
function Decoder(bytes, port) {
  return decodeUplink({ bytes: bytes, fPort: port }).data;
}

// --- Milesight ---
function Decode(port, bytes) {
  return decodeUplink({ bytes: bytes, fPort: port }).data;
}

// --- The Things Stack v3 / TTN V3 / TTI / ChirpStack v4 / Main ---
function decodeUplink(input) {
  var bytes = input.bytes;
  var data = {};
  var messageTypes = ['keepalive', 'testButtonPressed', 'floodDetected', 'controlButtonPressed', 'fraudDetected'];

  // 2-byte keepalive (short package)
  function shortPackage(byteArray, data) {
    data.reason = 'keepalive';
    data.waterTemp = (byteArray[0] & 0xFF) / 2;                 // byte 0, /2
    data.valveState = !!(byteArray[1] & 0x80);                  // byte 1, bit 7
    data.ambientTemp = ((byteArray[1] & 0x7F) - 20) / 2;        // byte 1, bits 6:0, -20, /2
    return data;
  }

  // 5-byte keepalive (long package / response header)
  function longPackage(byteArray, data) {
    data.reason = messageTypes[(byteArray[0] >> 5) & 0x7];      // bits 7:5
    data.boxTamper = !!(byteArray[0] & (1 << 3));               // bit 3
    data.floodDetectionWireState = !!(byteArray[0] & (1 << 2)); // bit 2
    data.flood = !!(byteArray[0] & (1 << 1));                   // bit 1
    data.magnet = !!(byteArray[0] & 1);                         // bit 0
    data.alarmValidated = !!(byteArray[1] & (1 << 7));          // bit 7
    data.manualOpenIndicator = !!(byteArray[1] & (1 << 6));     // bit 6
    data.manualCloseIndicator = !!(byteArray[1] & (1 << 5));    // bit 5
    data.manualControl = {
      enableOpen: !!(byteArray[1] & (1 << 6)),
      enableClose: !!(byteArray[1] & (1 << 5))
    };
    data.deviceVersions = { software: byteArray[1] & 0x1F, hardware: 0 }; // bits 4:0
    data.closeTime = byteArray[2];                              // byte 2
    data.openTime = byteArray[3];                               // byte 3
    data.openCloseTime = { openingTime: byteArray[3], closingTime: byteArray[2] };
    data.battery = ((byteArray[4] * 8) + 1600) / 1000;          // byte 4
    return data;
  }

  // command-answer parsing -- case IDs from commandsReadingHelper.ts (DeviceType.TValve)
  function handleResponse(bytes, data) {
    var commands = bytes.map(function (byte) {
      return ("0" + byte.toString(16)).substr(-2);
    });
    var command_len = 0;
    commands.map(function (command, i) {
      switch (command) {
        case '04': // device versions (shared)
          command_len = 2;
          data.deviceVersions = {
            hardware: Number(commands[i + 1]),
            software: Number(commands[i + 2])
          };
          break;
        case '0e': // open/close time extended
          command_len = 4;
          var openingTime = (parseInt(commands[i + 1], 16) << 8) | parseInt(commands[i + 2], 16);
          var closingTime = (parseInt(commands[i + 3], 16) << 8) | parseInt(commands[i + 4], 16);
          data.openCloseTimeExtended = { openingTime: Number(openingTime), closingTime: Number(closingTime) };
          break;
        case '0f': // emergency openings
          command_len = 1;
          data.emergencyOpenings = parseInt(commands[i + 1], 16);
          break;
        case '10': // flood alarm time
          command_len = 1;
          data.floodAlarmTime = parseInt(commands[i + 1], 16);
          break;
        case '11': // working voltage -> mV
          command_len = 1;
          data.workingVoltage = (parseInt(commands[i + 1], 16) * 8) + 1600;
          break;
        case '12': // keepalive time
          command_len = 1;
          data.keepAliveTime = parseInt(commands[i + 1], 16);
          break;
        case '13': // device flood sensor
          command_len = 1;
          data.deviceFloodSensor = parseInt(commands[i + 1], 16);
          break;
        case '16': // join retry period -> minutes
          command_len = 1;
          data.joinRetryPeriod = (parseInt(commands[i + 1], 16) * 5) / 60;
          break;
        case '18': // uplink type
          command_len = 1;
          data.uplinkType = parseInt(commands[i + 1], 16);
          break;
        case '1a': // watchdog params
          command_len = 2;
          data.watchDogParams = {
            wdpC: commands[i + 1] === '00' ? false : parseInt(commands[i + 1], 16),
            wdpUc: commands[i + 2] === '00' ? false : parseInt(commands[i + 2], 16)
          };
          break;
        case 'a0': // FUOTA session address
          command_len = 4;
          var fuota_address_raw = commands[i + 1] + commands[i + 2] + commands[i + 3] + commands[i + 4];
          data.fuota = { fuota_address: parseInt(fuota_address_raw, 16), fuota_address_raw: fuota_address_raw };
          break;
        default:
          break;
      }
      commands.splice(i, command_len);
    });
    return data;
  }

  if (bytes.length == 5) {
    data = longPackage(bytes, data);
  } else if (bytes.length == 2) {
    data = shortPackage(bytes, data);
  } else {
    data = longPackage(bytes.slice(0, 5), data);
    bytes = bytes.slice(5);
    data = handleResponse(bytes, data);
  }
  return { data: data };
}

Last updated

Was this helpful?