Safecast NEMA format notes (bGeigieNano)
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); }