PPS-Client  2.0.0
Client for synchronizing the system clock to a GPS PPS source
pps-serial.cpp
Go to the documentation of this file.
1 
6 /*
7  * Copyright (C) 2020 Raymond S. Connell
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23 
24 #include "../client/pps-client.h"
25 
26 #define MSG_WAIT_TIME 990000
27 #define SECS_PER_HOUR 3600
28 #define VERIFY_NUM 10
29 #define MAX_NOT_READY 60
30 //#define DEBUG
31 
32 extern struct G g;
33 extern struct ppsFiles f;
34 
38 static struct serialLocalVars {
39  bool noGPRMCmsg;
40  bool bufferIsEmpty;
41  bool badGPRMCmsg;
42  bool badTimeConversion;
43  int activeCount;
44  bool threadIsBusy[1];
45  pthread_t tid[1];
46  int timeCheckEnable;
47  char *serialPort;
48  int lostGPSCount;
49  bool doReadSerial;
50  int lastSerialTimeDif;
51  int timeDiff[VERIFY_NUM];
52  int diffCount[VERIFY_NUM];
53  int notReadyCount;
54  int missMsg;
55  int gmtSeconds;
56  char msgbuf[10000];
57  char *ptimestr;
58 } s;
59 
75 bool getUTCfromGPSmessages(const char *msgbuf, timeCheckParams *tcp, time_t *gmt0Seconds){
76 
77  char scnbuf[10];
78  memset(scnbuf, 0, 10);
79 
80  struct tm gmt;
81  memset(&gmt, 0, sizeof(struct tm));
82 
83  char *active, *ctmp2, *ctmp4;
84 
85  active = scnbuf;
86  ctmp2 = active + 2;
87  ctmp4 = ctmp2 + 2;
88 
89  float ftmp1, ftmp3, ftmp5, ftmp6;
90  int frac;
91 
92  char *pstr = (char *)msgbuf;
93 
94  // $GPRMC,144940.000,A,3614.5286,N,08051.3851,W,0.01,219.16,260420,,,D*71
95  pstr = strstr(pstr, "$GPRMC"); // $GPRMC,205950.000,A,3614.5277,N,08051.3851,W,0.02,288.47,051217, ,,D*75
96  if (pstr != NULL){ // If a GPRMC message was received, continue.
97 
98  char *pNext = strstr(pstr, "\n"); // this is a complete message by looking for CR
99  if (pNext != NULL){ // If the message is complete, read it.
100  pNext += 4;
101  *pNext = '\0';
102  sscanf(pstr, "$GPRMC,%2d%2d%2d.%d,%1c,%f,%1c,%f,%1c,%f,%f,%2d%2d%2d,", &gmt.tm_hour, &gmt.tm_min, &gmt.tm_sec,
103  &frac, active, &ftmp1, ctmp2, &ftmp3, ctmp4, &ftmp5, &ftmp6, &gmt.tm_mday, &gmt.tm_mon, &gmt.tm_year);
104 
105  if (active[0] == 'A'){ // this is an active message
106 
107  gmt.tm_mon -= 1; // Convert to tm struct format with months: 0 to 11
108  gmt.tm_year += 100; // Convert to tm struct format with year since 1900
109  // But actual conversion is from Unix 1/1/1970
110  *gmt0Seconds = timegm(&gmt); // Local time in seconds because GPS returns the timezone
111  // time in gmt and timegm() makes no timezone correction.
112  if (*gmt0Seconds == -1){
113  s.badTimeConversion = true;
114  }
115  s.lostGPSCount = 0;
116  }
117  else {
118  s.badGPRMCmsg = true;
119  return false;
120  }
121  }
122  else {
123  s.badGPRMCmsg = true;
124  return false;
125  }
126  }
127  else {
128  s.noGPRMCmsg = true;
129  return false;
130  }
131  return true;
132 }
133 
134 
142 int saveTimestamps(int gmt0Seconds, int gmtSeconds, int tv_usec, timeCheckParams *tcp){
143 
144  mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
145  int sfd = open(tcp->gmtTime_file, O_CREAT | O_WRONLY, mode);
146  if (sfd == -1){
147  sprintf(tcp->strbuf, "saveTimestamps() Could not create/open gmtTime file %s\n", tcp->gmtTime_file);
148  writeToLog(tcp->strbuf, "saveTimestamps()");
149  return -1;
150  }
151 
152  char sbuf[100];
153  memset(sbuf, 0, 100);
154  sprintf(sbuf, "%d %d %d\n", gmt0Seconds, gmtSeconds, tv_usec);
155 
156  int rv = write(sfd, sbuf, 100);
157  close(sfd);
158 
159  if (rv == -1){
160  sprintf(tcp->strbuf, "saveTimestamps() write to gmtTime file failed\n");
161  writeToLog(tcp->strbuf, "saveTimestamps()");
162  }
163  return 0;
164 }
165 
166 
177 void read_save(int captureTime, timeCheckParams *tcp){
178  time_t gmt0Seconds = 0;
179 
180  if (getUTCfromGPSmessages(s.ptimestr, tcp, &gmt0Seconds) == false){
181  s.missMsg += 1;
182 
183  if (s.missMsg >= MAX_NOT_READY){
184  sprintf(tcp->strbuf, "saveGPSTime(): No GPRMC message was recieved from the serial port in 60 seconds\n");
185  writeToLog(tcp->strbuf, "saveGPSTime()");
186  s.missMsg = 0;
187  }
188  }
189  else if (s.noGPRMCmsg == false && s.badGPRMCmsg == false && s.badTimeConversion == false){
190 
191  if (captureTime < 500){
192 
193 #ifdef DEBUG
194  printf("read_save() gmt0Seconds: %ld captureTime: %d\n", gmt0Seconds, captureTime);
195 #endif
196  saveTimestamps((int)gmt0Seconds, s.gmtSeconds, captureTime, tcp); // timestamps from the previous second
197 
198  s.missMsg = 0;
199  }
200  else {
201  s.missMsg += 1;
202  }
203  }
204 
205  return;
206 }
207 
208 
229 
230  int rfd = open(tcp->serialPort, O_RDONLY | O_NOCTTY);
231  if (rfd == -1){
232  sprintf(tcp->strbuf, "saveGPSTime() Unable to open %s\n", tcp->serialPort);
233  writeToLog(tcp->strbuf, "saveGPSTime()");
234  return;
235  }
236 
237  struct timeval tv1;
238  int captureTime, nRead, timeToWait;
239 
240  for (int i = 0; i < 1000; i++){ // In case it has backed up, empty the serial port buffer.
241  nRead = read(rfd, s.msgbuf, 9950);
242  if (nRead < 500){
243  break;
244  }
245  }
246 
247  while (true){
248  s.noGPRMCmsg = false;
249  s.bufferIsEmpty = false;
250  s.badGPRMCmsg = false;
251 
252  gettimeofday(&tv1, NULL);
253 
254  s.gmtSeconds = (int)tv1.tv_sec;
255 
256  int timeToStart = 1000000 - tv1.tv_usec + 100; // Start 100 usec after rollover of second
257  usleep(timeToStart);
258 
259  memset(s.msgbuf, 0, 10000 * sizeof(char));
260 
261  int readNum = 9950;
262 
263  nRead = read(rfd, s.msgbuf, readNum);
264 
265 #ifdef DEBUG
266  printf("\nsaveGPSTime() Number of serial port lines read: %d\n", nRead);
267 #endif
268  s.ptimestr = strstr(s.msgbuf, "$GPRMC");
269 
270  if (s.ptimestr != NULL &&
271  strlen(s.ptimestr) < 150){ // $GPRMC and $GPVTG should be the last two messages. If
272 #ifdef DEBUG // not then messages have been delayed and are not reliable.
273  printf("\ns.ptimestr: %s\n", s.ptimestr);
274  printf("saveGPSTime() s.gmtSeconds: %d usec: %d\n", s.gmtSeconds, (int)tv1.tv_usec);
275 #endif
276  gettimeofday(&tv1, NULL);
277  captureTime = (int)tv1.tv_usec;
278 
279  read_save(captureTime, tcp);
280  }
281 
282  if (nRead == -1){
283  s.bufferIsEmpty = true;
284  }
285 
286  gettimeofday(&tv1, NULL);
287  int readEnd = tv1.tv_usec;
288  timeToWait = 1000000 - readEnd - 10000;
289 
290  usleep(timeToWait); // Sleep until 10 msec before end of second.
291 
292  }
293 
294  close(rfd);
295 }
296 
313 
314  int rv = 0;
315  int sfd;
316  char sbuf[100];
317  time_t gmt0Seconds, gmtSeconds;
318  struct stat statbuf;
319 
320  usleep(5000);
321 
322  int idx = s.activeCount % VERIFY_NUM;
323 
324  if (idx == 0){
325  memset(s.timeDiff, 0, VERIFY_NUM * sizeof(int));
326  memset(s.diffCount, 0, VERIFY_NUM * sizeof(int));
327  }
328 
329  memset(sbuf, 0, 100);
330 
331  if (stat(f.gmtTime_file, &statbuf) == 0){ // If file exists
332  sfd = open(f.gmtTime_file, O_RDONLY);
333 
334  if (s.notReadyCount >= MAX_NOT_READY){
335  sprintf(g.logbuf, "makeSerialTimeQuery(): Serial port GPS time data has resumed\n");
336  writeToLog(g.logbuf, "makeSerialTimeQuery()");
337  }
338 
339  s.notReadyCount = 0;
340  s.activeCount += 1;
341  }
342  else {
343  s.notReadyCount += 1;
344 
345  if (s.notReadyCount >= MAX_NOT_READY){
346 
347  if (s.notReadyCount == MAX_NOT_READY){
348  sprintf(g.logbuf, "makeSerialTimeQuery(): Serial port GPS time data has stopped\n");
349  writeToLog(g.logbuf, "makeSerialTimeQuery()");
350  }
351  }
352  return 0;
353  }
354 
355  int sz = read(sfd, sbuf, 100);
356  if (sz == 0){
357  close(sfd);
358  remove(f.gmtTime_file);
359  return 0;
360  }
361  if (sz == -1){
362  close(sfd);
363  remove(f.gmtTime_file);
364  return 0;
365  }
366 
367  close(sfd);
368  remove(f.gmtTime_file);
369 
370  int captureTime;
371 
372  sscanf(sbuf, "%ld %ld %d\n", &gmt0Seconds, &gmtSeconds, &captureTime);
373 
374  s.timeDiff[idx] = (int)(gmt0Seconds - gmtSeconds);
375 
376  int maxDiffCount = 0, timeDiff = 0;
377 
378  if (idx == VERIFY_NUM - 1){
379 
380  for (int i = 0; i < VERIFY_NUM; i++){ // Get a consensis of the time error for VERIFY_NUM timeDiff values
381  for (int j = 0; j < VERIFY_NUM; j++){
382  if ((s.timeDiff[i] != 0) && (s.timeDiff[i] == s.timeDiff[j])){
383  s.diffCount[i] += 1;
384  }
385  }
386  }
387 
388  for (int i = 0; i < VERIFY_NUM; i++){
389  if (s.diffCount[i] > maxDiffCount){
390  maxDiffCount = s.diffCount[i];
391  timeDiff = s.timeDiff[i];
392  }
393  }
394 
395  if ((maxDiffCount >= (VERIFY_NUM - 2)) && (g.serialTimeError == 0)){ // At least four out of five must agree on the timeDiff.
396  sprintf(g.logbuf, "makeSerialTimeQuery() Time error: %d seconds. The error will be corrected within 1 minute.\n", timeDiff);
397  writeToLog(g.logbuf, "makeSerialTimeQuery()");
398 
399  g.serialTimeError = timeDiff;
400  }
401  }
402 #ifdef DEBUG
403  printf("makeSerialTimeQuery() s.timeDiff[%d]: %d timeDiff %d maxDiffCount: %d\n", idx, s.timeDiff[idx], timeDiff, maxDiffCount);
404 #endif
405  return rv;
406 }
407 
418  memset(&s, 0, sizeof(struct serialLocalVars));
419 
420  int buflen = strlen(g.serialPort);
421  s.serialPort = new char[buflen + 1];
422  strcpy(s.serialPort, g.serialPort);
423 
424  s.threadIsBusy[0] = false;
425 
426  tcp->tid = s.tid;
427  tcp->strbuf = new char[STRBUF_SZ];
428  tcp->threadIsBusy = s.threadIsBusy;
429  tcp->serialPort = s.serialPort;
430  tcp->gmtTime_file = f.gmtTime_file;
431 
432  tcp->rv = 0;
433  tcp->doReadSerial = false;
434 
435  int rv = pthread_attr_init(&(tcp->attr));
436  if (rv != 0) {
437  sprintf(g.logbuf, "Can't init pthread_attr_t object: %s\n", strerror(errno));
438  writeToLog(g.logbuf, "allocInitializeSerialThread()");
439  return -1;
440  }
441 
442  rv = pthread_attr_setstacksize(&(tcp->attr), PTHREAD_STACK_REQUIRED);
443  if (rv != 0){
444  sprintf(g.logbuf, "Can't set pthread_attr_setstacksize(): %s\n", strerror(errno));
445  writeToLog(g.logbuf, "allocInitializeSerialThread()");
446  return -1;
447  }
448 
449  rv = pthread_attr_setdetachstate(&(tcp->attr), PTHREAD_CREATE_DETACHED);
450  if (rv != 0){
451  sprintf(g.logbuf, "Can't set pthread_attr_t object state: %s\n", strerror(errno));
452  writeToLog(g.logbuf, "allocInitializeSerialThread()");
453  return -1;
454  }
455 
456  return 0;
457 }
458 
465  pthread_attr_destroy(&(tcp->attr));
466  delete[] tcp->strbuf;
467  if (tcp->serialPort != NULL){
468  delete[] tcp->serialPort;
469  tcp->serialPort = NULL;
470  }
471 }
g
struct G g
Declares the global variables defined in pps-client.h.
Definition: pps-client.cpp:55
timeCheckParams::serialPort
char * serialPort
The serial port filename when serial time is used.
Definition: pps-client.h:152
timeCheckParams
Definition: pps-client.h:146
freeSerialThread
void freeSerialThread(timeCheckParams *tcp)
Definition: pps-serial.cpp:464
timeCheckParams::attr
pthread_attr_t attr
Thread attribute object.
Definition: pps-client.h:148
writeToLog
void writeToLog(char *logbuf, const char *location)
Definition: pps-files.cpp:364
timeCheckParams::tid
pthread_t * tid
Thread id.
Definition: pps-client.h:147
getUTCfromGPSmessages
bool getUTCfromGPSmessages(const char *msgbuf, timeCheckParams *tcp, time_t *gmt0Seconds)
Definition: pps-serial.cpp:75
makeSerialTimeQuery
int makeSerialTimeQuery(timeCheckParams *tcp)
Definition: pps-serial.cpp:312
f
struct ppsFiles f
PPS-Client internal files.
Definition: pps-files.cpp:63
saveGPSTime
void saveGPSTime(timeCheckParams *tcp)
Definition: pps-serial.cpp:228
timeCheckParams::doReadSerial
bool doReadSerial
Flag to read serial messages from serial port.
Definition: pps-client.h:154
read_save
void read_save(int captureTime, timeCheckParams *tcp)
Definition: pps-serial.cpp:177
timeCheckParams::threadIsBusy
bool * threadIsBusy
True while thread is waiting for or processing a time query.
Definition: pps-client.h:157
MAX_NOT_READY
#define MAX_NOT_READY
Definition: pps-serial.cpp:29
STRBUF_SZ
#define STRBUF_SZ
Definition: pps-client.h:95
timeCheckParams::strbuf
char * strbuf
Space for messages and query strings.
Definition: pps-client.h:155
saveTimestamps
int saveTimestamps(int gmt0Seconds, int gmtSeconds, int tv_usec, timeCheckParams *tcp)
Definition: pps-serial.cpp:142
timeCheckParams::gmtTime_file
char * gmtTime_file
Definition: pps-client.h:160
allocInitializeSerialThread
int allocInitializeSerialThread(timeCheckParams *tcp)
Definition: pps-serial.cpp:417
VERIFY_NUM
#define VERIFY_NUM
Definition: pps-serial.cpp:28
G
Struct for program-wide global variables showing those important to the controller.
Definition: pps-client.h:167
timeCheckParams::rv
int rv
Definition: pps-client.h:158
PTHREAD_STACK_REQUIRED
#define PTHREAD_STACK_REQUIRED
Stack space requirements for threads.
Definition: pps-client.h:62
G::serialTimeError
int serialTimeError
Error reported by GPS serial port.S.
Definition: pps-client.h:244