SensativeAB Strip MS-H

Hi,

anyone use Sensative Strips with ChirpStack 3 or 4 (for me its the latest v4 ChirpStack)?
The device joins and send status packets but the payload is not decoded - i use the TTN Device Template for the strip:

From the Event view i see the up packtes:

There is no error in the Events page as it is with an non functional decoder.

This is the Detail window for the packet from 12:06:

Any hints where to start searching for an solution?

Thanks in advance.

PS: One more common question - is there a chance to get the gateway name into the detail information page? With chirpstack v3 it was included.

Are 100% sure the payload is not decoded (you mean decrypted right)? Iā€™m only asking to exclude the possibility that your device is just sending something other than what you are expecting.

Hi @1337,

i mean decoded with the payload decoder assigned by the device profile - maybe i mix up some things but i expect the decoded values in the Events View up packet as with other devices, dectypt should be a part of the network/application server imho and should work (i case of otaa) with the right AppKey?!
Or did get your answer wrong?

I switched to TTN and there the built in decoder doesnt work too:

If i switch to the decoder provided from sensative (https://gitlab.com/sensative/strips-lora-translator-ttn
the data is decoded:

Here is the decoder after some modification for chirpstack:


// v3 to v4 compatibility wrapper
function decodeUplink(input) {
	return {
		data: Decode(input.fPort, input.bytes, input.variables)
	};
}

function encodeDownlink(input) {
	return {
		bytes: Encode(input.fPort, input.data, input.variables)
	};
}

function Decode(port, bytes) {
	// Decode an uplink message from a buffer
	// (array) of bytes to an object of fields.
	
	function decodeFrame(type, target)
	{
		switch(type & 0x7f) {
			case 0:
				target.emptyFrame = {};
				break;
			case 1: // Battery 1byte 0-100%
				target.battery = {};
				target.battery = bytes[pos++];
				break;
			case 2: // TempReport 2bytes 0.1degree C
				target.temperature = {}; // celcius 0.1 precision
				target.temperature.value = ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10;
				break;
			case 3:
				// Temp alarm
				target.tempAlarm = {};  // sends alarm after >x<
				target.tempAlarm.highAlarm = !!(bytes[pos] & 0x01); // boolean
				target.tempAlarm.lowAlarm = !!(bytes[pos] & 0x02);  // boolean
				pos++;
				break;
			case 4: // AvgTempReport 2bytes 0.1degree C
				target.averageTemperature = {};
				target.averageTemperature.value = ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10;
				break;
			case 5:
				// AvgTemp alarm
				target.avgTempAlarm = {}; // sends alarm after >x<
				target.avgTempAlarm.highAlarm = !!(bytes[pos] & 0x01); // boolean
				target.avgTempAlarm.lowAlarm = !!(bytes[pos] & 0x02);  // boolean
				pos++;
				break;
			case 6: // Humidity 1byte 0-100% in 0.5%
				target.humidity = {};
				target.humidity.value = bytes[pos++] / 2; // relativeHumidity percent 0,5
				break;
			case 7: // Lux 2bytes 0-65535lux
				target.lux = {};
				target.lux.value = ((bytes[pos++] << 8) | bytes[pos++]); // you can  the lux range between two sets (lux1 and 2)
				break;
			case 8: // Lux 2bytes 0-65535lux
				target.lux2 = {};
				target.lux2.value = ((bytes[pos++] << 8) | bytes[pos++]);
				break;
			case 9: // DoorSwitch 1bytes binary
				target.door = {};
				target.door.value = !!bytes[pos++]; // false = door open, true = door closed
				break;
			case 10: // DoorAlarm 1bytes binary
				target.doorAlarm = {};
				target.doorAlarm.value = !!bytes[pos++]; // boolean true = alarm
				break;
			case 11: // TamperReport 1bytes binary (was previously TamperSwitch)
				target.tamperReport = {};
				target.tamperReport.value = !!bytes[pos++];
				break;
			case 12: // TamperAlarm 1bytes binary
				target.tamperAlarm = {};
				target.tamperAlarm.value = !!bytes[pos++];
				break;
			case 13: // Flood 1byte 0-100%
				target.flood = {};
				target.flood.value = bytes[pos++]; // percentage, relative wetness
				break;
			case 14: // FloodAlarm 1bytes binary
				target.floodAlarm = {};
				target.floodAlarm.value = !!bytes[pos++]; // boolean, after >x<
				break;
			case 15: // oilAlarm 1bytes analog
				target.oilAlarm = {};
				target.oilAlarm.value = bytes[pos++];
				target.foilAlarm = {}; // Compatibility with older strips
				target.foilAlarm.value = !!bytes[pos++];
				break;
			case 16: // UserSwitch1Alarm, 1 byte digital
				target.userSwitch1Alarm = {};
				target.userSwitch1Alarm.value = !!bytes[pos++];
				break;
			case 17: // DoorCountReport, 2 byte analog
				target.doorCount = {};
				target.doorCount.value = ((bytes[pos++] << 8) | bytes[pos++]);
				break;
			case 18: // PresenceReport, 1 byte digital
				target.presence = {};
				target.presence.value = !!bytes[pos++];
				break;
			case 19: // IRProximityReport
				target.IRproximity = {};
				target.IRproximity.value = ((bytes[pos++] << 8) | bytes[pos++]);
				break;
			case 20: // IRCloseProximityReport, low power
				target.IRcloseproximity = {};
				target.IRcloseproximity.value = ((bytes[pos++] << 8) | bytes[pos++]);
				break;
			case 21: // CloseProximityAlarm, something very close to presence sensor
				target.closeProximityAlarm = {};
				target.closeProximityAlarm.value = !!bytes[pos++];
				break;
			case 22: // DisinfectAlarm
				target.disinfectAlarm = {};
				target.disinfectAlarm.value = bytes[pos++];
					if (target.disinfectAlarm.value === 0) target.disinfectAlarm.state='dirty';
					if (target.disinfectAlarm.value == 1) target.disinfectAlarm.state='occupied';
					if (target.disinfectAlarm.value == 2) target.disinfectAlarm.state='cleaning';
					if (target.disinfectAlarm.value == 3) target.disinfectAlarm.state='clean';
				break;
			case 80:
				target.humidity = {};
				target.humidity.value = bytes[pos++] / 2;
				target.temperature = {};
				target.temperature = ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10;
				break;
			case 81:
				target.humidity = {};
				target.humidity.value = bytes[pos++] / 2;
				target.averageTemperature = {};
				target.averageTemperature.value = ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10;
				break;
			case 82:
				target.door = {};
				target.door.value = !!bytes[pos++]; // true = door open, false = door closed
				target.temperature = {};
				target.temperature = ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10;
				break;
			case 112: // Capacitance Raw Sensor Value 2bytes 0-65535
				target.capacitanceFlood = {};
				target.capacitanceFlood.value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore
				break;
			case 113: // Capacitance Raw Sensor Value 2bytes 0-65535
				target.capacitancePad = {};
				target.capacitancePad.value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore
				break;
			case 110:
				pos += 8;
				break;
			case 114: // Capacitance Raw Sensor Value 2bytes 0-65535
				target.capacitanceEnd = {};
				target.capacitanceEnd.value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore
				break;
		}
	}
	
	var decoded = {};
	var pos = 0;
	var type;
	
	switch(port) {
		case 1:
		if(bytes.length < 2) {
			decoded.error = 'Wrong length of RX package';
			break;
		}
		decoded.historySeqNr = (bytes[pos++] << 8) | bytes[pos++];
		decoded.prevHistSeqNr = decoded.historySeqNr;
		while(pos < bytes.length) {
			type = bytes[pos++];
			if(type & 0x80)
			decoded.prevHistSeqNr--;
			decodeFrame(type, decoded);
		}
		break;
		
		case 2:
		var now = new Date();
		decoded.history = {};
		if(bytes.length < 2) {
			decoded.history.error = 'Wrong length of RX package';
			break;
		}	  
		var seqNr = (bytes[pos++] << 8) | bytes[pos++];
		while(pos < bytes.length) {
			decoded.history[seqNr] = {};
			decoded.history.now = now.toUTCString();
			secondsAgo = (bytes[pos++] << 24) | (bytes[pos++] << 16) | (bytes[pos++] << 8) | bytes[pos++];
			decoded.history[seqNr].timeStamp = new Date(now.getTime() - secondsAgo*1000).toUTCString();
			type = bytes[pos++];
			decodeFrame(type, decoded.history[seqNr]);
			seqNr++;
		}
	}
	return decoded;
}

With success:
image

Regards

1 Like