-
Notifications
You must be signed in to change notification settings - Fork 2
/
wsocknmea.c
2478 lines (2113 loc) · 89.3 KB
/
wsocknmea.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* wsocknmea.c
*
* Copyright (C) 2013-2024 by Erland Hedman <erland@hedmanshome.se>
*
* 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.
*
* Description:
* Connect to an nmea mux such as the kplex daemon and collect data
* that virtual instruments can obtain by using the websocket transport
* protocol by issuing requests that this server will reply with
* JSON formatted data to the requester.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <zlib.h>
#include <ctype.h>
#include <pthread.h>
#include <sqlite3.h>
#include <sys/mman.h>
#include <libwebsockets.h>
#include <json-c/json.h>
#include <syslog.h>
#include <math.h>
#include <portable.h>
#include <nmea.h>
#include <sixbit.h>
#include <vdm_parse.h>
#include "wsocknmea.h"
// Configuration
#define NMEAPORT 10110 // Port 10110 is designated by IANA for "NMEA-0183 Navigational Data"
#define NMEAADDR "127.0.0.1" // localhost for TCP for UDP use 239.194.4.4 for a multicast address
#define WSPORT 8080 // Port for the websocket protocol (to be allowed by firewall)
#ifndef KPCONFPATH
#define KPCONFPATH "/etc/default/kplex.conf" // KPlex configuration file writable for webserver
#endif
#ifndef UPLOADPATH
#define UPLOADPATH "/var/www/upload" // PATH to NMEA recordings
#endif
#define FIFOKPLEX "/tmp/fifo_kplex" // KPlex fifo for file nmea input
#define FIFOPNMEA "/tmp/fifo_nmea_p" // KPlex fifo for "$P". These extended messages are not standardized.
#define WSREBOOT "/tmp/wss-reboot" // Arguments to this server from WEB GUI
#ifndef UID // UID/GID - Set to webserver's properties so that db and kplex config files can be writtin to
#define UID 33
#endif
#ifndef GID
#define GID 33
#endif
#define DEFAULT_AUTH "200ceb26807d6bf99fd6f4f0d1ca54d4" // MD5 password = "administrator" used outside LAN
#define DEFAULT_AUTH_DURATION 60 // Seconds of open GUI
#ifdef REV
#define SWREV REV
#else
#define SWREV __DATE__
#endif
// Sentences to control AIS transmitter on/off. Works for Ray, SRT and others.
// NOTE: "--QuaRk--" is actually a password that might changed by the manufacturer.
#define AIXTRXON "\r\n$PSRT,012,,,(--QuaRk--)*4B\r\n$PSRT,TRG,02,00*6A\r\n"
#define AISTRXOFF "\r\n$PSRT,012,,,(--QuaRk--)*4B\r\n$PSRT,TRG,02,33*6A\r\n"
#define MAX_LWSZ 32768 // Max payload size for uncompressed websockets data
#define WS_FRAMEZ 8192 // Websocket frame size
#define MAX_TTYS 50 // No of serial devices to manage in the db
#define MAX_NICS 6 // No of nics to manage in the db
#define POLLRATE 5 // Rate to collect data in ms.
#define INVALID 4 // Invalidate current sentences after # seconds without a refresh from talker.
#define NMPARSE(str, nsent) !strncmp(nsent, &str[3], strlen(nsent))
// Request codes from virtual instruments
enum requests {
SpeedOverGround = 100,
SpeedThroughWater = 101,
DepthAndTemp = 110,
CompassHeading = 120,
GPS = 121,
WindSpeedAndAngle = 130,
GoogleMapFeed = 140,
GoogleAisFeed = 141,
GoogleAisBuddy = 142,
SensorVolt = 200,
SensorCurr = 201,
SensorTemp = 202,
SensorRelay = 203,
tdtStatus = 205,
AisTrxStatus = 206,
WaterTankData = 210,
ServerPing = 900,
TimeOfDAy = 901,
SaveNMEAstream = 904,
StatusReport = 906,
Authentication = 908,
UpdateSettings = 910
};
static int debug = 0;
static int backGround = 0;
static int muxFd = 0;
static pthread_t threadKplex = 0;
static pid_t pidKplex = 0;
static int kplexStatus = 0;
static int pNmeaStatus = 1;
static int sigExit = 0;
static int smartplugRun = 1;
static useconds_t lineRate = 1;
static int fileFeed = 0;
static struct sockaddr_in peer_sa;
static struct lws_context *ws_context = NULL;
static int socketType = 0;
static char interFace[40];
static int socketCast = 0;
static char *programName;
static int unusedInt __attribute__((unused)); // To make -Wall shut up
static FILE * unusedFd __attribute__((unused));
static int adcFd;
typedef struct {
// Static configs from GUI sql db
int map_zoom; // Google Map Zoom factor;
int map_updt; // Update Time
int depth_vwrn; // Depth visual low warning
float depth_transp; // Depth of transponer
char adc_dev[40]; // ADC in /dev
char ais_dev[40]; // AIS in /dev
float shuntRS; // Current shunt resistance
float shuntTV; // Voltage/tick
float shuntTG; // Voltage/tick gain
int venus; // Use Victron Venus flag
char venusBA[40]; // Venus battery bank name
float voltLow; // Voltage low warning level
char fdn_outf[120]; // NMEA stream output file name
FILE *fdn_stream; // Save NMEA stream file
time_t fdn_starttime; // Start time for recording
time_t fdn_endtime; // End time for recording
char fdn_inf[PATH_MAX]; // NMEA stream input file
char md5pw[40]; // Password for GUI
int m55pw_ts; // Passwd valid x seconds;
char client_ip[20]; // Granted client
} in_configs;
static in_configs iconf;
typedef struct {
long my_userid; // AIS own user i.d
long my_buddy; // AIS new buddy
char my_name[80]; // AIS own name
int my_useais; // AIS use or not
} in_aisconfigs;
static in_aisconfigs aisconf;
typedef struct {
// Dynamic data from sensors
float rmc; // RMC (Speed Over Ground) in knots
time_t rmc_ts; // RMC Timestamp
float stw; // Speed of vessel relative to the water (Knots)
time_t stw_ts; // STW Timestamp
float dbt; // Depth in meters
time_t dbt_ts; // DBT Timestamp
float mtw; // Water temperature
time_t mtw_ts; // Water temperature Timestamp
float hdm; // Heading
time_t hdm_ts; // HDM Timestamp
float vwra; // Relative wind angle (0-180)
float vwta; // True wind angle
time_t vwr_ts; // Wind data Timestamp
time_t vwt_ts; // True wind data Timestamp
int vwrd; // Right or Left Heading
float vwrs; // Relative wind speed knots
float vwts; // True wind speed
char gll[40]; // Position Latitude
time_t gll_ts; // Position Timestamp
char glo[40]; // Position Longitude
char glns[2]; // North (N) or South (S)
char glne[2]; // East (E) or West (W)
int txs; // AIS transponder transmitter status
time_t txs_ts; // AIS transponder Timestamp
// Sensors non NMEA
float volt; // Sensor Volt
time_t volt_ts; // Volt Timestamp
float curr; // Sensor Current
time_t curr_ts; // Current Timestamp
float temp; // Sensor Temp
time_t temp_ts; // Temp Timestamp
float kWhp; // Kilowatt hour - charged
float kWhn; // Kilowatt hour - consumed
time_t upTime; // Server's uptime
time_t startTime; // Server's starttime
#ifdef DIGIFLOW
time_t fdate; // Filter date
float tvol; // Total consumed volume
float gvol; // Grand total consumed volume
float tank; // Tank Volume
int tds; // TDS value
float ttemp; // Water temp
#endif
} collected_nmea;
static collected_nmea cnmea;
void printlog(char *format, ...)
{
va_list args;
char buf[200];
static char oldbuf[200];
int ern = errno;
va_start (args, format); // Initialize the argument list.
vsprintf(buf, format, args);
if (!strncmp(buf, oldbuf, sizeof(buf))) {
va_end(args);
return; // Do not repeate same msgs from thread loops
} else {
strncpy(oldbuf,buf, sizeof(oldbuf));
}
if (backGround) {
syslog(LOG_NOTICE, "%s", buf);
} else {
fprintf(stderr, "%s\n", buf);
}
va_end(args);
errno = ern;
}
#ifdef DIGIFLOW
static int doDigiflow()
{
// dpendent on https://github.com/ehedman/flowSensor
FILE *tankFd;
int tankIndx = 0;
char tBuff[40];
struct stat statbuf;
int rval = 1;
system("digiflow.sh /tmp/digiflow.txt");
if ((tankFd = fopen("/tmp/digiflow.txt","r")) != NULL) {
if (fstat(fileno(tankFd), &statbuf) == 0 && statbuf.st_size != 0) {
while( fgets(tBuff, sizeof(tBuff), tankFd) != NULL) {
switch(tankIndx++) {
case 0: cnmea.fdate = atol(tBuff); break;
case 1: cnmea.tvol = atof(tBuff); break;
case 2: cnmea.gvol = atof(tBuff); break;
case 3: cnmea.tank = atof(tBuff); break;
case 4: cnmea.tds = atoi(tBuff); break;
case 5: cnmea.ttemp = atof(tBuff);
rval = 0;
break;
default: rval = 1; break;
}
}
}
fclose(tankFd);
unlink("/tmp/digiflow.txt");
}
return rval;
}
#endif /* DIGIFLOW */
static void do_sensors(time_t ts, collected_nmea *cn)
{
#ifdef DOADC
float a2dVal;
float crefVal;
int ad2Tick;
static int firstTurn;
static int ccnt;
static float avcurr;
static float sampcurr[200]; // No of samples to collect
static int tcnt;
static float avtemp;
static float samptemp[20];
static int vcnt;
static float sampvolt[20];
static float avvolt;
const float tickVolt = iconf.shuntTV; // Volt / tick according to external electrical circuits
const float tickcrVolt = iconf.shuntTG; // 10-bit adc over 5V for current messurement
float crShunt = iconf.shuntRS; // Current shunt (ohm) according to external electrical circuits
const float cGain = 50; // Current sense amplifier gain for LT1999-50
const float cZero = 1.6; // Sense lines in short-circuit should read 0
const int linearize = 0; // No extra compesation needed
if (iconf.venus == 1) {
/*
* Assuming that Victron Venus@rpi is running in a docker container on this host.
* The json data is dumped to a shared area in /run and it originates from a Simarine Pico device.
* See project @ https://github.com/ehedman/pico2venus and https://github.com/ehedman/victron-venus-container
*/
char *json_string;
FILE *fd = NULL;
struct stat st_buf;
struct json_object *obj, *json_obj, *name_obj, *voltage_obj, *current_obj;
char *jsFile = "/run/udev/data/pico-data.json";
if (!stat(jsFile, &st_buf) && st_buf.st_size > 10 && (fd = fopen(jsFile, "r")) != NULL) {
json_string = malloc(st_buf.st_size);
fread(json_string, sizeof(char), st_buf.st_size, fd);
fclose(fd);
// Parse JSON string
if ((json_obj = json_tokener_parse(json_string)) != NULL) {
// Iterate through each key-value pair
json_object_object_foreach(json_obj, key, val) {
//printf("Key: %s\n", key);
// Get the object corresponding to the current key
obj = json_object_object_get(json_obj, key);
if (json_object_object_get_ex(obj, "name", &name_obj)) {
if (!strcmp(json_object_get_string(name_obj), iconf.venusBA)) {
//printf("Found Name: %s\n", json_object_get_string(name_obj));
json_object_object_get_ex(obj, "voltage", &voltage_obj);
json_object_object_get_ex(obj, "current", ¤t_obj);
cn->volt = json_object_get_double(voltage_obj);
cn->curr = (1.0-json_object_get_double(current_obj)-1.0);
cn->volt_ts = cn->curr_ts = ts;
}
}
}
// Free the JSON object
json_object_put(json_obj);
free(json_string);
} else {
free(json_string);
}
}
} else {
ad2Tick = adcRead(voltChannel);
a2dVal = tick2volt(ad2Tick, tickVolt, 0); // Return voltage, no invert
// Calculate an average in case of ADC drifts.
if (a2dVal >= VOLTLOWLEVEL) {
if (linearize) {
sampvolt[vcnt++] = a2dVal;
if (vcnt >= sizeof(sampvolt)/sizeof(float)) {
vcnt = 0;
for (int i=0; i < sizeof(sampvolt)/sizeof(float); i++) {
avvolt += sampvolt[i];
}
avvolt /= sizeof(sampvolt)/sizeof(float);
}
} else {
avvolt = a2dVal;
}
cn->volt = avvolt;
cn->volt_ts = ts;
}
// Alert about low voltage/ENV
if (! firstTurn ) {
a2dNotice(voltChannel, cn->volt, iconf.voltLow, iconf.voltLow*1.06);
firstTurn = 1;
}
//ad2Tick = adcRead(crefChannel); // Read volt refrerence from current sensor
//crefVal = tick2volt(ad2Tick, tickcrVolt, 0); // Return voltage, no invert
crefVal = 2.486; // Reference voltage measured by hand
ad2Tick = adcRead(currChannel);
a2dVal = tick2current(ad2Tick, tickcrVolt, crefVal, crShunt, cGain, 0)-cZero; // Return current, no invert
// Calculate an average in case of ADC drifts.
if (a2dVal >= CURRLOWLEVEL) {
if (linearize+1) {
int i;
sampcurr[ccnt++] = a2dVal;
if (ccnt >= sizeof(sampcurr)/sizeof(float)) {
ccnt = 0;
for (i=0; i < sizeof(sampcurr)/sizeof(float); i++) {
avcurr += sampcurr[i];
}
avcurr /= sizeof(sampcurr)/sizeof(float);
}
} else {
avcurr = a2dVal;
}
cn->curr = avcurr;
cn->curr_ts = ts;
}
}
a2dVal = adcRead(tempChannel);
a2dVal *= ADCTICKSTEMP;
// Calculate an average in case of ADC drifts.
if (a2dVal >= TEMPLOWLEVEL) {
if (linearize) {
samptemp[tcnt++] = a2dVal;
if (tcnt >= sizeof(samptemp)/sizeof(float)) {
tcnt = 0;
for (int i=0; i < sizeof(samptemp)/sizeof(float); i++) {
avtemp += samptemp[i];
}
avtemp /= sizeof(samptemp)/sizeof(float);
}
} else {
avtemp = a2dVal;
}
cn->temp = avtemp;
cn->temp_ts = ts;
}
#else
// Just for demo
cn->volt = (float)13.0 + (float)(rand() % 18)/100;
cn->volt_ts = ts;
cn->curr = (float)-2.0 + (float)(rand() % 18)/100;
cn->curr_ts = ts;
cn->temp = (float)20.0 + (float)(rand() % 18)/100;
cn->temp_ts = ts;
#endif
}
static void parse(char *line, char **argv)
{
*argv++ = programName;
while (*line != '\0') {
while (*line == ' ' || *line == '\t' || *line == '\n')
*line++ = '\0'; // replace white spaces with 0
*argv++ = line; // save the argument position
while (*line != '\0' && *line != ' ' &&
*line != '\t' && *line != '\n')
line++; // skip the argument until ...
}
*argv = '\0'; // mark the end of argument list
}
static void exit_clean(int sig)
{
printlog("Terminating NMEA and KPLEX Services - reason: %d", sig);
fileFeed = 0;
sigExit = 1;
int fd;
pid_t pid;
char argstr[100];
pNmeaStatus = 0;
smartplugRun = 0;
sleep(1);
#if 0
if (threadKplex) {
if (pthread_self() != threadKplex)
pthread_cancel(threadKplex);
}
#endif
if (pidKplex) {
kill (pidKplex, SIGINT);
pidKplex = 0;
}
sleep(2);
if(muxFd > 0)
close(muxFd);
if (ws_context != NULL)
lws_context_destroy(ws_context);
if (sig == SIGSTOP) {
if ((fd = open(WSREBOOT, O_RDONLY)) >0) {
if (read(fd, argstr, sizeof(argstr))>0) {
(void)close(fd); (void)unlink(WSREBOOT);
pid = fork();
if (pid == 0) {
/* child */
char *args[100];
sleep(2);
parse(argstr, args);
execvp(programName, args);
printlog("Failed to execute %s: %s", programName, strerror(errno));
exit(errno);
}
}
}
}
(void)unlink(WSREBOOT);
(void)unlink(SMARTPLUGSTS);
if (backGround) {
closelog();
}
exit(EXIT_SUCCESS);
}
static char *getf(int pos, char *str)
{ // Extract an item from a sentence
int len=strlen(str);
int i,j,k;
int npos=0;
static char out[200];
memset(out,0,sizeof(out));
pos--;
for (i=0; i<len;i++) {
if (str[i]==',') {
if (npos++ == pos) {
strcat(out, &str[++i]);
j=strlen(out);
for (k=0; k<j; k++) {
if (out[k] == ',') {
out[k] = '\0';
i=len;
break;
}
}
}
}
}
return (out);
}
// Open a socket to the kplex MUX
static int nmea_sock_open(int kplex_fork)
{
u_int yes = 1;
struct ip_mreq mreq;
long flags;
if (muxFd > 0)
close(muxFd);
// Create the socket
if (!socketType) socketType = SOCK_STREAM;
if (ntohs(peer_sa.sin_port) == 0) peer_sa.sin_port = htons(NMEAPORT);
if (peer_sa.sin_addr.s_addr == 0) peer_sa.sin_addr.s_addr = inet_addr(NMEAADDR);
if ((muxFd = socket(AF_INET, socketType, 0)) < 0) {
printlog("Failed to create NMEA socket: %s", strerror(errno));
return 1;
}
// Allow multiple sockets to use the same port number
if (setsockopt(muxFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
printlog("Failed to set REUSEADDR on NMEA socket: %s", strerror(errno));
close(muxFd);
return 1;
}
if (socketType == SOCK_DGRAM) { // UDP part needs more testing
if (strlen(interFace)) {
if (setsockopt(muxFd, SOL_SOCKET, SO_BINDTODEVICE, interFace, strlen(interFace))) {
printlog("Failed to bind NMEA socket to device %s: %s", interFace, strerror(errno));
close(muxFd);
return 1;
}
}
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(peer_sa.sin_addr), str, INET_ADDRSTRLEN);
if (socketCast == SO_BROADCAST) {
//peer_sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (setsockopt(muxFd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes))) {
printlog("Failed to set SO_BROADCAST on NMEA socket: %s", strerror(errno));
close(muxFd);
return 1;
}
}
if (socketCast == IP_MULTICAST_IF) {
// use setsockopt() to request that the kernel join a multicast group
mreq.imr_multiaddr.s_addr = inet_addr(str);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(muxFd, IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
printlog("Failed to add multicast membership on NMEA socket: %s", strerror(errno));
close(muxFd);
return 1;
}
}
// Bind to receive address
if (bind(muxFd, (struct sockaddr *) &peer_sa, sizeof(peer_sa)) < 0) {
printlog("Failed to bind to UDP address on NMEA socket: %s", strerror(errno));
close(muxFd);
return 1;
}
} else if (!kplex_fork) {
if (connect(muxFd, (struct sockaddr*)&peer_sa, sizeof(peer_sa)) < 0) {
printlog("Failed to connect to NMEA socket: %s : is a mux (kplex) running?", strerror(errno));
return 1;
}
}
// Set socket nonblocking
flags = fcntl(muxFd, F_GETFL, NULL);
flags |= O_NONBLOCK;
fcntl(muxFd, F_SETFL, flags);
return 0;
}
// The configuration database is maintained from the WEB GUI settings dialogue.
static int configure(int kpf)
{
sqlite3 *conn;
sqlite3_stmt *res;
const char *tail, *cast;
char *ptr, buf[200];
int rval = 0;
int i, fd;
struct stat sb;
// Defaults
iconf.map_zoom = 12;
iconf.map_updt = 6;
iconf.depth_vwrn = 4;
iconf.client_ip[0] = '\0';
iconf.shuntRS = 0.00015;
iconf.shuntTV = 0.01356;
iconf.shuntTG = 0.004882;
// If kplex.conf and/or a config database file is missing, create templates so that
// we keep the system going. The user has then to create his own profile from the web gui.
if (kpf) {
if (stat(KPCONFPATH, &sb) <0) {
if (errno == ENOENT) {
char str[200];
sprintf(str, "[file]\nfilename=%s\ndirection=in\neol=rn\npersist=yes\n\n[tcp]\nmode=server\naddress=127.0.0.1\nport=%d\n", FIFOPNMEA, NMEAPORT);
printlog("KPlex configuration file is missing. Creating an empty config file as %s ", KPCONFPATH);
if ((fd=open(KPCONFPATH, O_CREAT | O_WRONLY, 0664)) < 0) {
printlog("Attempt to create %s failed: %s", KPCONFPATH, strerror(errno));
return 1;
}
unusedInt = write(fd, str, strlen(str));
(void)close (fd);
unusedInt = chown(KPCONFPATH, (uid_t)UID, (gid_t)GID);
} else {
printlog("Cannot access %s: %s: ", KPCONFPATH, strerror(errno));
return 1;
}
}
}
memset(&sb, 0, sizeof(struct stat));
(void)stat(NAVIDBPATH, &sb);
rval = errno;
if (sqlite3_open_v2(NAVIDBPATH, &conn, SQLITE_OPEN_READONLY, 0) || sb.st_size == 0) {
(void)sqlite3_close(conn);
if (sb.st_size == 0) rval = ENOENT;
if (!sb.st_size) {
switch (rval) {
case EACCES:
printlog("Configuration database %s: ", strerror(rval));
return 1;
case ENOENT:
printlog("Configuration database does not exist. A new empty database will be created");
(void)sqlite3_open_v2(NAVIDBPATH, &conn, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE , 0);
if (conn == NULL) {
printlog("Failed to create a new database %s: ", (char*)sqlite3_errmsg(conn));
return 1;
}
sqlite3_prepare_v2(conn, "CREATE TABLE rev(Id INTEGER PRIMARY KEY, rev TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE auth(Id INTEGER PRIMARY KEY, password TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE gmap(Id INTEGER PRIMARY KEY, zoom INTEGER, updt INTEGER, key TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE ttys (Id INTEGER PRIMARY KEY, name TEXT, baud TEXT, dir TEXT, use TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE depth (Id INTEGER PRIMARY KEY, vwrn INTEGER, tdb DECIMAL)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE sumlog (Id INTEGER PRIMARY KEY, display INTEGER, cal INTEGER)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE netif (Id INTEGER PRIMARY KEY, device TEXT, port TEXT, addr TEXT, type TEXT, proto TEXT, use TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE file (Id INTEGER PRIMARY KEY, fname TEXT, rate INTEGER, use TEXT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE ais (Id INTEGER PRIMARY KEY, aisname TEXT, aiscallsign TEXT, aisid BIGINT, aisuse INTEGER, ro INTEGER)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE abuddies (Id INTEGER PRIMARY KEY, userid BIGINT)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE devadc(Id INTEGER PRIMARY KEY, device TEXT, shuntRS REAL, shuntTV REAL, shuntTG REAL, venusBA TEXT, venus INTEGER)", -1, &res, &tail);
sqlite3_step(res);
sprintf(buf, "INSERT INTO devadc (device,shuntRS,shuntTV,shuntTG,venusBA,venus) VALUES ('%s',%f,%f,%f,'undef',0)", "/dev/null", iconf.shuntRS, iconf.shuntTV, iconf.shuntTG);
sqlite3_prepare_v2(conn, buf, -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "CREATE TABLE devrelay(Id INTEGER PRIMARY KEY, name TEXT, days TEXT, time TEXT, timeout TEXT);", -1, &res, &tail);
sqlite3_step(res);
for (i=0; i <4; i++) {
sprintf(buf, "INSERT INTO devrelay (time) VALUES ('0');");
sqlite3_prepare_v2(conn, buf, -1, &res, &tail);
sqlite3_step(res);
}
sqlite3_prepare_v2(conn, "CREATE TABLE limits(Id INTEGER PRIMARY KEY, volt REAL, current REAL);", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO limits (volt,current) values(11.7,9.0)", -1, &res, &tail);
sqlite3_step(res);
#ifdef REV
sprintf(buf, "INSERT INTO rev (rev) VALUES ('%s')", REV);
#else
sprintf(buf, "INSERT INTO rev (rev) VALUES ('unknown')");
#endif
sqlite3_prepare_v2(conn, buf, -1, &res, &tail);
sqlite3_step(res);
sprintf(buf, "INSERT INTO auth (password) VALUES ('%s')", DEFAULT_AUTH);
sqlite3_prepare_v2(conn, buf, -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO gmap (zoom,updt) VALUES (14,6)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO depth (vwrn,tdb) VALUES (4,1)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO sumlog (display,cal) VALUES (1,2)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO ais (aisname,aiscallsign,aisid,aisuse,ro) VALUES ('my yacht','my call',366881180,1,0)", -1, &res, &tail);
sqlite3_step(res);
sqlite3_prepare_v2(conn, "INSERT INTO file (fname,rate,use) VALUES ('',1,'off')", -1, &res, &tail);
sqlite3_step(res);
for (i=0; i < MAX_TTYS; i++) {
sqlite3_prepare_v2(conn, "INSERT INTO ttys (name,baud,dir,use) VALUES ('undef','4800','in','off')", -1, &res, &tail);
sqlite3_step(res);
}
for (i=0; i < MAX_NICS; i++) {
sqlite3_prepare_v2(conn, "INSERT INTO netif (device,port,addr,type,proto,use) VALUES ('undef','10110','127.0.0.1','unicast','tcp','off')", -1, &res, &tail);
sqlite3_step(res);
}
unusedInt = chown(NAVIDBPATH, (uid_t)UID, (gid_t)GID);
rval = sqlite3_finalize(res);
break;
default:
printlog("Configuration database initialization failed: %s. Try command line options", strerror(rval));
return 0;
}
} else {
printlog("Failed to handle configuration database %s: ", (char*)sqlite3_errmsg(conn));
(void)sqlite3_close(conn);
return 0;
}
}
#ifdef REV
// Check revision of database
rval = sqlite3_prepare_v2(conn, "select rev from rev", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
if(strcmp((ptr=(char*)sqlite3_column_text(res, 0)), REV)) {
printlog("Warning: Database version missmatch in %s", NAVIDBPATH);
printlog("Expected %s but current revision is %s", REV, ptr);
printlog("You may have to remove %s and restart this program to get it rebuilt and then configure the settings from the GUI", NAVIDBPATH);
}
}
#endif
// Google Map Service
rval = sqlite3_prepare_v2(conn, "select zoom,updt from gmap", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
iconf.map_zoom = sqlite3_column_int(res, 0);
iconf.map_updt = sqlite3_column_int(res, 1);
}
// Depth
rval = sqlite3_prepare_v2(conn, "select vwrn from depth", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
iconf.depth_vwrn = sqlite3_column_int(res, 0);
}
// Transponder Depth
rval = sqlite3_prepare_v2(conn, "select tdb from depth", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
iconf.depth_transp = sqlite3_column_double(res, 0);
}
// ADC device
rval = sqlite3_prepare_v2(conn, "select device,shuntRS,shuntTV,shuntTG,venusBA,venus from devadc", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)strcpy(iconf.adc_dev, (char*)sqlite3_column_text(res, 0));
iconf.shuntRS = sqlite3_column_double(res, 1);
iconf.shuntTV = sqlite3_column_double(res, 2);
iconf.shuntTG = sqlite3_column_double(res, 3);
(void)strcpy(iconf.venusBA, (char*)sqlite3_column_text(res, 4));
iconf.venus = sqlite3_column_int(res, 5);
}
// Limits
rval = sqlite3_prepare_v2(conn, "select volt from limits", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
iconf.voltLow = sqlite3_column_double(res, 0);
}
// AIS
rval = sqlite3_prepare_v2(conn, "select aisname,aisid,aisuse from ais", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)strcpy(aisconf.my_name, (char*)sqlite3_column_text(res, 0));
aisconf.my_userid = (long)sqlite3_column_int(res, 1);
aisconf.my_useais = sqlite3_column_int(res, 2);
}
rval = sqlite3_prepare_v2(conn, "select name from ttys where dir = 'Ais' limit 1", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)strcpy(iconf.ais_dev, (char*)sqlite3_column_text(res, 0));
printlog(" AIS device: %s", iconf.ais_dev);
} else iconf.ais_dev[0]= '\0';
// Authentication for GUI
rval = sqlite3_prepare_v2(conn, "select password from auth", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)strcpy(iconf.md5pw, (char*)sqlite3_column_text(res, 0));
iconf.m55pw_ts = 0;
} else iconf.md5pw[0]= '\0';
// Still in file feed config mode?
if ((fd=open(KPCONFPATH, O_RDONLY)) > 0) {
if (read(fd, buf, sizeof(buf)) >0 && strstr(buf, FIFOKPLEX) != NULL) {
rval = sqlite3_prepare_v2(conn, "select fname,rate from file", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)strcpy(iconf.fdn_inf, (char*)sqlite3_column_text(res, 0));
lineRate = sqlite3_column_int(res, 1);
}
if (access( FIFOKPLEX, F_OK ) == -1) { // Must exist from now on ..
if (mkfifo(FIFOKPLEX, (mode_t)0664)) {
printlog("Error create kplex fifo: %s", strerror(errno));
}
}
}
(void)close(fd);
}
// Server network properties if not set on command line
rval = sqlite3_prepare_v2(conn, "select device,addr,port,proto,type from netif where use = 'on'", -1, &res, &tail);
if (rval == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
if (backGround) printlog("Setting network properties from configuration database:");
if (peer_sa.sin_addr.s_addr == 0) {
peer_sa.sin_addr.s_addr = inet_addr((char*)sqlite3_column_text(res, 1));
printlog(" I.P address: %s", (char*)sqlite3_column_text(res, 1));
}
if (ntohs(peer_sa.sin_port) == 0) {
peer_sa.sin_port = htons(atoi((char*)sqlite3_column_text(res, 2)));
printlog(" Port: %s", (char*)sqlite3_column_text(res, 2));
}
if (socketType == 0) {
if (!strncmp((char*)sqlite3_column_text(res, 3),"udp", 3))
socketType = SOCK_DGRAM;
else
socketType = SOCK_STREAM;
printlog(" Protocol: %s", (char*)sqlite3_column_text(res, 3));
}
if (socketType == SOCK_DGRAM) {
if (!strlen(interFace)) {
(void)strcpy(interFace, (char*)sqlite3_column_text(res, 0));
printlog(" Interface: %s", (char*)sqlite3_column_text(res, 0));
}
if (!socketCast) {
cast = (char*)sqlite3_column_text(res, 4);
printlog(" Type: %s", cast);
if (!strcmp("unicast", cast)) socketCast = IP_UNICAST_IF;
else if (!strcmp("multicast", cast)) socketCast = IP_MULTICAST_IF;
else if (!strcmp("broadcast", cast)) socketCast = SO_BROADCAST;
else socketCast = IP_UNICAST_IF;
}
}
} else {
printlog("Autoconfig of server network properties not done. The config database needs to be populated from the GUI");
}
rval = SQLITE_BUSY;
while(rval == SQLITE_BUSY) { // make sure ! busy ! db locked
rval = SQLITE_OK;
sqlite3_stmt * res = sqlite3_next_stmt(conn, NULL);
if (res != NULL) {
rval = sqlite3_finalize(res);
if (rval == SQLITE_OK) {
rval = sqlite3_close(conn);
}
}
}
(void)sqlite3_close(conn);
return 0;
}
static void handle_ais_buddy(long userid)
{
sqlite3 *conn;
sqlite3_stmt *res;
const char *tail;
char sql[60];
(void)sqlite3_open_v2(NAVIDBPATH, &conn, SQLITE_OPEN_READWRITE, 0);
if (conn == NULL) {
printlog("Failed to open database %s to add a buddy: ", (char*)sqlite3_errmsg(conn));
aisconf.my_buddy = 0;
return;
}
(void)sprintf(sql, "SELECT userid FROM abuddies WHERE userid = %ld", userid);
if (sqlite3_prepare_v2(conn, sql, -1, &res, &tail) == SQLITE_OK && sqlite3_step(res) == SQLITE_ROW) {
(void)sqlite3_finalize(res);
(void)sprintf(sql, "DELETE FROM abuddies WHERE userid = %ld", userid);
if (sqlite3_prepare_v2(conn, sql, -1, &res, &tail) == SQLITE_OK) {
(void)sqlite3_step(res);
} else userid = 0;
(void)sqlite3_finalize(res);
} else {
(void)sqlite3_finalize(res);
(void)sprintf(sql, "INSERT INTO abuddies (userid) VALUES (%ld)", userid);
if (sqlite3_prepare_v2(conn, sql, -1, &res, &tail) == SQLITE_OK) {
(void)sqlite3_step(res);
} else userid = 0;
(void)sqlite3_finalize(res);
}
(void)sqlite3_close(conn);
aisconf.my_buddy = userid; // Leave it to be picked up later by addShip()
}
// Save NMEA stream to file
static void saveNmea(void)
{
FILE *fd;
char *ptr;
int mins=0;
char fname[PATH_MAX];
iconf.fdn_stream = NULL;
iconf.fdn_starttime = iconf.fdn_endtime= 0;
if (!strlen(iconf.fdn_outf))
return;