Beispiel #1
0
static void process_trust_0007(const char * const string, const jsmntok_t * const tokens, const int index)
{
   char query[1024];
   char train_id[128], new_id[128];
   
   jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id));
   jsmn_find_extract_token(string, tokens, index, "revised_train_id", new_id, sizeof(new_id));

   time_t now = time(NULL);
   sprintf(query, "INSERT INTO trust_changeid VALUES(%ld, '%s', '%s')", now, train_id, new_id);
   db_query(query);
   
   return;
}
Beispiel #2
0
static void process_trust_0005(const char * const string, const jsmntok_t * const tokens, const int index)
{
   char query[1024];
   char train_id[128], stanox[128];
   
   jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id));
   jsmn_find_extract_token(string, tokens, index, "loc_stanox", stanox, sizeof(stanox));

   time_t now = time(NULL);
   sprintf(query, "INSERT INTO trust_cancellation VALUES(%ld, '%s', '', '', '%s', 1)", now, train_id, stanox);
   db_query(query);
   
   return;
}
Beispiel #3
0
static void process_vstp(const char * string, const jsmntok_t * tokens)
{
   char zs[128];

   stats[GoodMessage]++;

   jsmn_find_extract_token(string, tokens, 0, "transaction_type", zs, sizeof(zs));
   if(zs[0])
   {
      // printf("   Transaction type: \"%s\"", zs);
      if(!strcasecmp(zs, "Delete")) process_delete_schedule(string, tokens);
      else if(!strcasecmp(zs, "Create")) process_create_schedule(string, tokens, false);
      else if(!strcasecmp(zs, "Update")) process_update_schedule(string, tokens);
      else 
      {
         _log(MAJOR, "process_schedule():  Unrecognised transaction type \"%s\".", zs);
         jsmn_dump_tokens(body, tokens, 0);
         stats[NotTransaction]++;
      }
   }
   else
   {
      _log(MAJOR, "process_schedule():  Failed to determine transaction type.");
      jsmn_dump_tokens(body, tokens, 0);
      stats[NotTransaction]++;
   }
}
Beispiel #4
0
static void process_trust_0002(const char * string, const jsmntok_t * tokens, const int index)
{
   char query[1024];
   char train_id[128], reason[128], type[128], stanox[128];
   
   jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id));
   jsmn_find_extract_token(string, tokens, index, "canx_reason_code", reason, sizeof(reason));
   jsmn_find_extract_token(string, tokens, index, "canx_type", type, sizeof(type));
   jsmn_find_extract_token(string, tokens, index, "loc_stanox", stanox, sizeof(stanox));

   time_t now = time(NULL);
   sprintf(query, "INSERT INTO trust_cancellation VALUES(%ld, '%s', '%s', '%s', '%s', 0)", now, train_id, reason, type, stanox);
   db_query(query);
   
   return;
}
Beispiel #5
0
static void process_frame(const char * const body)
{
   jsmn_parser parser;
   qword elapsed = time_ms();
   
   jsmn_init(&parser);
   int r = jsmn_parse(&parser, body, tokens, NUM_TOKENS);
   if(r != 0) 
   {
      _log(MAJOR, "Parser result %d.  Message discarded.", r);
      stats[NotRecog]++;
   }
   else
   {
      size_t messages, i, index;
      // Is it an array?
      if(tokens[0].type == JSMN_ARRAY)
      {
         messages = tokens[0].size;
         index = 1;
         _log(DEBUG, "STOMP message is array of %d TD messages.", messages);
      }
      else
      {
         messages = 1;
         index = 0;
         _log(DEBUG, "STOMP message contains a single TD message.");
      }

      for(i=0; i < messages && run; i++)
      {
         char area_id[4];
         word describer;
         jsmn_find_extract_token(body, tokens, index, "area_id", area_id, sizeof(area_id));

         stats[GoodMessage]++;
         message_count++;

         for(describer = 0; describer < DESCRIBERS; describer++)
         {
            if(!strcasecmp(area_id, describers[describer]))
            {
               process_message(describer, body, index);
               stats[RelMessage]++;
               message_count_rel++;
               describer = DESCRIBERS;
            }
         }
         
         size_t message_ends = tokens[index].end;
          do  index++; 
         while ( tokens[index].start < message_ends && tokens[index].start >= 0 && index < NUM_TOKENS);
      }
   }
   elapsed = time_ms() - elapsed;
   if(debug || elapsed > 2500)
   {
      _log(MINOR, "Frame took %s ms to process.", commas_q(elapsed));
   }
}
Beispiel #6
0
static void process_timetable(const char * string, const jsmntok_t * tokens)
{
   char timestamp[32], zs[256];
   strcpy(zs, "Timetable information: ");
   jsmn_find_extract_token(string, tokens, 0, "timestamp", timestamp, sizeof(timestamp));
   if(timestamp[0])
   {
      time_t stamp = atoi(timestamp);
      strcat(zs, "Timestamp ");
      strcat(zs, time_text(stamp, true));

      _log(GENERAL, "%s.  Sequence number %s.", zs, commas_q(stats[Sequence]));
   }
   else
   {
      strcat(zs, "Timestamp not found.  Processing aborted.");
      _log(CRITICAL, zs);
      run = 0;
   }
}
Beispiel #7
0
static void process_schedule(const char * string, const jsmntok_t * tokens)
{
   char zs[128], zs1[1024];

   jsmn_find_extract_token(string, tokens, 0, "transaction_type", zs, sizeof(zs));
   if(zs[0])
   {
      // printf("   Transaction type: \"%s\"", zs);
      if(!strcasecmp(zs, "Delete"))      ;
      else if(!strcasecmp(zs, "Create")) process_create_schedule(string, tokens);
      else 
      {
         sprintf(zs1, "process_schedule():  Unrecognised transaction type \"%s\".", zs);
         _log(MAJOR, zs1);
      }
   }
   else
   {
      _log(MAJOR, "process_schedule():  Failed to determine transaction type.");
   }
}
Beispiel #8
0
static void process_trust_0003(const char * string, const jsmntok_t * tokens, const int index)
{
   char query[1024], zs[32], zs1[32], train_id[128], loc_stanox[128];
   time_t planned_timestamp, actual_timestamp, timestamp;

   time_t now = time(NULL);
   
   sprintf(query, "INSERT INTO trust_movement VALUES(%ld, '", now);
   jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id));
   strcat(query, train_id);
   strcat(query, "', '");
   jsmn_find_extract_token(string, tokens, index, "event_type", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', '");
   jsmn_find_extract_token(string, tokens, index, "planned_event_type", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', '");
   jsmn_find_extract_token(string, tokens, index, "platform", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', '");
   jsmn_find_extract_token(string, tokens, index, "loc_stanox", loc_stanox, sizeof(loc_stanox));
   strcat(query, loc_stanox);
   strcat(query, "', ");
   jsmn_find_extract_token(string, tokens, index, "actual_timestamp", zs, sizeof(zs));
   zs[10] = '\0';
   actual_timestamp = correct_trust_timestamp(atol(zs));
   sprintf(zs, "%ld", actual_timestamp);
   strcat(query, zs);
   strcat(query, ", ");
   jsmn_find_extract_token(string, tokens, index, "gbtt_timestamp", zs, sizeof(zs));
   zs[10] = '\0';
   timestamp = correct_trust_timestamp(atol(zs));
   sprintf(zs, "%ld", timestamp);
   strcat(query, zs);
   strcat(query, ", ");
   jsmn_find_extract_token(string, tokens, index, "planned_timestamp", zs, sizeof(zs));
   zs[10] = '\0';
   planned_timestamp = correct_trust_timestamp(atol(zs));
   sprintf(zs, "%ld", planned_timestamp);
   strcat(query, zs);
   strcat(query, ", ");
   jsmn_find_extract_token(string, tokens, index, "timetable_variation", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, ", '");
   jsmn_find_extract_token(string, tokens, index, "event_source", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', ");
   jsmn_find_extract_token(string, tokens, index, "offroute_ind", zs, sizeof(zs));
   strcat(query, (zs[0] == 't')?"1":"0");
   strcat(query, ", ");
   jsmn_find_extract_token(string, tokens, index, "train_terminated", zs, sizeof(zs));
   strcat(query, (zs[0] == 't')?"1":"0");
   strcat(query, ", '");
   jsmn_find_extract_token(string, tokens, index, "variation_status", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', '");
   jsmn_find_extract_token(string, tokens, index, "next_report_stanox", zs, sizeof(zs));
   strcat(query, zs);
   strcat(query, "', ");
   jsmn_find_extract_token(string, tokens, index, "next_report_run_time", zs, sizeof(zs));
   sprintf(zs1, "%d", atoi(zs));
   strcat(query, zs1);
   strcat(query, ")");

   db_query(query);

   // Old one?
   if(planned_timestamp && now - actual_timestamp > 6*60*60)
   {
      sprintf(query, "Late movement message received, actual timestamp %s.", time_text(actual_timestamp, true));
      _log(MINOR, query);
   }
   sprintf(query, "SELECT * from trust_activation where trust_id = '%s' and created > %ld and cif_schedule_id > 0", train_id, now - (4*24*60*60));
   if(!db_query(query))
   {
      MYSQL_RES * result0 = db_store_result();

      word num_rows = mysql_num_rows(result0);
      mysql_free_result(result0);
      if(num_rows > 1)
      {
         // This is not actually invalid, if there's some cancellations as well
         // sprintf(query, "Movement message received with %d matching activations, train_id = \"%s\".", num_rows, train_id);
         // _log(MAJOR, query);
      }
      else if(num_rows < 1)
      {
         MYSQL_ROW row0;
         char tiploc[128], reason[128];
         word sort_time;
         time_t now = time(NULL);

         strcpy(reason, "");
         tiploc[0] = '\0';

         sprintf(query, "Movement message received with %d matching activations, train_id = \"%s\".", num_rows, train_id);
         _log(MINOR, query);
         stats[MovtNoAct]++;
         if(planned_timestamp == 0)
         {
            strcpy(reason, "No planned timestamp");
         }
         if(high_load) strcpy(reason, "Message load too high");

         if(!reason[0])
         {
            sprintf(query, "SELECT tiploc FROM corpus WHERE stanox = %s AND tiploc != ''", loc_stanox);
            if(!db_query(query))
            {
               result0 = db_store_result();
               num_rows = mysql_num_rows(result0);
               if(num_rows)
               {
                  row0 = mysql_fetch_row(result0);
                  strcpy(tiploc, row0[0]);
               }
               else
               {
                  strcpy(reason, "Unable to determine TIPLOC");
               }
               mysql_free_result(result0);
            }
         }

         if(!reason[0])
         {
            char query1[256];
            struct tm * broken = localtime(&planned_timestamp);
            sort_time = broken->tm_hour * 4 * 60 + broken->tm_min * 4;
            // Select the day
            word day = broken->tm_wday;
            word yest = (day + 6) % 7;
            // word tom = (day + 1) % 7;
            broken->tm_hour = 12;
            broken->tm_min = 0;
            broken->tm_sec = 0;
            time_t when = timegm(broken);
            sprintf(query, "SELECT cif_schedules.id, cif_schedules.CIF_train_uid, signalling_id, CIF_stp_indicator FROM cif_schedules INNER JOIN cif_schedule_locations ON cif_schedules.id = cif_schedule_locations.cif_schedule_id WHERE cif_schedule_locations.tiploc_code = '%s'",
                    tiploc);
            sprintf(query1, " AND cif_schedule_locations.sort_time > %d AND cif_schedule_locations.sort_time < %d",
                    sort_time - 1, sort_time + 4);
            strcat(query, query1);
            strcat(query, " AND (cif_schedules.CIF_stp_indicator = 'N' OR cif_schedules.CIF_stp_indicator = 'P' OR cif_schedules.CIF_stp_indicator = 'O')");


            static const char * days_runs[8] = {"runs_su", "runs_mo", "runs_tu", "runs_we", "runs_th", "runs_fr", "runs_sa", "runs_su"};

            //
            sprintf(query1, " AND (((%s) AND (schedule_start_date <= %ld) AND (schedule_end_date >= %ld) AND (NOT next_day))",   days_runs[day],  when + 12*60*60, when - 12*60*60);
            strcat(query, query1);
            sprintf(query1, " OR   ((%s) AND (schedule_start_date <= %ld) AND (schedule_end_date >= %ld) AND (    next_day)))",  days_runs[yest], when - 12*60*60, when - 36*60*60);
            strcat(query, query1);

            sprintf(query1, " AND deleted > %ld ORDER BY LOCATE(CIF_stp_indicator, 'NPO')", planned_timestamp);
            strcat(query, query1);

            if(!db_query(query))
            {
               char save_uid[16], save_stp;
               save_uid[0] = save_stp = '\0';
               dword cif_schedule_id = 0;
               result0 = db_store_result();
               num_rows = mysql_num_rows(result0);
               if(!num_rows)
               {
                  strcpy(reason, "No schedules found");
               }

               while((row0 = mysql_fetch_row(result0)))
               {
                  sprintf(query, "   Found potential match:%8s (%s) %s STP=%s", row0[0], row0[1], row0[2], row0[3]);
                  _log(MINOR, query);
                  if (!reason[0])
                  {
                     if(save_uid[0] && strcmp(save_uid, row0[1]))  strcpy(reason, "Multiple matching schedule UIDs");
                     else if (save_stp == 'O' && row0[3][0] =='O') strcpy(reason, "Multiple matching overlay schedules");
                     else
                     {
                        cif_schedule_id = atol(row0[0]);
                        strcpy(save_uid, row0[1]);
                        save_stp = row0[3][0];
                     }
                  }
               }
               mysql_free_result(result0);

               if(!reason[0])
               {
                  sprintf(query, "INSERT INTO trust_activation VALUES(%ld, '%s', %ld, 1)", now, train_id, cif_schedule_id);
                  db_query(query);
                  sprintf(query, "   Successfully deduced activation %ld", cif_schedule_id);
                  _log(MINOR, query);
               }
            }
         }

         if(!reason[0])
         {
            stats[DeducedAct]++;
         }
         else
         {
            sprintf(query, "   Failed to deduce an activation - Reason:  %s.", reason );
            _log(MINOR, query);
            
            sprintf(query, "      stanox = %s, tiploc = \"%s\", planned_timestamp %s, derived sort time = %d", loc_stanox, tiploc, time_text(planned_timestamp, true), sort_time);
            _log(MINOR, query);
            // jsmn_dump_tokens(string, tokens, index);
         }
      }
   }
   return;
}
Beispiel #9
0
static void process_trust_0001(const char * const string, const jsmntok_t * const tokens, const int index)
{
   char zs[128], zs1[128], report[1024];
   char train_id[64], train_uid[64];
   char query[1024];
   dword cif_schedule_id;
   MYSQL_RES * result0;
   MYSQL_ROW row0;
   
   sprintf(report, "Activation message:");

   jsmn_find_extract_token(string, tokens, index, "train_id", train_id, sizeof(train_id));
   sprintf(zs1, " train_id=\"%s\"", train_id);
   strcat(report, zs1);

   jsmn_find_extract_token(string, tokens, index, "schedule_start_date", zs, sizeof(zs));
   time_t schedule_start_date_stamp = parse_datestamp(zs);
   sprintf(zs1, " schedule_start_date=\"%s\" %ld", zs, schedule_start_date_stamp);
   strcat(report, zs1);

   jsmn_find_extract_token(string, tokens, index, "schedule_end_date", zs, sizeof(zs));
   time_t schedule_end_date_stamp   = parse_datestamp(zs);
   sprintf(zs1, " schedule_end_date=\"%s\" %ld", zs, schedule_end_date_stamp);
   strcat(report, zs1);

   jsmn_find_extract_token(string, tokens, index, "train_uid", train_uid, sizeof(train_uid));
   sprintf(zs1, " train_uid=\"%s\"", train_uid);
   strcat(report, zs1);

   jsmn_find_extract_token(string, tokens, index, "schedule_source", zs, sizeof(zs));
   sprintf(zs1, " schedule_source=\"%s\"", zs);
   strcat(report, zs1);

   jsmn_find_extract_token(string, tokens, index, "schedule_wtt_id", zs, sizeof(zs));
   sprintf(zs1, " schedule_wtt_id=\"%s\"", zs);
   strcat(report, zs1);

   // Bodge.  The ORDER BY here will *usually* get the correct one out first!
   // Idea:  If we store and index on cif_train_uid, we can guarantee to get the right one.
   sprintf(query, "select id from cif_schedules where cif_train_uid = '%s' AND schedule_start_date = %ld AND schedule_end_date = %ld AND deleted > %ld ORDER BY LOCATE(CIF_stp_indicator, 'OCPN')", train_uid, schedule_start_date_stamp, schedule_end_date_stamp, time(NULL));
   if(!db_query(query))
   {
      result0 = db_store_result();
      word num_rows = mysql_num_rows(result0);
      sprintf(zs, "  Schedule hit count %d.  Message contents:", num_rows);
      strcat(report, zs);
      if(num_rows < 1) 
      {
         stats[Mess1Miss]++;
         _log(MINOR, report);
         time_t now = time(NULL);
         sprintf(query, "INSERT INTO trust_activation VALUES(%ld, '%s', %ld, 0)", now, train_id, 0L);
         db_query(query);
         jsmn_dump_tokens(string, tokens, index);
      }
      else
      {
         row0 = mysql_fetch_row(result0);
         cif_schedule_id = atol(row0[0]);
         time_t now = time(NULL);
         sprintf(query, "INSERT INTO trust_activation VALUES(%ld, '%s', %ld, 0)", now, train_id, cif_schedule_id);
         db_query(query);
      }
      mysql_free_result(result0);
   }
   return;
}
Beispiel #10
0
static void process_message(const char * const body)
{
   jsmn_parser parser;
   time_t elapsed = time(NULL);
   
   log_message(body);

   jsmn_init(&parser);
   int r = jsmn_parse(&parser, body, tokens, NUM_TOKENS);
   if(r != 0) 
   {
      sprintf(zs, "Parser result %d.  Message discarded.", r);
      _log(MAJOR, zs);
      stats[NotRecog]++;
   }
   else
   {
      size_t messages, i, index;
      // Is it an array?
      if(tokens[0].type == JSMN_ARRAY)
      {
         messages = tokens[0].size;
         index = 1;
         sprintf(zs, "STOMP message is array of %d TRUST messages.", messages);
      }
      else
      {
         messages = 1;
         index = 0;
         sprintf(zs, "STOMP message contains a single TRUST message.");
      }
      _log(DEBUG,zs);

      for(i=0; i < messages; i++)
      {
         char message_name[128];
         jsmn_find_extract_token(body, tokens, index, "msg_type", message_name, sizeof(message_name));
         word message_type = atoi(message_name);
         if(debug)
         {
            sprintf(zs, "Processing TRUST message %d", i);
            _log(DEBUG, zs);
         }
         if(!strncmp(message_name, "000", 3) && message_type > 0 && message_type < 9) 
         {
            stats[GoodMessage]++;
            stats[GoodMessage + message_type]++;
            switch(message_type)
            {
            case 1: process_trust_0001(body, tokens, index); break;
            case 2: process_trust_0002(body, tokens, index); break;
            case 3: process_trust_0003(body, tokens, index); break;
            case 5: process_trust_0005(body, tokens, index); break;
            case 6: process_trust_0006(body, tokens, index); break;
            case 7: process_trust_0007(body, tokens, index); break;
            default:
               sprintf(zs, "Message type \"%s\" discarded.", message_name);
               _log(MINOR, zs);
               break;
            }
         }
         else
         {
            sprintf(zs, "Unrecognised message type \"%s\".", message_name);
            _log(MINOR, zs);
            jsmn_dump_tokens(body, tokens, index);
            stats[NotRecog]++;
         }
         
         size_t message_ends = tokens[index].end;
         do  index++; 
         while ( tokens[index].start < message_ends && tokens[index].start >= 0 && index < NUM_TOKENS);
      }
   }
   elapsed = time(NULL) - elapsed;
   if(debug || elapsed > 1)
   {
      char zs[128];
      sprintf(zs, "Transaction took %ld seconds.", elapsed);
      _log(MINOR, zs);
   }
}
Beispiel #11
0
static void process_message(const word describer, const char * const body, const size_t index)
{
   char message_type[8];
   char times[16];
   time_t timestamp;
   char from[16], to[16], descr[16], wasf[16], wast[16];

   jsmn_find_extract_token(body, tokens, index, "msg_type", message_type, sizeof(message_type));
   _log(DEBUG, "Message name = \"%s\".", message_type);
   jsmn_find_extract_token(body, tokens, index, "time", times, sizeof(times));
   times[10] = '\0';
   timestamp = atol(times);

   time_t now = time(NULL);

   last_td_processed[describer] = now;

   if(((status_last_td_actual[describer] + 8) < timestamp) || ((status_last_td_processed + 8) < now))
   {
       status_last_td_actual[describer] = timestamp;
       status_last_td_processed = now;
       char query[256];
       sprintf(query, "update status SET last_td_processed = %ld", now);
       db_query(query);
       sprintf(query, "update td_status set last_timestamp = %ld WHERE d = '%s'", timestamp, describers[describer]);
       db_query(query);
   }

   if(!strcasecmp(message_type, "CA"))
   {
      jsmn_find_extract_token(body, tokens, index, "from", from, sizeof(from));
      jsmn_find_extract_token(body, tokens, index, "to", to, sizeof(to));
      jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr));
      strcpy(wasf, query_berth(describer, from));
      strcpy(wast, query_berth(describer, to));
      _log(DEBUG, "%s CA:               Berth step (%s) Description \"%s\" from berth \"%s\" to berth \"%s\"", describers[describer], time_text(timestamp, true), descr, from, to);
      log_detail(timestamp, "%s CA: %s from %s to %s", describers[describer], descr, from, to);
      if(strcmp(wasf, descr) || strcmp(wast, ""))
      {
         _log(DEBUG, "Message %s CA: Step \"%s\" from \"%s\" to \"%s\" found \"%s\" in \"%s\" and \"%s\" in \"%s\".", describers[describer], descr, from, to, wasf, from, wast, to);
      }
      update_database(Berth, describer, from, "");
      update_database(Berth, describer, to, descr);
      stats[CA]++;
   }
   else if(!strcasecmp(message_type, "CB"))
   {
      jsmn_find_extract_token(body, tokens, index, "from", from, sizeof(from));
      jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr));
      strcpy(wasf, query_berth(describer, from));
      _log(DEBUG, "%s CB:             Berth cancel (%s) Description \"%s\" from berth \"%s\"", describers[describer], time_text(timestamp, true), descr, from);
      log_detail(timestamp, "%s CB: %s from %s", describers[describer], descr, from);
      if(strcmp(wasf, descr))
      {
         _log(DEBUG, "Message %s CB: Cancel \"%s\" from \"%s\" found \"%s\" in \"%s\".", describers[describer], descr, from, wasf, from);
      }
      update_database(Berth, describer, from, "");
      stats[CB]++;
   }
   else if(!strcasecmp(message_type, "CC"))
   {
      jsmn_find_extract_token(body, tokens, index, "to", to, sizeof(to));
      jsmn_find_extract_token(body, tokens, index, "descr", descr, sizeof(descr));
      _log(DEBUG, "%s CC:          Berth interpose (%s) Description \"%s\" to berth \"%s\"", describers[describer], time_text(timestamp, true), descr, to);
      log_detail(timestamp, "%s CC: %s           to %s", describers[describer], descr, to);
      update_database(Berth, describer, to, descr);
      stats[CC]++;
   }
   else if(!strcasecmp(message_type, "CT"))
   {
      // Heartbeat
      char report_time[16];
   
      jsmn_find_extract_token(body, tokens, index, "report_time", report_time, sizeof(report_time));
      _log(DEBUG, "%s CT:                  Heartbeat (%s) Report time = %s", describers[describer], time_text(timestamp, true), report_time);
      stats[CT]++;
   }
   else if(!strcasecmp(message_type, "SF"))
   {
      char address[16], data[32];
      jsmn_find_extract_token(body, tokens, index, "address", address, sizeof(address));
      jsmn_find_extract_token(body, tokens, index, "data", data, sizeof(data));
      _log(DEBUG, "%s SF:        Signalling update (%s) Address \"%s\", data \"%s\"", describers[describer], time_text(timestamp, true), address, data);

      word  a = strtoul(address, NULL, 16);
      dword d = strtoul(data,    NULL, 16);
      signalling_update("SF", describer, timestamp, a, d);

      stats[SF]++;
   }
   else if(!strcasecmp(message_type, "SG"))
   {
      char address[16], data[32];
      jsmn_find_extract_token(body, tokens, index, "address", address, sizeof(address));
      jsmn_find_extract_token(body, tokens, index, "data", data, sizeof(data));
      _log(DEBUG, "%s SG:       Signalling refresh (%s) Address \"%s\", data \"%s\"", describers[describer], time_text(timestamp, true), address, data);
      word  a = strtoul(address, NULL, 16);
      dword d = strtoul(data,    NULL, 16);
      _log(DEBUG, "a = %04x, d = %08x", a, d);
      signalling_update("SG", describer, timestamp, a     , 0xff & (d >> 24));
      signalling_update("SG", describer, timestamp, a + 1 , 0xff & (d >> 16));
      signalling_update("SG", describer, timestamp, a + 2 , 0xff & (d >> 8 ));
      signalling_update("SG", describer, timestamp, a + 3 , 0xff & (d      ));

      stats[SG]++;
   }