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);
}