Safecast use a modified NEMA format on the bGeigieNano for writing logfiles, and when dumping data over the serial port to the XBee interface. I need to parse this for a project, so these are my notes on the format. The line of code that generates the NEMA string follows, and is found here:
sprintf_P(buf, PSTR("$%s,%04d,%02d-%02d-%02dT%02d:%02d:%02dZ,%ld,%ld,%ld,%c,%s,%c,%s,%c,%s,%c,%d,%ld"), \
NANO_HEADER, \
config.device_id, \
year, month, day, \
hour, minute, second, \
cpm, \
cpb, \
total_count, \
geiger_status, \
lat, NS,\
lon, WE,\
strbuffer, \
gps_status, \
nbsat == TinyGPS::GPS_INVALID_SATELLITES ? 0 : nbsat, \
precission == TinyGPS::GPS_INVALID_HDOP ? 0 : precission);
Here are a few example strings I captured (though in these strings the reading is marked as VOID):
$BNRDD,0000,2015-01-06T13:49:32Z,0,0,0,V,0000.0000,N,00000.0000,E,0.00,V,0,0*4A
$BNRDD,0000,2015-01-06T13:49:55Z,0,0,0,V,3537.2793,N,13938.0878,E,110.60,A,3,558B
$BNRDD,0000,2015-01-06T13:50:23Z,0,0,0,V,3537.2776,N,13938.0777,E,105.20,A,4,3036
$BNRDD,0000,2015-01-06T13:52:58Z,15,15,15,V,3537.2776,N,13938.0735,E,100.80,A,4,3
$BNRDD,0000,2015-01-06T13:53:05Z,28,13,28,V,3537.2778,N,13938.0740,E,100.70,A,4,F
$BNRDD,0000,2015-01-06T13:53:10Z,33,5,33,V,3537.2779,N,13938.0744,E,100.70,A,4,39
$BNRDD,0000,2015-01-06T13:53:16Z,89,56,89,V,3537.2776,N,13938.0740,E,100.60,A,4,0
$BNRDD,0000,2015-01-06T13:53:21Z,128,39,128,V,3537.2771,N,13938.0732,E,100.60,A,F
$BNRDD,0000,2015-01-06T13:53:27Z,128,0,128,V,3537.2777,N,13938.0743,E,100.50,A,40
$BNRDD,0000,2015-01-06T13:53:32Z,128,0,128,V,3537.2775,N,13938.0751,E,100.30,A
The format can be summarized as follows (values in brackets indicate variables):
$BNRDD,[4digit_device_id],[ISO8601_time],[cpm],[cpb],[total_count],[geiger_status],[lat],[NS],[long],[WE],[altitude],[gps_status],[nbsat],[precision]
4digit_device_id: This defaults to 0000, but can be set from “did” in SAFECAST.TXT on the SD card.
cpm: Count per minute. I don’t believe this is the count over the last min, but rather an average using a variable window size dependent on the current rate.
cpb: As I understand “count per bin”. I think count in the last time period (last second by default?)
total_count: Total event count since the device was turned on.
geiger_status: The device needs to acquire at least 12 windows of data until it is considered valid. “V” for VOID or “A” for available.
lat: Latitude float, 4.4
NS: N or S, North or South
long: Longitude 4.4
WE: W or E, West or East
altitude: altitude
gps_status: “V” for VOID or “A” for available.
nbsat: Satellite count
precision: GPS precision
There’s also a Safecast JSON format. This is used by the Safecast webservice API to post data. It looks like this:
{"longitude":"111.1111","latitude":"11.1111","device_id":"47","value":"63","unit":"cpm"}
The following C function converts from NEMA to JSON format, it also adds a “captured_at” JSON field with the time shown in the NEMA string. captured_at is used by the safecast API when measurements are download, but I don’t think it’s normal present when a measurement is posted. The function also returns true if the measurement is valid and false if it’s not. The code has only been briefly tested, please comment with fixes:
#include <stdio.h>
#include <string.h>
// json_string needs to be preallocated and large enough to hold the output
int safecast_nema2json(const char *nema_string,char *json_string) {
int device_id;
char iso_timestr[50];
int cpm;
int cpb;
int total_count;
char geiger_status;
float latitude;
char NorS;
float longitude;
char WorE;
float altitude;
char gps_status;
int nbsat;
char precision[50];
iso_timestr[0]=0;
nsscanf(nema_string,
"$BNRDD,%04d,%[^,],%d,%d,%d,%c,%f,%c,%f,%c,%f,%c,%d,%s",
&device_id,
iso_timestr,
&cpm,
&cpb,
&total_count,
&geiger_status,
&latitude,
&NorS,
&longitude,
&WorE,
&altitude,
&gps_status,
&nbsat,
precision);
if(NorS == 'S') latitude = 0-latitude;
if(WorE == 'W') longitude = 0-longitude;
sprintf(json_string,"{\"captured_at\":\"%s\",\"device_id\":\"%d\",\"value\":\"%d\",\"unit\":\"cpm\", \"longitude\":\"%f\", \"latitude\":\"%f\" }\n", iso_timestr, device_id,cpm, longitude,latitude);
if((gps_status == 'A') && (geiger_status == 'A')) return 1;
else return 0;
}
int main() {
char json[1024];
int safecast_nema_valid = safecast_nema2json("$BNRDD,0101,2015-01-06T15:11:28Z,11,12,128,V,3537.2618,N,13938.0256,E,40.70,A,5,138*64",json);
if( safecast_nema_valid) printf("VALIDJSON: %s\n",json);
if(!safecast_nema_valid) printf("INVALIDJSON: %s\n",json);
}