RAK10701-P - Chirpstack Integration

Hello,

I bought the RAK10710 Field tester and I am trying to receive data. I used the Wistool to update the device and successfully connected to my Chirpstack LNS. I tried all the payloads in the documentation as well as in Github, but haven’t been able to decode the payload.

As I understood, the chirpstack LNS should send a downlink to the field tester so that it can display the number of gateways, distance, RSSI, etc… How can I configure this in Chirpstack v4?
Do I really have to use DataCake?

The field tester displays no information on the gateway, distance and other info.

Below is one of the payloads that I am receiving on my chirpstack console:

{
        "deduplicationId": "242278c3-c734-412c-a740-49d2dfbcd1e1",
        "time": "2024-04-25T18:53:42.408+00:00",
        "deviceInfo": {
            "tenantId": "d6ab71ff-f049-4cd3-9a3d-cfda6991feb8",
            "tenantName": "PV - Jumbo Phoenix",
            "applicationId": "52172547-bab9-439b-8ce5-43e1b53f20e0",
            "applicationName": "Field Tester",
            "deviceProfileId": "92941926-fa9b-4526-8324-a9abe9b5054c",
            "deviceProfileName": "Rak wireless Field Tester",
            "deviceName": "Field Tester RAK",
            "devEui": "ac1f09fffe0fd0ba",
            "deviceClassEnabled": "CLASS_A",
            "tags": {}
        },
        "devAddr": "00d5d5a7",
        "adr": true,
        "dr": 0,
        "fCnt": 1,
        "fPort": 1,
        "confirmed": true,
        "data": "TiTvKOWIA/ISBg==",
        "rxInfo": [
            {
                "gatewayId": "24e124fffef8b910",
                "uplinkId": 14594,
                "gwTime": "2024-04-25T18:53:42.408281+00:00",
                "nsTime": "2024-04-25T18:53:42.500856745+00:00",
                "timeSinceGpsEpoch": "1398106440.408s",
                "rssi": -31,
                "snr": 13,
                "channel": 1,
                "rfChain": 1,
                "location": {
                    "latitude": -20.279246145558563,
                    "longitude": 57.49518848348408
                },
                "context": "HoAwFw==",
                "metadata": {
                    "region_config_id": "eu868",
                    "region_common_name": "EU868"
                },
                "crcStatus": "CRC_OK"
            }
        ],
        "txInfo": {
            "frequency": 868300000,
            "modulation": {
                "lora": {
                    "bandwidth": 125000,
                    "spreadingFactor": 12,
                    "codeRate": "CR_4_5"
                }
            }
        }
    }

Please advise how to decode the received payload and enqueue the required downlink as well using chirpstack only.

Thank you.

Welcome to the forum @Yougesh

The RAK10701 will not work with Chirpstack alone. It requires a backend that analyzes the received data and creates a downlink to the device.

See RAK10701-P Field Tester Pro Guide for Chirpstack

Can I use PHP as a backend to decode and analyze the data and send a downlink via chirpstack to the Field Tester?

That is:

Uplink:

RAK10701 → Gateway → Chirpstack → PHP Backend

Downlink:
PHP Backend → Chirpstack → Gateway → RAK10701

Will it work?
If yes, please send me details for the raw payload decoder. I’ll develop the decoder in php and also, the encoder format for the downlink.

Thanks to advise.

Payloads and example decoders are in the link I sent you. Just scroll down

Hello,

I still haven’t been able to display data on my Field Tester Pro.
I am using chirpstack and datacake.

Please see all details - Steps from tester guide and also copied updated decoders from RakWireless Github.

My Gateway on Chirpstack:

Field Tester:

Codec in Chirpstack:

