⬆️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?