Thanks JamesC for pointing me in the right direction!
For anyone trying the same in the future, this is (simplified) how I got it working with node.js:
// mqtt_msg is retrieved from MQTT tcp://croft.thethings.girovito.nl:1883
var decoded_data = new Buffer(mqtt_msg.data, 'base64');
// manually extract 24-bit latitude
var lat_val = decoded_data[8] << 16 | decoded_data[9] << 8 | decoded_data[10];
var lat = !(lat_val&0x800000) ? lat_val : ((0xffffff-lat_val+1)*-1)
// manually extract 24-bit longitude
var lon_val = decoded_data[11] << 16 | decoded_data[12] << 8 | decoded_data[13];
var lon = !(lon_val&0x800000) ? lon_val : ((0xffffff-lon_val+1)*-1);
mqtt_msg.sensors = {
'LED' : decoded_data.readUInt8(0), // 0: OFF, 1: ON
'Pressure' : decoded_data.readUInt16BE(1)/10, // in hPa
'Temperature' : decoded_data.readInt16BE(3)/100, // in degrees Celsius
'BarometricAltitude' : decoded_data.readInt16BE(5)/10, // in meters
'Battery' : (decoded_data.readUInt8(7) == 255) ? -1 : Math.round(decoded_data.readUInt8(7)/2.54), // Percentage (0-100), or -1 if not able to measure
'Latitude' : lat/Math.pow(2,23)*90, // latitude from -90 to 90
'Longitude' : lon/Math.pow(2,23)*180, // longitude from -180 to 180
'GPSAltitude' : decoded_data.readInt16BE(14) // in meters
};
An example MQTT message form croft:
{
"gatewayEui": "0000024B080602ED",
"nodeEui": "02949D84",
"time": "2015-12-31T11:48:41.910Z",
"rawData": "QISdlAIABQACxuiUx3tu6BJMaMffAk330x/sm8U=",
"data": "ACe4CIX7rltKGk4DqhP/+w=="
}
After processing with node.js:
{
"gatewayEui": "0000024B080602ED",
"nodeEui": "02949D84",
"time": "2015-12-31T11:48:41.910Z",
"rawData": "QISdlAIABQACxuiUx3tu6BJMaMffAk330x/sm8U=",
"data": "ACe4CIX7rltKGk4DqhP/+w==",
"sensors": {
"LED": 0,
"Pressure": 1016.8,
"Temperature": 21.81,
"BarometricAltitude": -110.6,
"Battery": 36,
"Latitude": 52.10349798202515,
"Longitude": 5.152995586395264,
"GPSAltitude": -5
}
}