2015-07-01 16:45:15 +03:00

686 lines
17 KiB
C

/*
* main.c
*
* Copyright 2015 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "usefull_macros.h"
#include "cmdlnopts.h"
#include "daemon.h"
#include <assert.h>
#include <signal.h>
#include <time.h>
#include <math.h> // sqrt
#ifndef PIDFILE
#define PIDFILE "/tmp/GPStest.pid"
#endif
glob_pars *GP = NULL;
#define PRINT(...) do{if(!GP->silent) printf(__VA_ARGS__);}while(0)
// Messages for blocking: GSV, RMC, GSA, GGA, GLL, VTG
char *GPmsgs[] = {"GSV", "RMC", "GSA", "GGA", "GLL", "VTG"};
static void signals(int sig){
DBG("Get signal %d, quit.\n", sig);
unlink(PIDFILE);
restore_tty();
exit(sig);
}
uint8_t *get_portdata(){
static uint8_t buf[1025];
uint8_t *ptr = buf;
size_t L = 0, rest = 1024;
while(rest && (L = read_tty(ptr, rest))){
rest -= L;
ptr += L;
}
if(ptr != buf){
*ptr = 0;
ptr = buf;
}else ptr = NULL;
return ptr;
}
/**
* Calculate checksum & write message to port
* @param buf - command to write (with leading $ and trailing *)
* return 0 if fails
*/
int write_with_checksum(uint8_t *buf){
static char CS[3];
//uint8_t *ptr = buf;
uint8_t checksum = 0;
if(*buf != '$') return 0;
if(write_tty(buf, strlen((char*)buf))) return 0;
++buf; // skip leaders
do{
checksum ^= *buf++;
}while(*buf && *buf != '*');
snprintf(CS, 3, "%X", checksum);
if(write_tty((uint8_t*)CS, 2)) return 0;
if(write_tty((uint8_t*)"\r\n", 2)) return 0;
//DBG("Write: %s%c%c", ptr, CS[0], CS[1]);
return 1;
}
// Check checksum
int checksum(uint8_t *buf){
uint8_t *eol;
char chs[3];
uint8_t checksum = 0;
if(*buf != '$' || !(eol = (uint8_t*)strchr((char*)buf, '*'))){
DBG("Wrong data: %s\n", buf);
return 0;
}
while(++buf != eol)
checksum ^= *buf;
snprintf(chs, 3, "%02X", checksum);
if(strncmp(chs, (char*)++buf, 2)){
DBG("Wrong checksum: %s", chs);
return 0;
}
return 1;
}
uint8_t *nextpos(uint8_t **buf, int pos){
int i;
if(pos < 1) pos = 1;
for(i = 0; i < pos; ++i){
*buf = (uint8_t*)strchr((char*)*buf, ',');
if(!*buf) break;
++(*buf);
}
return *buf;
}
#define NEXT() do{if(!nextpos(&buf, 1)) goto ret;}while(0)
#define SKIP(NPOS) do{if(!nextpos(&buf, NPOS)) goto ret;}while(0)
double timediff_aver = 0.;
int timediff_N = 0;
/**
* difference (in seconds) in system & GPS clock
* @return system_time - GPS_time
*/
double timediff(int h, int m, double s){
struct timeval tv;
// struct timezone tz;
gettimeofday(&tv, NULL);
time_t tm0 = time(NULL);
struct tm *gmt = gmtime(&tm0);
double td = (gmt->tm_hour - h) * 3600.;
td += (gmt->tm_min - m) * 60.;
td += ((double)tv.tv_usec)/1e6 + (gmt->tm_sec - s);
timediff_aver += td;
++timediff_N;
return td;
}
double Latt_mean = 0., Long_mean = 0., Latt_sq = 0., Long_sq = 0.;
int Latt_N = 0, Long_N = 0;
/**
* acquire last command
* @return 0 if failed
*/
int get_ACK(uint8_t *conf) {
int i;
uint8_t ack[10] = {0xb5, 0x62, // header
0x05, 0x01, // ACK-ACK
2, 0}; // 2 bytes, little-endian
// send ACK to recent CONF changes
ack[6] = conf[2];
ack[7] = conf[3];
double T0 = dtime();
// checksum
for (i = 2; i < 8; ++i){
ack[8] += ack[i];
ack[9] += ack[8];
}
/*
printf("ack: ");
for(i = 0; i < 10; ++i)
printf("%X ", ack[i]);
printf("\n");
*/
uint8_t buf[10], *ptr = buf, *cmp = ack;
size_t remain = 10;
// wait not more than 3 seconds
while(dtime() - T0 < 3. && remain){
size_t L = read_tty(ptr, remain);
if(L){
remain -= L;
for(i = 0; i < (int)L; ++i){
//DBG("got: %X (%c)", *ptr, *ptr);
if(*ptr++ != *cmp++){
//DBG("NEQ: %X != %X", ptr[-1], cmp[-1]);
ptr = buf; cmp = ack; remain = 10;
break;
}
}
}
}
if(!remain) return 1;
return 0;
}
void cfg_stationary(){
int i;
uint8_t stat[44] = {0xb5, 0x62, // header
0x06, 0x24, // CFG-NAV5
36, 0, // 36 bytes, little-endian
1, 0, // mask: only dynamic model
2}; // stationary model
// stat[44] = '\r';
// stat[45] = '\n';
/*
uint8_t stat[] = {
0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x02, 0x03,
0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00,
0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, '\r', '\n'};
*/
for(i = 2; i < 42; ++i){
stat[42] += stat[i];
stat[43] += stat[42];
}
/*
printf("conf: ");
for(i = 0; i < 44; ++i)
printf("%X ", conf[i]);
printf("\n");
*/
i = 0;
do{
write_tty(stat, 44);
DBG("Written, aquire");
}while(!get_ACK(stat) && ++i < 11);
// precise point position
uint8_t prec[48] = {0xb5, 0x62, // header
0x06, 0x23, // CFG-NAVX5
40, 0, // 40 bytes, little-endian
0, 0, 0, 0x20}; // mask for PPP
prec[32] = 1; // usePPP = TRUE (field 26)
for(i = 2; i < 46; ++i){
prec[46] += prec[i];
prec[47] += prec[46];
}
i = 0;
do{
write_tty(prec, 48);
DBG("Written, aquire");
}while(!get_ACK(prec) && ++i < 11);
}
/**
* Show current date in appropriate format for initial clock setup (MMDDhhmmCCYY.ss)
* make from root:
* date $(gpstest -sSD)
*/
void show_date(int H, int M, double S, int d, int m, int y){
printf("--utc %02d%02d%02d%02d%02d.%02d\n", m, d, H, M, y, (int)(S + 0.3));
signals(0); // appropriate exit
}
/*
* Satellites in View
* $GPGSV,NoMsg,MsgNo,NoSv,{,sv,elv,az,cno}*cs
* 1 = total number of GPGSV messages being output
* 2 = Number of this message
* 3 = Satellites in View
* 4-7 will be repeated 1..4 times
* sv = Satellite ID
* elv = Elevation, range 0..90deg
* az = Azimuth, range 0..359deg
* cno = SNR, range 0.99dB
* cs = control sum
* 3,1,11,01,68,278,,03,39,292,08,04,66,190,30,11,55,231,34*79
* 3,2,11,14,30,050,10,17,11,322,,19,09,202,,22,14,096,*74
* 3,3,11,23,20,232,30,31,43,118,33,32,76,352,*43
*/
void gsv(uint8_t *buf){
//DBG("gsv: %s\n", buf);
int Nrec = -1, Ncur, inview = 0;
static int sat_scanned = 0;
if(sscanf((char*)buf, "%d,%d,", &Nrec, &Ncur) != 2) goto ret;
SKIP(2);
if(sscanf((char*)buf, "%d,", &inview) != 1) goto ret;
if(inview < 1) goto ret;
NEXT();
if(Ncur == 1)
PRINT("%d satellites in view field: (No: ID, elevation, azimuth, SNR)\n", inview);
do{
int id, el, az, snr;
// there could be no "SNR" field if we can't find this satellite on sky
if(sscanf((char*)buf, "%d,%d,%d,%d,", &id, &el, &az, &snr) < 3) break;
PRINT(" (%d: %d, %d, %d, %d)", ++sat_scanned, id, el, az, snr);
SKIP(4);
}while(1);
ret:
if(inview < 1) PRINT("There's no GPS satellites in viewfield\n");
if(Nrec > 0 && Nrec == Ncur){
sat_scanned = 0;
PRINT("\n");
}
}
/*
* Recommended minimum specific GPS/Transit data
* $GPRMC,hhmmss,status,latitude,N,longitude,E,spd,cog,ddmmyy,mv,mvE,mode*cs
* 1 = UTC of position fix
* 2 = Data status (V=navigation receiver warning)
* 3 = Latitude of fix
* 4 = N or S
* 5 = Longitude of fix
* 6 = E or W
* 7 = Speed over ground in knots
* 8 = Cource over ground in degrees
* 9 = UT date
* 10 = Magnetic variation degrees (Easterly var. subtracts from true course)
* 11 = E or W
* 12 = Mode: N(bad), E(approx), A(auto), D(diff)
* 213457.00,A,4340.59415,N,04127.47560,E,2.494,,290615,,,A*7B
*/
void rmc(uint8_t *buf){
//DBG("rmc: %s\n", buf);
int H, M, LO, LA, d, m, y, getdate = 0;
double S, longitude, lattitude, speed, track, mag;
char varn = 'V', north = '0', east = '0', mageast = '0', mode = 'N';
sscanf((char*)buf, "%2d%2d%lf", &H, &M, &S);
NEXT();
if(*buf != ',') varn = *buf;
if(varn != 'A')
PRINT("(data could be wrong)");
else{
PRINT("(data valid)");
if(GP->date) getdate = 1; // as only we have valid data we show it to user
}
PRINT(" time: %02d:%02d:%05.2f", H, M, S);
PRINT(" timediff: %g", timediff(H, M, S));
NEXT();
sscanf((char*)buf, "%2d%lf", &LA, &lattitude);
NEXT();
if(*buf != ','){
north = *buf;
lattitude = (double)LA + lattitude / 60.;
if(north == 'S') lattitude = -lattitude;
PRINT(" latt: %g", lattitude);
Latt_mean += lattitude;
Latt_sq += lattitude*lattitude;
++Latt_N;
}
NEXT();
sscanf((char*)buf, "%3d%lf", &LO, &longitude);
NEXT();
if(*buf != ','){
east = *buf;
longitude = (double)LO + longitude / 60.;
if(east == 'W') longitude = -longitude;
PRINT(" long: %g", longitude);
Long_mean += longitude;
Long_sq += longitude*longitude;
++Long_N;
}
NEXT();
if(*buf != ','){
sscanf((char*)buf, "%lf", &speed);
PRINT(" speed: %gknots", speed);
}
NEXT();
if(*buf != ','){
sscanf((char*)buf, "%lf", &track);
PRINT(" track: %gdeg,True", track);
}
NEXT();
if(sscanf((char*)buf, "%2d%2d%2d", &d, &m, &y) == 3)
PRINT(" date(dd/mm/yy): %02d/%02d/%02d", d, m, y);
if(getdate) show_date(H,M,S,d,m,y); // show date & exit
NEXT();
sscanf((char*)buf, "%lf,%c", &mag, &mageast);
if(mageast == 'E' || mageast == 'W'){
if(mageast == 'W') mag = -mag;
PRINT(" magnetic var: %g", mag);
}
SKIP(2);
if(*buf != ','){
mode = *buf;
PRINT(" mode: %c", mode);
}
ret:
PRINT("\n");
}
/*
* Overall Satellite data
* $GPGSA,Smode,FS{,sv},PDOP,HDOP,VDOP*cs
* 1 = mode: 'M' - manual, 'A' - auto
* 2 = fix status: 1 - not available, 2 - 2D, 3 - 3D
* 3..14 = used satellite number
* 15 = position dilution
* 16 = horizontal dilution
* 17 = vertical dilution
* A,2,04,31,32,14,19,,,,,,,,2.77,2.58,1.00*05
*/
void gsa(uint8_t *buf){
//DBG("gsa: %s\n", buf);
int used[12];
int i, Nused = 0;
if(*buf == 'M') PRINT("Mode: manual; ");
else if(*buf == 'A') PRINT("Mode: auto; ");
else return; // wrong data
NEXT();
switch(*buf){
case '1':
PRINT("no fix; ");
break;
case '2':
PRINT("2D fix; ");
break;
case '3':
PRINT("3D fix; ");
break;
default:
goto ret;
}
NEXT();
for(i = 0; i < 12; ++i){
int N;
if(sscanf((char*)buf, "%d,", &N) == 1)
used[Nused++] = N;
NEXT();
}
if(Nused){
PRINT("%d satellites used: ", Nused);
for(i = 0; i < Nused; ++i)
PRINT("%d, ", used[i]);
}
double pos, hor, vert;
if(sscanf((char*)buf, "%lf,%lf,%lf", &pos, &hor, &vert) == 3){
PRINT("DILUTION: pos=%.2f, hor=%.2f, vert=%.2f", pos, hor, vert);
}
ret:
PRINT("\n");
}
// 213457.00,4340.59415,N,04127.47560,E,1,05,2.58,1275.8,M,17.0,M,,*60
void gga(_U_ uint8_t *buf){
//DBG("gga: %s\n", buf);
}
//4340.59415,N,04127.47560,E,213457.00,A,A*60
void gll(_U_ uint8_t *buf){
//DBG("gll: %s\n", buf);
}
// ,T,,M,2.494,N,4.618,K,A*23
void vtg(_U_ uint8_t *buf){
//DBG("vtg: %s\n", buf);
}
void txt(_U_ uint8_t *buf){
DBG("txt: %s\n", buf);
}
/**
* PUBX,00
* $PUBX,00,hhmmss.ss,Latitude,N,Longitude,E,AltRef,NavStat,Hacc,Vacc,SOG,COG,Vvel,ageC,HDOP,VDOP,TDOP,GU,RU,DR,*cs
* here buf starts from hhmmss.ss == 1
* 1 = UTC time
* 2 = lattitude
* 3 = N/S
* 4 = longitude
* 5 = E/W
* 6 = altitude
* 7 = nav.stat: NF/DR/G2/G3/D2/D3/RK/TT
* 8 = horizontal accuracy
* 9 = vertical accuracy
* 10 = speed over ground (km/h)
* 11 = cource over ground (deg)
* 12 = vertical velocy ("+" -- down, "-" -- up)
* 13 = age of most recent DGPS correction
* 14 = hor. dilution
* 15 = vert. dilution
* 16 = time dilution
* 17 = number of GPS satell. used
* 18 = number of GLONASS sat. used
* 19 = DR used
* $PUBX,00,113123.00,4340.61823,N,04127.45581,E,1295.919,G2,30,6.9,1.875,38.17,0.000,,2.77,1.00,1.41,3,0,0*4C
*/
void pubx(uint8_t *buf){
//DBG("pubx_00: %s\n", buf);
int H, M, LO, LA, gps, glo, dr;
double S, longitude, lattitude, altitude, hacc, vacc, speed, track, vertspd,
age, hdop, vdop, tdop;
char north = '0', east = '0';
sscanf((char*)buf, "%2d%2d%lf", &H, &M, &S);
PRINT("time: %02d:%02d:%05.2f", H, M, S);
PRINT(" timediff: %g", timediff(H, M, S));
NEXT();
sscanf((char*)buf, "%2d%lf", &LA, &lattitude);
NEXT();
if(*buf != ','){
north = *buf;
lattitude = (double)LA + lattitude / 60.;
if(north == 'S') lattitude = -lattitude;
PRINT(" latt: %g", lattitude);
Latt_mean += lattitude;
Latt_sq += lattitude*lattitude;
++Latt_N;
}
NEXT();
sscanf((char*)buf, "%3d%lf", &LO, &longitude);
NEXT();
if(*buf != ','){
east = *buf;
longitude = (double)LO + longitude / 60.;
if(east == 'W') longitude = -longitude;
PRINT(" long: %g", longitude);
Long_mean += longitude;
Long_sq += longitude*longitude;
++Long_N;
}
NEXT();
#define FSCAN(par, nam) do{if(*buf != ','){sscanf((char*)buf, "%lf", &par); \
PRINT(" " nam ": %g", par);} NEXT();}while(0)
FSCAN(altitude, "altitude");
if(*buf != ','){
PRINT(" nav. status: %c%c", buf[0], buf[1]);
}
NEXT();
FSCAN(hacc,"hor.accuracy");
FSCAN(vacc, "vert.accuracy");
FSCAN(speed,"speed");
FSCAN(track,"cource");
FSCAN(vertspd,"vertical speed ('+'-down)");
FSCAN(age,"DGPS age");
FSCAN(hdop, "hor. dilution");
FSCAN(vdop, "vert. dilution");
FSCAN(tdop, "time dilution");
#undef FSCAN
#define ISCAN(par, nam) do{if(*buf != ','){sscanf((char*)buf, "%d", &par); \
PRINT(" " nam ": %d", par);} NEXT();}while(0)
ISCAN(gps, "GPS used");
ISCAN(glo, "GLONASS used");
ISCAN(dr, "DR used");
#undef ISCAN
ret:
PRINT("\n");
}
/**
* Parce content of buffer with GPS data
* WARNING! This function changes data content
*/
void parce_data(uint8_t *buf){
uint8_t *eol;
//DBG("GOT: %s", buf);
while(*buf && (eol = (uint8_t*)strchr((char*)buf, '\r'))){
*eol = 0;
// now make checksum checking:
if(!checksum(buf)) goto cont;
if(strncmp((char*)buf, "$GP", 3)){
if(strncmp((char*)buf, "$PUBX,00,", 9) == 0){
buf += 9;
pubx(buf);
}else{
DBG("Bad string: %s\n", buf);
}
goto cont;
}
buf += 3;
// PARSE variants: GSV, RMC, GSA, GGA, GLL, VTG, TXT
// 1st letter, cold be one of G,R,V or T
switch(*buf){
case 'G': // GSV, GSA, GGA, GLL
++buf;
if(strncmp((char*)buf, "SV", 2) == 0){
gsv(buf+3);
}else if(strncmp((char*)buf, "SA", 2) == 0){
gsa(buf+3);
}else if(strncmp((char*)buf, "GA", 2) == 0){
gga(buf+3);
}else if(strncmp((char*)buf, "LL", 2) == 0){
gll(buf+3);
}else{
DBG("Unknown: $GPG%s", buf);
goto cont;
}
break;
case 'R': // RMC
++buf;
if(strncmp((char*)buf, "MC", 2) == 0){
rmc(buf+3);
}else{
DBG("Unknown: $GPR%s", buf);
goto cont;
}
break;
case 'V': // VTG
++buf;
if(strncmp((char*)buf, "TG", 2) == 0){
vtg(buf+3);
}else{
DBG("Unknown: $GPV%s", buf);
goto cont;
}
break;
case 'T': // TXT
++buf;
if(strncmp((char*)buf, "XT", 2) == 0){
txt(buf+3);
}else{
DBG("Unknown: $GPT%s", buf);
goto cont;
}
break;
default:
DBG("Unknown: $GP%s", buf);
goto cont;
}
cont:
if(eol[1] != '\n') break;
buf = eol + 2;
}
}
/**
* set rate for given NMEA field
* @param field - name of NMEA field
* @param rate - rate in seconds (0 disables field)
* @return -1 if fails, rate if OK
*/
int nmea_fieldrate(uint8_t *field, int rate){
uint8_t buf[256];
if(rate < 0) return -1;
snprintf((char*)buf, 255, "$PUBX,40,%s,0,%d,0,0*", field, rate);
if(write_with_checksum(buf)) return rate;
else return -1;
}
int main(int argc, char **argv){
check4running(argv, PIDFILE, NULL);
initial_setup();
GP = parce_args(argc, argv);
assert(GP);
DBG("Device path: %s\n", GP->devpath);
tty_init(GP->devpath);
signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGHUP, signals); // hup - quit
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
int i;
if(GP->date) GP->block_msg[GPRMC] = 1; // we calculate date in RMC events
for(i = 0; i < GPMAXMSG; ++i){
int block = 0;
if(GP->block_msg[i])
block = 1; // unblock message
if(nmea_fieldrate((uint8_t*)GPmsgs[i], block) != block)
WARN("Can't %sblock %s!", block?"un":"", GPmsgs[i]);
else
PRINT("%sblock %s\n", block?"un":"", GPmsgs[i]);
}
if(GP->stationary){
PRINT("stationary config\n");
cfg_stationary();
}
double T, T0 = dtime(), Tpoll = 0., tmout = GP->polltmout;
uint8_t *str = NULL;
while(((T=dtime()) - T0 < tmout) || GP->date){ // if we want get date, we should wait a lot
if(GP->pollubx && T-Tpoll > GP->pollinterval){
Tpoll = T;
write_tty((uint8_t*)"$PUBX,00*33\r\n", 13);
}
if((str = get_portdata())){
parce_data(str);
}
}
if(GP->gettimediff)
printf("\nAverage time difference (local-GPS) = %g seconds\n", timediff_aver/timediff_N);
if(GP->meancoords){
printf("\nAverage coordinates:\n\tLattitude");
double mean, std, s;
int d, m;
if(Latt_N){
mean = Latt_mean / Latt_N;
std = sqrt(Latt_sq/Latt_N - mean*mean);
d = (int)mean; m = (int)(60.*(mean-d)); s = 3600*fabs(mean-d-m/60.);
if(m < 0) m = -m;
printf(" = %.6f (%d %d' %.2f''), std = %g (%.2f'')\n", mean, d, m, s, std, std*3600.);
}else printf(": no data\n");
printf("\tLongitude");
if(Long_N){
mean = Long_mean / Long_N;
std = sqrt(Long_sq/Long_N - mean*mean);
d = (int)mean; m = (int)(60.*(mean-d)); s = 3600*fabs(mean-d-m/60.);
if(m < 0) m = -m;
printf(" = %.6f (%d %d' %.2f''), std = %g (%.2f'')\n", mean, d, m, s, std, std*3600.);
}else printf(": no data\n");
}
signals(0);
return 0;
}