// Decode decodes an array of bytes into an object.
//  - fPort contains the LoRaWAN fPort number
//  - bytes is an array of bytes, e.g. [225, 230, 255, 0]
//  - variables contains the device variables e.g. {"calibration": "3.5"} (both the key / value are of type string)
// The function must return an object, e.g. {"temperature": 22.5}
function Decode(fPort, bytes, variables) {
	var decoded = {};
	// avoid sending Downlink ACK to integration (Cargo)
	if (fPort === 1) {
		var lonSign = (bytes[0] >> 7) & 0x01 ? -1 : 1;
		var latSign = (bytes[0] >> 6) & 0x01 ? -1 : 1;

		var encLat = ((bytes[0] & 0x3f) << 17) +
			(bytes[1] << 9) +
			(bytes[2] << 1) +
			(bytes[3] >> 7);

		var encLon = ((bytes[3] & 0x7f) << 16) +
			(bytes[4] << 8) +
			bytes[5];

		var hdop = bytes[8] / 10;
		var sats = bytes[9];

		var maxHdop = 2;
		var minSats = 5;

		if ((hdop < maxHdop) && (sats >= minSats)) {
			// Send only acceptable quality of position to mappers
			decoded.latitude = latSign * (encLat * 108 + 53) / 10000000;
			decoded.longitude = lonSign * (encLon * 215 + 107) / 10000000;
			decoded.altitude = ((bytes[6] << 8) + bytes[7]) - 1000;
			decoded.accuracy = (hdop * 5 + 5) / 10
			decoded.hdop = hdop;
			decoded.sats = sats;
		} else {
			decoded.error = "Need more GPS precision (hdop must be <" + maxHdop +
				" & sats must be >= " + minSats + ") current hdop: " + hdop + " & sats:" + sats;
			decoded.latitude = latSign * (encLat * 108 + 53) / 10000000;
			decoded.longitude = lonSign * (encLon * 215 + 107) / 10000000;
			decoded.altitude = ((bytes[6] << 8) + bytes[7]) - 1000;
			decoded.accuracy = (hdop * 5 + 5) / 10
			decoded.hdop = hdop;
			decoded.sats = sats;
		}
		return decoded;
	}
	return null;

}


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

Sample uplink:

{
        "deduplicationId": "89d1ee68-9353-4e94-b2fe-f5a9a21abbf9",
        "time": "2025-02-12T10:01:33.445+00:00",
        "deviceInfo": {
            "tenantId": "ea6ab5f4-ad75-40c5-9a56-093ab4089ffc",
            "tenantName": "Demo",
            "applicationId": "ee4a686c-7191-478a-a6ce-afc675d9c22e",
            "applicationName": "Field Tester",
            "deviceProfileId": "3c243fe9-cffd-4275-9c20-f1f64313f8d8",
            "deviceProfileName": "RakWireless Field Tester",
            "deviceName": "Field Tester",
            "devEui": "ac1f09fffe0fd0ba",
            "deviceClassEnabled": "CLASS_A",
            "tags": {}
        },
        "devAddr": "0150ddbe",
        "adr": true,
        "dr": 5,
        "fCnt": 53,
        "fPort": 1,
        "confirmed": true,
        "data": "TlONKNE3BZsHDQ==",
        "object": {
            "longitude": 57.5124892,
            "accuracy": 0.85,
            "altitude": 435,
            "latitude": -20.2800941,
            "sats": 13,
            "hdop": 0.7
        },
        "rxInfo": [
            {
                "gatewayId": "24e124fffef9e711",
                "uplinkId": 11034,
                "gwTime": "2025-02-12T10:01:33.445062+00:00",
                "nsTime": "2025-02-12T10:01:33.566017330+00:00",
                "timeSinceGpsEpoch": "1423389711.445s",
                "rssi": -97,
                "snr": 12.5,
                "channel": 7,
                "location": {
                    "latitude": -20.28009,
                    "longitude": 57.51255,
                    "altitude": 432
                },
                "context": "fBIWPQ==",
                "metadata": {
                    "region_config_id": "eu868",
                    "region_common_name": "EU868"
                },
                "crcStatus": "CRC_OK"
            }
        ],
        "txInfo": {
            "frequency": 867900000,
            "modulation": {
                "lora": {
                    "bandwidth": 125000,
                    "spreadingFactor": 7,
                    "codeRate": "CR_4_5"
                }
            }
        }
    }

Payload Decoder in DataCake:

function distance(lat1, lon1, lat2, lon2) {
    if ((lat1 == lat2) && (lon1 == lon2)) {
        return 0;
    }
    else {
        var radlat1 = Math.PI * lat1 / 180;
        var radlat2 = Math.PI * lat2 / 180;
        var theta = lon1 - lon2;
        var radtheta = Math.PI * theta / 180;
        var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
        if (dist > 1) {
            dist = 1;
        }
        dist = Math.acos(dist);
        dist = dist * 180 / Math.PI;
        dist = dist * 60 * 1.1515;
        dist = dist * 1.609344;
        return dist;
    }
}

function Decoder(bytes, fPort) {
    var decoded = {};
    // avoid sending Downlink ACK to integration (Cargo)
    if (fPort === 1) {
        var lonSign = (bytes[0] >> 7) & 0x01 ? -1 : 1;
        var latSign = (bytes[0] >> 6) & 0x01 ? -1 : 1;

        var encLat = ((bytes[0] & 0x3f) << 17) +
            (bytes[1] << 9) +
            (bytes[2] << 1) +
            (bytes[3] >> 7);

        var encLon = ((bytes[3] & 0x7f) << 16) +
            (bytes[4] << 8) +
            bytes[5];

        var hdop = bytes[8] / 10;
        var sats = bytes[9];

        var maxHdop = 2;
        var minSats = 5;

        if ((hdop < maxHdop) && (sats >= minSats)) {
            // Send only acceptable quality of position to mappers
            decoded.latitude = latSign * (encLat * 108 + 53) / 10000000;
            decoded.longitude = lonSign * (encLon * 215 + 107) / 10000000;
            decoded.altitude = ((bytes[6] << 8) + bytes[7]) - 1000;
            decoded.accuracy = (hdop * 5 + 5) / 10
            decoded.hdop = hdop;
            decoded.sats = sats;
            decoded.location = "(" + decoded.latitude + "," + decoded.longitude + ")";
        } else {
            decoded.error = "Need more GPS precision (hdop must be <" + maxHdop +
                " & sats must be >= " + minSats + ") current hdop: " + hdop + " & sats:" + sats;
            decoded.latitude = latSign * (encLat * 108 + 53) / 10000000;
            decoded.longitude = lonSign * (encLon * 215 + 107) / 10000000;
            decoded.altitude = ((bytes[6] << 8) + bytes[7]) - 1000;
            decoded.accuracy = (hdop * 5 + 5) / 10
            decoded.hdop = hdop;
            decoded.sats = sats;
            decoded.location = "(" + decoded.latitude + "," + decoded.longitude + ")";
        }
        //      decoded.raw = rawPayload.uplink_message.rx_metadata[0].location;
        decoded.num_gw = normalizedPayload.gateways.length;
        decoded.minRSSI = 0;
        decoded.maxRSSI = 0;
        decoded.minSNR = 0;
        decoded.maxSNR = 0;
        decoded.minDistance = 0;
        decoded.maxDistance = 0;

        var server_type = 0;
        // Check if payload comes from TTN
        if (typeof (rawPayload.uplink_message) != "undefined") {
            console.log("Found TTN format");
            server_type = 1;
        }
        // Check if payload comes from Helium
        else if (typeof (rawPayload.hotspots) != "undefined") {
            console.log("Found Helium format");
            server_type = 2;
        }
        // Check if payload comes from Chirpstack
        else if (typeof (rawPayload.rxInfo) != "undefined") {
            console.log("Found Chirpstack format");
            server_type = 3;
            decoded.is_chirpstack = 1;
        }
        else {
            console.log("Unknown raw format");
        }

        var gw_lat = {};
        var gw_long = {};

        decoded.num_gw = 0;
        for (idx_tst = 0; idx_tst < 10; idx_tst++)
        {
            if (typeof (normalizedPayload.gateways[idx_tst]) != "undefined")
            {
                console.log("Found gateway with IDX " + idx_tst);
                decoded.num_gw += 1;
            }
        }

        for (idx = 0; idx < decoded.num_gw; idx++) {
            var new_rssi = (!!normalizedPayload.gateways && !!normalizedPayload.gateways[idx] && normalizedPayload.gateways[idx].rssi) || 0;
            var new_snr = (!!normalizedPayload.gateways && !!normalizedPayload.gateways[idx] && normalizedPayload.gateways[idx].snr) || 0;
            if ((new_rssi < decoded.minRSSI) || (decoded.minRSSI == 0)) {
                decoded.minRSSI = new_rssi;
            }
            if ((new_rssi > decoded.maxRSSI) || (decoded.maxRSSI == 0)) {
                decoded.maxRSSI = new_rssi;
            }
            if ((new_snr < decoded.minSNR) || (decoded.minSNR == 0)) {
                decoded.minSNR = new_snr;
            }
            if ((new_snr > decoded.maxSNR) || (decoded.maxSNR == 0)) {
                decoded.maxSNR = new_snr;
            }

            // var gw_lat = 0.0;
            // var gw_long = 0.0;
            switch (server_type) {
                //TTN
                case 1:
                    gw_lat[idx] = rawPayload.uplink_message.rx_metadata[idx].location.latitude;
                    gw_long[idx] = rawPayload.uplink_message.rx_metadata[idx].location.longitude;
                    break;
                // Helium
                case 2:
                    gw_lat[idx] = rawPayload.hotspots[idx].lat;
                    gw_long[idx] = rawPayload.hotspots[idx].long;
                    break;
                // Chirpstack
                case 3:
                    gw_lat[idx] = rawPayload.rxInfo[idx].location.latitude;
                    gw_long[idx] = rawPayload.rxInfo[idx].location.longitude;
                    break;
                default:
                    console.log("Unknown LNS");
                    break;
            }

            console.log("IDX " + idx + " lat " + gw_lat[idx] + " long " + gw_long[idx]);
            // decoded.gw_lat[idx] = gw_lat;
            // decoded.gw_long[idx] = gw_long;

            // Calculate distance
            var new_distance = distance(gw_lat[idx], gw_long[idx], decoded.latitude, decoded.longitude);
            if ((new_distance < decoded.minDistance) || (decoded.minDistance == 0)) {
                decoded.minDistance = new_distance * 1000;
            }
            if ((new_distance > decoded.maxDistance) || (decoded.maxDistance == 0)) {
                decoded.maxDistance = new_distance * 1000;
            }
        }

        switch (decoded.num_gw) {
            case 20:
                decoded.hotspot_10 = "(" + gw_lat[19] + "," + gw_long[19] + ")";
            case 19:
                decoded.hotspot_09 = "(" + gw_lat[18] + "," + gw_long[18] + ")";
            case 18:
                decoded.hotspot_08 = "(" + gw_lat[17] + "," + gw_long[17] + ")";
            case 17:
                decoded.hotspot_07 = "(" + gw_lat[16] + "," + gw_long[16] + ")";
            case 16:
                decoded.hotspot_06 = "(" + gw_lat[15] + "," + gw_long[15] + ")";
            case 15:
                decoded.hotspot_05 = "(" + gw_lat[14] + "," + gw_long[14] + ")";
            case 14:
                decoded.hotspot_04 = "(" + gw_lat[13] + "," + gw_long[13] + ")";
            case 13:
                decoded.hotspot_03 = "(" + gw_lat[12] + "," + gw_long[12] + ")";
            case 12:
                decoded.hotspot_02 = "(" + gw_lat[11] + "," + gw_long[11] + ")";
            case 11:
                decoded.hotspot_01 = "(" + gw_lat[10] + "," + gw_long[10] + ")";
            case 10:
                decoded.hotspot_10 = "(" + gw_lat[9] + "," + gw_long[9] + ")";
            case 9:
                decoded.hotspot_09 = "(" + gw_lat[8] + "," + gw_long[8] + ")";
            case 8:
                decoded.hotspot_08 = "(" + gw_lat[7] + "," + gw_long[7] + ")";
            case 7:
                decoded.hotspot_07 = "(" + gw_lat[6] + "," + gw_long[6] + ")";
            case 6:
                decoded.hotspot_06 = "(" + gw_lat[5] + "," + gw_long[5] + ")";
            case 5:
                decoded.hotspot_05 = "(" + gw_lat[4] + "," + gw_long[4] + ")";
            case 4:
                decoded.hotspot_04 = "(" + gw_lat[3] + "," + gw_long[3] + ")";
            case 3:
                decoded.hotspot_03 = "(" + gw_lat[2] + "," + gw_long[2] + ")";
            case 2:
                decoded.hotspot_02 = "(" + gw_lat[1] + "," + gw_long[1] + ")";
            case 1:
                decoded.hotspot_01 = "(" + gw_lat[0] + "," + gw_long[0] + ")";
            default:
                break;
        }

        decoded.maxMod = parseInt((decoded.maxDistance / 250), 10);
        decoded.minMod = parseInt((decoded.minDistance / 250), 10);
        decoded.maxDistance = parseInt((decoded.maxMod * 250), 10);
        decoded.minDistance = parseInt((decoded.minMod * 250), 10);
        if (decoded.maxDistance <= 1) {
            decoded.maxDistance = parseInt(250, 10);
        }
        if (decoded.minDistance <= 1) {
            decoded.minDistance = parseInt(250, 10);
        }
        return decoded;
    }
    return null;

}

DataCake Fields:

Downlink Encoder in Datacake downlink:

function Encoder(measurements, port) {
    var buf = [];
    buf[0] = 1;
    buf[1] = measurements.MINRSSI.value + 200;
    buf[2] = measurements.MAXRSSI.value + 200;
    // var temp = parseInt(measurements.MINMOD.value,10);
    if (measurements.MINMOD.value == 0) {
        measurements.MINMOD.value = 1;
    }
    console.log(measurements.MINMOD.value);
    buf[3] = measurements.MINMOD.value;
    if (measurements.MAXMOD.value == 0) {
        measurements.MAXMOD.value = 1;
    }
    buf[4] = measurements.MAXMOD.value;
    buf[5] = measurements.NUM_GW.value;
    return buf;
}

Debug Log in DataCake:

Sometimes, I receive this error in chirpstack:

The only data I can see on Chirpstack are:
image

Kindly advise if I am doing something wrong.

From your logs, it seems that Datacake is able to decode the payload.

Did you configure “Downlinks” in the device configuration in Datacake?

In the “Downlinks” configuration, make sure you selected Chirpstack Version 4(gRPC)

Hello,

Here is the downlink configuration in Datacake:

Datacake last update:

Datacake History Blank:

Data in Chirpstack: