-
-
Notifications
You must be signed in to change notification settings - Fork 412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SDS011 sensor data is sent on counterport, but this is not documented #681
Comments
Hey, is this still the case? I've switched from Cayenne to Plain for various reasons and appear to have lost the SDS011 data even though the BLE/WiFi data continues to flow. Looking at the decoder, I don't see anywhere that the SDS011 data is filtered out into the payload for port 1, so I'm assuming this needs to be added to the decoder somehow? |
@cyberman54 - apologies for the mention but just trying to work out the best way forward here. Should I revert to Cayenne format and map the Cayenne name to the actual sensor name, or is there an easy fix for the |
@proffalken could you put together a solution, and suggest it as PR here? |
@cyberman54 - I'd love too, but I'm struggling to know where to start. I can see from the code that we store the values and then add it to the payload but I can't work out from that code where in the payload it ends up (i.e. which byte it is contained in) If you (or someone else!) can point me in the right direction on working that bit out, I'm sure I can update the decoder for plain-text payloads accordingly, but my C++ isn't good enough to trace that all the way through and understand at which point of compiling the payyload this data is added. |
Hi @proffalken, 1-2 WiFi So in your case without GPS the SDS011 data start at byte 5 in the payload. @cyberman54
The order of the sensor values within the payload are fixed. If we have a config like 0x0000 0000 0001 0111 which means: WiFi, BLE, BME and SDS011 Than the payload byte positions are:
In this scenario the decoder can handle the payload automatically. What do you think? |
@commeco the problem with every change in the payload data structure is, that it would be a breaking change. So i think it's better not to touch it and solve this issue by adapting the decoder to the status quo of payload. |
@commeco - thanks, so the decoder needs to work out the length of the payload and then effectively "guess" where the values are based on how long it is and what it thinks is in there? @cyberman54 - completely understand your concerns here, presumably this is why the Cayenne payload is an option, so that the fields always show up with the same labels? If that's the case then it may make more sense for me to create a "translation layer" in my code that says For reference, I'm working on https://github.com/proffalken/ttn2otel which takes the payload from TTN, discards the metadata, and converts the values into OpenTelemetry metrics that can then be forwarded on to a platform such as Grafana Cloud or DataDog for further analysis. If I have control over the payload output, then I can just do a 1-1 mapping of the key pairs in the payload to the metrics, but if the names coming through are different to the data that is actually contained in the payload (which is the case with Cayenne as they don't have fields for PM2.5/PM10) then I need to add some kind of lookup, which I was hoping to avoid! Appreciate the input from both of you on this! |
Yes, you're right. Maybe i can solve this by a DEFINE key which distinguish between legacy payload structure and the new payload structure. Is that a way to go? |
@proffalken
(Untested. :) ) |
Thanks, I'll make that change in the next couple of days and play around with it, then commit it back once I've got it working. |
Finally managed to get around to this and with a bit of tweaking the code now looks as follows:
Unfortunately, this is returning an error: Should the call to |
@commeco - After some support from the TTN forum, I've got the following code that should decode this properly: <!DOCTYPE html>
<html>
<head>
<title>Payload formatter tester</title>
</head>
<body>
<h1>Payload formatter tester</h2>
<p>SDS011: pm25: 1.20, pm10: 2.30</p>
<p>SDS011: pm25: 1.20, pm10: 4.20</p>
<h2>Input</h2>
<pre id="positions"></pre>
<pre id="payloadAsHex"></pre>
<p>Port: <span id="port"></span>
<p>Size: <span id="size"></span>
<h2>Result</h2>
<pre id="result"></pre>
<script>
// #### Your stuff here ####
const payloadFromConsole = "000200082C2020342E322C2020312E32";
const fPort = 1;
function decodeUplink(input) {
var data = {};
if (input.fPort === 1) {
console.log("Port 1");
var i = 0;
if (input.bytes.length >= 2) {
data.wifi = (input.bytes[i++] << 8) | input.bytes[i++];
console.log("WiFi: "+ i);
}
if (input.bytes.length === 4 || input.bytes.length > 15) {
data.ble = (input.bytes[i++] << 8) | input.bytes[i++];
console.log("BLE: "+ i);
}
if (input.bytes.length >= 15) {
data.sds011 = String.fromCharCode(input.bytes[i]);
i++;
}
data.pax = 0;
if ('wifi' in data) {
data.pax += data.wifi;
}
if ('ble' in data) {
data.pax += data.ble;
}
}
if (input.fPort === 2) {
var i = 0;
data.voltage = ((input.bytes[i++] << 8) | input.bytes[i++]);
data.uptime = ((input.bytes[i++] << 56) | (input.bytes[i++] << 48) | (input.bytes[i++] << 40) | (input.bytes[i++] << 32) | (input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
data.cputemp = input.bytes[i++];
data.memory = ((input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
data.reset0 = input.bytes[i++];
data.restarts = ((input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
}
if (input.fPort === 4) {
var i = 0;
data.latitude = ((input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
data.longitude = ((input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
data.sats = input.bytes[i++];
data.hdop = (input.bytes[i++] << 8) | (input.bytes[i++]);
data.altitude = ((input.bytes[i++] << 8) | (input.bytes[i++]));
}
if (input.fPort === 5) {
var i = 0;
data.button = input.bytes[i++];
}
if (input.fPort === 7) {
var i = 0;
data.temperature = ((input.bytes[i++] << 8) | input.bytes[i++]);
data.pressure = ((input.bytes[i++] << 8) | input.bytes[i++]);
data.humidity = ((input.bytes[i++] << 8) | input.bytes[i++]);
data.air = ((input.bytes[i++] << 8) | input.bytes[i++]);
}
if (input.fPort === 8) {
var i = 0;
if (input.bytes.length >= 2) {
data.voltage = (input.bytes[i++] << 8) | input.bytes[i++];
}
}
if (input.fPort === 9) {
// timesync request
if (input.bytes.length === 1) {
data.timesync_seqno = input.bytes[0];
}
// epoch time answer
if (input.bytes.length === 5) {
var i = 0;
data.time = ((input.bytes[i++] << 24) | (input.bytes[i++] << 16) | (input.bytes[i++] << 8) | input.bytes[i++]);
data.timestatus = input.bytes[i++];
}
}
if (data.hdop) {
data.hdop /= 100;
data.latitude /= 1000000;
data.longitude /= 1000000;
}
data.bytes = input.bytes; // comment out if you do not want to include the original payload
data.port = input.fPort; // comment out if you do not want to include the port
return {
data: data,
warnings: [],
errors: []
};
}
// JS Payload support stuff - do NOT put this in to the TTS console
const toHexCount = byteArray => 'Position: ' + byteArray.map((x, i) => ('0' + i).slice(-2) ).join(' ');
const toHexStringX = byteArray => 'Payload: 0x' + Array.from(byteArray, byte => ('0' + byte.toString(16)).slice(-2)).join(' 0x')
function hexToBytes(hex) { let bytes = []; for (let c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)); return bytes; }
var payload = {}
payload.bytes = hexToBytes(payloadFromConsole);
payload.fPort = fPort;
result = decodeUplink(payload);
console.log("data", result.data);
document.getElementById("positions").innerHTML = toHexCount(payload.bytes); //payload.bytes.map((x, i) => ('0' + i).slice(-2) ).join(' ');
document.getElementById("payloadAsHex").innerHTML = toHexStringX(payload.bytes);
document.getElementById("port").innerHTML = payload.fPort;
document.getElementById("size").innerHTML = payload.bytes.length;
document.getElementById("result").innerHTML = JSON.stringify(result, null, 4);
</script>
</body>
</html> Unfortunately, when given the payload
I think I'm extracting the correct fields now because I've deleted the stuff that deals with GPS (I'm not using that here), but it's returning Any help you can provide would be welcome, I'll be posting the same over on the TTN forums as well. |
@proffalken |
It might be, but for now the following seems to work:
So I'll leave it running for a bit and see if it gives the expected results, then I'll look at switching to another payload if that doesn't work. |
Thanks for investigating this. If it is working, could you please raise a PR for the adapted payload decoder? I will then change it on TTN platform side for the predefined paxcounter device. |
It is working, but only if I remove the GPS code from the decoder. I'm not using GPS on any of my devices, so it's not an issue for me, but I don't want to break someone else's setup. I'm back at work now, and this is a "spare time" project, but I'll try and switch over to the Packed payload and see if that makes it easier to extract the data. If it does, I'll add back any changes that are needed there too, but it will probably be a few weeks before I get a chance to look at that. |
Either document this, or better handle SDS011 data like user sensor data.
The text was updated successfully, but these errors were encountered: