File: header_utils.pas

package info (click to toggle)
udm 1.0.0.352-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 28,076 kB
  • sloc: pascal: 72,496; ansic: 6,892; awk: 880; makefile: 768; sh: 493; perl: 34; python: 22; tcl: 18
file content (1444 lines) | stat: -rw-r--r-- 58,273 bytes parent folder | download
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
unit header_utils;

// Header file utilities

{$mode objfpc}{$H+}
interface

uses
  Classes, SysUtils, StrUtils, Forms, Graphics, dateutils
  , appsettings, dlheader, viewlog
  , synaser, synautil, blcksock
  , math // required for radtodeg
  , Printers, Process
  , LazSysUtils //For NowUTC()
  ;

function OpenComm() : boolean;
function CloseComm() : boolean;
procedure WriteDLHeader(Style:String; Setting:String=''; Ext: String = '.dat');
function SendGet(command:string; LeaveOpen:boolean = False; Timeout:Integer=3000; GetAlso:boolean = True; HideStatus:boolean = False) : string;
function GetReading(): String;
procedure DisplayedReading(Darkness:Double);
procedure StatusMessage(Mstring:string);
procedure GetVersion;
procedure ClearLockVisibility;
procedure CheckLockVisibility;
procedure UpdateCalReport;
procedure PrintLine(LabelText:String; DataText: String='');
function FixDate(incoming:AnsiString): AnsiString;
function ParameterCommand(Command:String): Boolean;

var
   ParameterValue: TStringList;
   FieldNames:String; //Data line entries in human format
   FieldUnits:String; //Units of data line entries

implementation

uses
  Unit1
  , vinfo
  , dlerase //Data logger erase-all form and some variables
  , Vector //VectorTab model
  , Logcont
  ;

{ Open the communications port
   - Will only open if not already opened. }
function OpenComm() : boolean;
begin

   if not CommOpened then begin

     { Clear out old port name. }
     PortName:='';

     { Check selected communications interface }
     case SelectedInterface of
         'USB': begin
              PortName:=Form1.USBPort.Text;
              if (PortName<>'') then begin
                ser.LinuxLock:=False; //lock file sometimes persists stuck if program closes before port
                SelectedPort:=PortName;
                ser.Connect(SelectedPort);
                ser.config(115200, 8, 'N', SB1, False, False); //original
                //ser.config(115200, 8, 'N', SB1, False,True); //test hardware flow control
                PortName:=SelectedPort;
                OpenComm:=True;    //Indicate success
              end;
         end;

         { The XPort Ethernet module may have been set to auto-disconnect in a few seconds.
           Each communication with it must be checked for availability.}
         'Eth','WiFi': begin

              PortName:=Form1.EthernetIP.Text;
              if (PortName<>'') then begin
                EthSocket := TTCPBlockSocket.Create;
                EthSocket.ConvertLineEnd := True;
                //Try setting the timeout a bit longer than normal, because
                // finding Ethernet devices failed a few times on slow Windows7 netbook.
                EthSocket.SetRecvTimeout(2000);
                EthSocket.SetSendTimeout(2000);
                SelectedIP:=PortName;
                SelectedPort:=Form1.EthernetPort.Text;
                EthSocket.Connect(SelectedIP, SelectedPort);
                EthConnected:=True;
                PortName:=SelectedIP;
                OpenComm:=True;    //Indicate success
               end;
         end;

         'RS232': begin
              ser.LinuxLock:=False; //lock file sometimes persists stuck if program closes before port
              //writeln(ser.LastError, ' ', ser.LastErrorDesc);
              //writeln('RS232PortName=', RS232PortName);
              ser.Connect(RS232PortName);
              //writeln(ser.LastError, ' ', ser.LastErrorDesc);
              //writeln('RS232PortBaud=', RS232PortBaud);
              ser.config(RS232PortBaud, 8, 'N', SB1, False, False);
              PortName:=RS232PortName;
              //if ser.InstanceActive then
              //  writeln('active')
              //else begin
              //    writeln('not active');
              //    writeln(ser.LastError, ' ', ser.LastErrorDesc);
              //end;
              OpenComm:=True;    //Indicate success
         end;
     end;

     if OpenComm then begin
         CommOpened:=True;
         //StatusMessage('OpenComm');
         form1.CommOpen.Brush.Color:=clLime;
       end
     else
       CommOpened:=False;
   end;

end;
// Close the communications port
function CloseComm() : boolean;
begin

     unit1.Form1.CommBusy.Enabled:=False; //Prevent further triggers to the comm. busy timer.
     CommBusyTime:=0; //Reset comm. busy count.
     CommOpened:=False; //Indicate that the comm. port is closed.

     {Close all UDM communication ports.
       - Ports not already opened will have an ignored exception.}
     try
        ser.Purge;
        ser.CloseSocket;
     except
        StatusMessage('ser.CloseSocket exception');
     end;

     try
        if EthConnected then begin
          EthSocket.CloseSocket;
          EthSocket.Free;
          EthConnected:=False;
        end;
     except
        StatusMessage('EthSocket.CloseSocket exception');
     end;

     CloseComm:=True;       //Indicate success
     Unit1.Form1.CommOpen.Brush.Color:=clGray;

end;

procedure WriteDLHeader(Style:String; Setting: String = ''; Ext: String = '.dat' );
//Style:
//  DL-Log (Short record)
//  LE (Long record) default
//  ADA (Auroral Detection Alarm)
//  DL-V-Log (Vector model)
//  DL-V-HSLog (Vector model hard soft calibration log)
//Setting describes how UDM was used to create this logfile.
var
  HeaderFirmwareVersion: AnsiString;
  result: AnsiString; //General purpose result
  result_ix: AnsiString; //Information result
  ProtocolNumber,ModelNumber,FeatureNumber,SerialNumber : Integer;
  Info:     TVersionInfo;
  AccCalPos: Integer; //Accelerometer position
  ClockDiffSeconds:Integer;
  ThisMomentUTC, UnitTime: TDateTime;
  UnitClock: AnsiString;
  NumberOfFields:Integer;
  ExternalGPSHeader: Boolean = False;

  HeaderLines: TStringList; //Contrains all headerlines for easier counting up later.
  s:String; // General purpose string

begin

    HeaderLines:=TStringList.Create;

    //Only use external GPS header information if external GPS is enabled and not getting from logger.
    ExternalGPSHeader:=(FormLogCont.GPSLogIndicator.Visible and not AnsiContainsStr(Setting,'retrieve'));

    {Gather information about the selected unit}
    result_ix:=SendGet('ix');
    ProtocolNumber:=StrToIntDef(AnsiMidStr(result_ix,3,8),0);
    ModelNumber:=StrToIntDef(AnsiMidStr(result_ix,12,8),0);
    FeatureNumber:=StrToIntDef(AnsiMidStr(result_ix,21,8),0);
    SerialNumber:=StrToIntDef(AnsiMidStr(result_ix,30,8),0);
    HeaderFirmwareVersion:=
      IntToStr(ProtocolNumber)+'-'+
      IntToStr(ModelNumber)+'-'+
      IntToStr(FeatureNumber);

    LogFileName:=Format('%s%s_%s'+Ext,[RemoveMultiSlash(appsettings.LogsDirectory + DirectorySeparator),FormatDateTime('yyyymmdd"_"hhnnss',Now()),DLHeaderForm.InstrumentIDEntry.Text]);
    CSVLogFileName:=Format('%s%s_%s.csv',[RemoveMultiSlash(appsettings.LogsDirectory + DirectorySeparator),FormatDateTime('yyyymmdd"_"hhnnss',Now()),DLHeaderForm.InstrumentIDEntry.Text]);
    if TransferCSV then begin
      AssignFile(DLRecFile,CSVLogFileName);
      Rewrite(DLRecFile); //Open file for writing
      WriteLn(DLRecFile,''); //Write to empty file as placeholder
      Flush(DLRecFile);
      CloseFile(DLRecFile);
    end;

    AssignFile(DLRecFile,LogFileName);
    Rewrite(DLRecFile); //Open file for writing

    { Write header }
    SetTextLineEnding(DLRecFile,#13#10);
    HeaderLines.Add('# Light Pollution Monitoring Data Format 1.0');
    HeaderLines.Add('# URL: http://www.darksky.org/measurements');

    //Determine number of header lines based on style of logging
    //if SelectedModel=model_V then
    //  HeaderLines.Add('# Number of header lines: 46')
    //else
    //  HeaderLines.Add('# Number of header lines: 36');

    HeaderLines.Add('# This data is released under the following license: ODbL 1.0 http://opendatacommons.org/licenses/odbl/summary/');
    HeaderLines.Add('# Device type: '+SelectedModelDescription);
    HeaderLines.Add('# Instrument ID: '+DLHeaderForm.InstrumentIDEntry.Text);
    HeaderLines.Add('# Data supplier: '+DLHeaderForm.DataSupplierEntry.Text);
    HeaderLines.Add('# Location name: '+DLHeaderForm.LocationNameEntry.Text);
    HeaderLines.Add('# Position (lat, lon, elev(m)): '+DLHeaderForm.PositionEntry.Text);
    HeaderLines.Add('# Local timezone: '+DLHeaderForm.TZLocationBox.Text);
    HeaderLines.Add('# Time Synchronization: '+DLHeaderForm.TimeSynchEntry.Text);

    { GPS moving platform }
    if ExternalGPSHeader then
      HeaderLines.Add('# Moving / Stationary position: MOVING')
    else
      HeaderLines.Add('# Moving / Stationary position: '+DLHeaderForm.MovingStationaryPositionCombo.Text);

    HeaderLines.Add('# Moving / Fixed look direction: '+DLHeaderForm.MovingStationaryDirectionCombo.Text);
    HeaderLines.Add('# Number of channels: '+DLHeaderForm.NumberOfChannelsEntry.Text);
    HeaderLines.Add('# Filters per channel: '+DLHeaderForm.FiltersPerChannelEntry.Text);
    HeaderLines.Add('# Measurement direction per channel: '+DLHeaderForm.MeasurementDirectionPerChannelEntry.Text);
    HeaderLines.Add('# Field of view (degrees): '+DLHeaderForm.FieldOfViewEntry.Text);

    {Determing number of field that are recorded}
    if Style = 'DL-Log' then {Short record}
      NumberOfFields:=5
    else if Style = 'DL-V-Log' then //vector model
      NumberOfFields:=14
    else if Style = 'DL-V-HSLog' then //vector model Hard/soft calibration log
      NumberOfFields:=3
    else if Style = 'ADA' then //aurora
      NumberOfFields:=7
    else if Style = 'GDM' then //geomagnetic disturbance meter
      NumberOfFields:=4
    else if Style = 'C' then //color model
      NumberOfFields:=8
    else {Long record}
       NumberOfFields:=6;

    if GoToEnabled then
      NumberOfFields:=NumberOfFields+2;

    if Freshness then
      NumberOfFields:=NumberOfFields+2;

    if ExternalGPSHeader then
      NumberOfFields:=NumberOfFields+5;

    if NumberOfMultipleDevices>0 then
      NumberOfFields:=NumberOfFields+1;

    HeaderLines.Add('# Number of fields per line: ' + IntToStr(NumberOfFields));


    HeaderLines.Add(Format('# SQM serial number: %d',[SerialNumber]));
    HeaderLines.Add('# SQM hardware identity: ' + SelectedHardwareID);
    HeaderLines.Add('# SQM firmware version: '+HeaderFirmwareVersion);
    HeaderLines.Add('# SQM cover offset value: '+DLHeaderForm.CoverOffsetEntry.Text);
    HeaderLines.Add('# SQM readout test ix (Information): '+result_ix);
    HeaderLines.Add('# SQM readout test rx (Reading): '+sendget('rx'));

    if not (Style = 'GDM') then
      HeaderLines.Add('# SQM readout test cx (Calibration): '+sendget('cx'));

    HeaderLines.Add('# SQM readout test Ix (Report Interval): '+sendget('Ix'));

    {Log the time difference, and logging threshold}
    case SelectedModel of
        model_DL, model_V, model_GPS, model_DLS: begin
        result:=sendget('Lcx'); { Read the RTC }
        ThisMomentUTC:=LazSysUtils.NowUTC();
         if Length(result)>=21 then begin
             UnitClock:=FixDate(AnsiMidStr(Trim(result),4,19));
             try
                UnitTime:=ScanDateTime('yy-mm-dd hh:nn:ss',LeftStr(UnitClock,9)+RightStr(UnitClock,8));
             except
                   StatusMessage('Invalid RTC from device = '+UnitClock);
                   UnitTime:=ThisMomentUTC;
             end;
             ClockDiffSeconds:=SecondsBetween(ThisMomentUTC,UnitTime);
             if ThisMomentUTC>UnitTime then ClockDiffSeconds:=ClockDiffSeconds*-1;
             HeaderLines.Add('# DL time difference (seconds): '+IntToStr(ClockDiffSeconds));
           end
         else
           HeaderLines.Add('# DL time difference: ???');

         HeaderLines.Add('# DL retrieved at (UTC): '+FormatDateTime('yyyy-mm-dd"T"hh:nn:ss.zzz',ThisMomentUTC));

         result:=sendget('LIx');  {Read the logging trigger and threshold setting}
         HeaderLines.Add('# DL trigger seconds : '+IntToStr(StrToIntDef(AnsiMidStr(result,4,10),0)));
         HeaderLines.Add('# DL trigger minutes : '+IntToStr(StrToIntDef(AnsiMidStr(result,16,10),0)));
         HeaderLines.Add('# DL trigger threshold : '+FloatToStr(StrToFloatDef(AnsiMidStr(result,52,11),0,FPointSeparator)));

      end;
    end;


    { TODO : vector cal info }
    //if vector model then write accellerometer and magnetic calibration values
    if SelectedModel=model_V then begin
      for AccCalPos:=1 to 6 do begin
        HeaderLines.Add(Format('# Acceleration position %d:  %6.0f  %6.0f  %6.0f',[AccCalPos,
                   w.getv(AccCalPos-1, 0),
                   w.getv(AccCalPos-1, 1),
                   w.getv(AccCalPos-1, 2)]));
        end;
        HeaderLines.Add(Format('# Magnetic maximum XYZ: %7.0f %7.0f %7.0f',[Mxmax,Mymax,Mzmax]));
        HeaderLines.Add(Format('# Magnetic minimum XYZ: %7.0f %7.0f %7.0f',[Mxmin,Mymin,Mzmin]));
    end;

    HeaderLines.Add('# Comment: '+DLHeaderForm.UserComment1.Text);
    HeaderLines.Add('# Comment: '+DLHeaderForm.UserComment2.Text);
    HeaderLines.Add('# Comment: '+DLHeaderForm.UserComment3.Text);
    HeaderLines.Add('# Comment: '+DLHeaderForm.UserComment4.Text);
    HeaderLines.Add('# Comment: '+DLHeaderForm.UserComment5.Text);

    // Log the UDM version.
    Info := TVersionInfo.Create;
    Info.Load(HINSTANCE);
    HeaderLines.Add(Format('# UDM version: %s',
                                [IntToStr(Info.FixedInfo.FileVersion[0])
                                +'.'+IntToStr(Info.FixedInfo.FileVersion[1])
                                +'.'+IntToStr(Info.FixedInfo.FileVersion[2])
                                +'.'+IntToStr(Info.FixedInfo.FileVersion[3])]));
    Info.Free;

    //Log the current UDM settings that were passed here.
    HeaderLines.Add(Format('# UDM setting: %s',[Setting]));

    HeaderLines.Add('# blank line');

    //Determing Field names and units
    if Style = 'DL-Log' then begin //Short record
      FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Voltage, MSAS';
      FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;Volts;mag/arcsec^2';
      if StrToInt(SelectedFeature)>=49 then begin
        FieldNames:=FieldNames+', Record type';
        FieldUnits:=FieldUnits+';Init/Subs';
      end;
    end
    else if Style = 'DL-V-Log' then begin
      if Setting ='One record logged' then begin
        FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Counts, Frequency, MSAS, Ax, Ay, Az, Mx, My, Mz, Altitude, Zenith, Azimuth, Vibration';
        FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2;Ax;Ay;Az;Mx;My;Mz;degrees;degrees;degrees;count';
      end
      else if ((Setting='DL Retrieve All') or (Setting='DL-V binary retrieve')) then begin
        FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Voltage, MSAS, Ax, Ay, Az, Mx, My, Mz, Altitude, Zenith, Azimuth, Vibration';
        FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;Volts;mag/arcsec^2;Ax;Ay;Az;Mx;My;Mz;degrees;degrees;degrees;count';
      end
      else begin
        FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Counts, Frequency, MSAS, Ax, Ay, Az, Mx, My, Mz, Altitude, Zenith, Azimuth, Vibration';
        FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2;Ax;Ay;Az;Mx;My;Mz;degrees;degrees;degrees;count';
      end;

      //Newer DL units have identification for initial or subsequent records.
      if StrToInt(SelectedFeature)>=49 then begin
        FieldNames:=FieldNames+', Record type';
        FieldUnits:=FieldUnits+';Init/Subs';
      end;
    end
    else if Style = 'DL-V-HSLog' then begin
      FieldNames:='# Counts, Counts, Counts';
      FieldUnits:='# Mx, My, Mz';
    end
    else if Style = 'ADA' then begin
      FieldNames:='# UTC Date & Time, Local Date & Time, Frequency, Counts1, Time1, Counts2, Time2, ADAFactor';
      FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Hz;Counts;Seconds;Counts;Seconds;Ratio';
    end
    else if Style = 'GDM' then begin
      FieldNames:='# UTC Date & Time, Local Date & Time, RawMag, Temperature, CompMag';
      FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Hz;Counts;Celcius;Counts';
    end
    else if Style = 'C' then begin
      FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Counts, Frequency, MSAS, Scale, Color, Cycling';
      FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2;scale;color;F/C';
    end
    else begin //Long record
        FieldNames:='# UTC Date & Time, Local Date & Time, Temperature, Counts, Frequency, MSAS';
        FieldUnits:='# YYYY-MM-DDTHH:mm:ss.fff;YYYY-MM-DDTHH:mm:ss.fff;Celsius;number;Hz;mag/arcsec^2';
    end;

    { Optional Raw frequency}
    if ((SelectedModel=model_DLS) and RawFrequencyEnabled) then begin
      case Style of
          'DL-Log':begin
          end;
          else begin
             FieldNames:=FieldNames+', Raw frequency';
             FieldUnits:=FieldUnits+';Hz';
          end;
      end;
    end;

    { Optional Snow factor header for datalogger}
    if ((SelectedModel=model_DLS) and SnowLoggingEnabled) then begin
      case Style of
          'DL-Log':begin
             FieldNames:=FieldNames+', Std lin., Snow MSAS, Snow lin.';
             FieldUnits:=FieldUnits+';n;mag/arcsec^2;n';
          end;
          else begin
             FieldNames:=FieldNames+', Snow/Dark LED status';
             FieldUnits:=FieldUnits+';S/D';
          end;
      end;
    end;

    { Optional GoTo position data }
    if GoToEnabled then begin
      FieldNames:=FieldNames+', Zenith, Azimuth';
      FieldUnits:=FieldUnits+';deg;deg';
    end;

    { Optional Moving platform data }
    if (Freshness and not(AnsiContainsStr(LowerCase(Setting),'retrieve'))) then begin
      FieldNames:=FieldNames+', MSASraw, Status';
      FieldUnits:=FieldUnits+';mag/arcsec^2;F/P/S';
    end;

    //Optional Moon data
    if FormLogCont.OptionsGroup.Checked[0] then begin
      FieldNames:=FieldNames+', MoonPhaseDeg, MoonElevDeg, MoonIllum, MoonAzimuth';
      FieldUnits:=FieldUnits+';Degrees;Degrees;Percent;Degrees';
    end;

    //Optional GPS data
    if ExternalGPSHeader then begin
      FieldNames:=FieldNames+', Latitude, Longitude, Elevation, Speed, Satellites';
      FieldUnits:=FieldUnits+';Degrees;Degrees;meters;meters/second;Number';
    end;

    //Optional Humidity data
    if A1Enabled then begin
      FieldNames:=FieldNames+', Humidity';
      FieldUnits:=FieldUnits+';Percent';
    end;

    {Optoinal Serial number}
    if NumberOfMultipleDevices>0 then begin
      FieldNames:=FieldNames+', SerialNumber';
      FieldUnits:=FieldUnits+';S/N';
    end;

    HeaderLines.Add(FieldNames);
    HeaderLines.Add(FieldUnits);

    if Ext='.dat' then
       HeaderLines.Add('# END OF HEADER');


    {Count header lines and insert number in second line.}
    HeaderLines.Insert(2,'# Number of header lines: '+ IntToStr(HeaderLines.Count+1));

    { Write header to file }
    for s in HeaderLines do begin
         Writeln(DLRecFile, s);
    end;

    HeaderLines.Destroy;
    Flush(DLRecFile);
    CloseFile(DLRecFile);
end;

// Send a command strings then return the result
function SendGet(command:string; LeaveOpen:boolean = False; Timeout:Integer=3000; GetAlso:boolean = True; HideStatus:boolean = False) : string;
//LeaveOpen indicates that the communication port should be left open
var
   //ErrorNumber: Integer;
   ErrorString: AnsiString;
begin

     {Start up Comm busy timer}
     CommBusyTime:=0; //Reset count
     unit1.Form1.CommBusy.Enabled:=True;

     //Initialze output string to nothing.
     SendGet:='';

     ErrorString:='';

     {Request to open communications, even if already opened. }
     OpenComm();

     { Check selected communication method. }
     case SelectedInterface of

         'USB','RS232': begin
              ser.Purge;//debug (does not seem to work with some Macs )

              while ser.CanRead(10) do //Try another purge method
                 ser.Recvbyte(10);//was recvstring

              ser.SendString(command);
              if (GetAlso) then
                 SendGet:=ser.Recvstring(Timeout);
              If CompareStr(ser.LastErrorDesc,'OK')<>0 then
                 ErrorString:='Error: '+ser.LastErrorDesc;
         end;

         'Eth','WiFi': begin
              EthSocket.ResetLastError;
              EthSocket.Purge;
              EthSocket.SendString(command);

              if (GetAlso) then
                 SendGet:=EthSocket.RecvString(Timeout);

              {If not connected, then retry the connection. }
              If ((EthSocket.LastError=104) or (EthSocket.LastError=10054))then begin
                OpenComm();
                EthSocket.ResetLastError;
                EthSocket.SendString(command);
                if (GetAlso) then
                   SendGet:=EthSocket.RecvString(Timeout);
              end;
              If (EthSocket.LastError<>0) then begin
                 ErrorString:= ' ['+IntToStr(EthSocket.LastError)+']'+ EthSocket.LastErrorDesc;
              end;
         end;

     end;

     if not HideStatus then     //Comment this out to allow all messages through to logging
        begin
          if GetAlso then
            StatusMessage('Sent: '+command+'   To: '+PortName+'   Received: '+SendGet + ErrorString)
          else
            StatusMessage('Sent: '+command+'   To: '+PortName);
        end;

     {Reset comm. counter in case incoming response took a while. }
     CommBusyTime:=0; //Reset count

end;

procedure DisplayedReading(Darkness:Double);
begin
  //Update displayed readings
  Form1.DisplayedReading.Caption :=Darkness2MPSASString(Darkness);
  Form1.DisplayedNELM.Caption    :=Darkness2NELMString(Darkness)+' NELM';
  Form1.Displayedcdm2.Caption    :=Darkness2CDM2String(Darkness)+' cd/m²';
  Form1.DisplayedNSU.Caption     :=Darkness2NSUString(Darkness) +' NSU';

end;

function GetReading(): String;
const
   {$WRITEABLECONST ON}
   IsInside:Boolean=False;
   {$WRITEABLECONST OFF}
var
   //result:string;
   pieces: TStringList;
   compose:string;
   NoResultString:String = 'No Response:'+sLineBreak+
     ' - Check Report Interval.'+sLineBreak+
     ' - Check Accessories.';
   Reading:Double; //Averaged reading.
   ReadingUA:Double; //Unaveraged reading.
   ExpectedPieces:Integer;
   Statustext:String;
   command:String; //Command to be sent
   ReadingUAField:Integer = -1;
   FreshnessField:Integer = -1;
   SnowLEDField:Integer = -1;
begin

     if IsInSide then begin
       StatusMessage('Is inside GetReading already.');
       Exit;
     end;
     IsInside:=True;
     try

     if not gettingreading then begin
       ExpectedPieces:=6;
       gettingreading:=True;
       //StatusMessage('GetReading called.');//debug

       //Clear out existing results
       Form1.ReadingListBox.Items.Clear;

  //Try to ensure a model version has been found.
  if SelectedModel=0 then
     GetVersion;

  //Get response to "Request"
  pieces := TStringList.Create;
  pieces.StrictDelimiter := true; //Do not parse spaces

  pieces.Delimiter := ',';
  if rotstage then
    command:='ux' //Rotational stage requires unaveraged values.
  else
    if ((Freshness) and (StrToInt(SelectedFeature)>=58)) then begin
      command:='r1x'; //Get averaged, unaveraged, Stale flag.
      Inc(ExpectedPieces,2); //Account for Freshness reading
      ReadingUAField:=ExpectedPieces-2;
      FreshnessField:=ExpectedPieces-1;
    end
  else begin
       if RawFrequencyEnabled then begin ;
           command:='rFx'; //get raw frequency readings also.
       end
       else begin
           command:='rx'; //Normally the averaged values are desired.
       end;
  end;

  //Are we expecting Snow LED status
  if ((SelectedModel=model_DLS) and SnowLoggingEnabled) then begin
    Inc(ExpectedPieces);
    SnowLEDField:=ExpectedPieces-1;
  end;

  result:=SendGet(command); //Normally the averaged values are desired.

  pieces.DelimitedText := result;

  case SelectedModel of
    model_LELU,model_LR,model_DL,model_GPS, model_DLS: begin
       if (pieces.count>=ExpectedPieces) then begin
           Reading:=StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,6),0,FPointSeparator);
           DisplayedReading(Reading);
           Form1.ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas',[Reading]));
           Form1.ReadingListBox.Items.Add(Format('Frequency: %dHz',      [StrToIntDef  (AnsiMidStr(pieces.Strings[2],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('  Counter: %dcounts',  [StrToIntDef  (AnsiMidStr(pieces.Strings[3],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('     Time: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0,FPointSeparator)]));
           Form1.ReadingListBox.Items.Add(Format('     Tint: %1.1fC',    [StrToFloatDef(AnsiMidStr(pieces.Strings[5],0,6),0,FPointSeparator)]));

           //Freshness
           if command='r1x' then begin
               ReadingUA:=StrToFloatDef(AnsiMidStr(pieces.Strings[ReadingUAField],1,6),0,FPointSeparator);
               Form1.ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas unaveraged',[ReadingUA]));
               case pieces.Strings[FreshnessField] of
                 'F': Statustext:='Fresh frequency';
                 'P': Statustext:='Fresh period';
                 'S': Statustext:='Stale reading';
                 else Statustext:='????';
               end;
               Form1.ReadingListBox.Items.Add(Format('   Status: %s',[Statustext]));
           end;

           //Indicate if Snow LED is on/off
           if ((SelectedModel=model_DLS) and SnowLoggingEnabled) then begin
             case pieces.Strings[SnowLEDField] of
               'S': Statustext:='On';
               'D': Statustext:='Off';
               else
                 Statustext:='???';
             end;
             Form1.ReadingListBox.Items.Add(Format(' Snow LED: %s',[Statustext]));
          end;

       end
       else begin
           Form1.ReadingListBox.Items.Add(NoResultString);
           StatusMessage('GetReading failed. Sent: '+command+' Received: '+result);
           end;
    end;

    model_V: begin
       if ((pieces.count=6) or (pieces.count=8)) then begin
           Reading:=StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,6),0,FPointSeparator);
           DisplayedReading(Reading);
           Form1.ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas', [Reading]));
           Form1.ReadingListBox.Items.Add(Format('Frequency: %dHz',       [StrToIntDef  (AnsiMidStr(pieces.Strings[2],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('  Counter: %dcounts',   [StrToIntDef  (AnsiMidStr(pieces.Strings[3],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('     Time: %1.3fs',     [StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0,FPointSeparator)]));
           Form1.ReadingListBox.Items.Add(Format('     Tint: %1.1fC',     [StrToFloatDef(AnsiMidStr(pieces.Strings[5],0,6),0,FPointSeparator)]));
           GetAccel();
           //Ax:=-1.0 * StrToFloatDef(pieces.Strings[6],0);
           //Ay:=StrToFloatDef(pieces.Strings[7],0);
           //Az:=StrToFloatDef(pieces.Strings[8],0);
           //Form1.ReadingListBox.Items.Add(Format('    Accel: %6.0fx %6.0fy %6.0fz',[Ax,Ay,Az]));
           //Mx:=StrToFloatDef(pieces.Strings[ 9],0);
           //My:=StrToFloatDef(pieces.Strings[10],0);
           //Mz:=StrToFloatDef(pieces.Strings[11],0);
           GetMag(False);
           //Form1.ReadingListBox.Items.Add(Format('      Mag: %6.0fx %6.0fy %6.0fz',[Mx,My,Mz]));

           NormalizeAccel(); //Compute acceleration values (In the future, this may be done inside the PIC)
           //Form1.ReadingListBox.Items.Add(Format(' Altitude: %4.0f°',[radtodeg(arcsin(-1.0*Ax1))]));
           Form1.ReadingListBox.Items.Add(Format(' Altitude: %4.1f°',[ComputeAltitude(Ax1, Ay1, Az1)]));

           ComputeAzimuth();
           Heading:=radtodeg(arctan2(-1*Mz2,Mx2))+180;
           Form1.ReadingListBox.Items.Add(Format('  Azimuth: %4.0f°',[Heading]));
         end
       else
           Form1.ReadingListBox.Items.Add(NoResultString);
    end;

    model_GDM: begin         //Magnetometer
       if pieces.count=3 then begin
         Form1.ReadingListBox.Items.Add(Format('M1: %dc',    [StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]));
         if StrToIntDef(pieces.Strings[2],0) < 32768 then
            Form1.ReadingListBox.Items.Add(Format('T1: %10.7fC',[StrToFloatDef(pieces.Strings[2],0,FPointSeparator)/128.0]))
         else
             Form1.ReadingListBox.Items.Add(Format('T1: %10.7fC',[(StrToFloatDef(pieces.Strings[2],0,FPointSeparator)-65536.0)/128.0]));

       end
       else
         Form1.ReadingListBox.Items.Add(NoResultString);
    end;
    model_TC: begin         //Temperature chamber
       if pieces.count=3 then begin
         Form1.ReadingListBox.Items.Add(Format('M1: %dc',    [StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]));
         if StrToIntDef(pieces.Strings[2],0) < 32768 then
            Form1.ReadingListBox.Items.Add(Format('T1: %10.7fC',[StrToFloatDef(pieces.Strings[2],0,FPointSeparator)/128.0]))
         else
             Form1.ReadingListBox.Items.Add(Format('T1: %10.7fC',[(StrToFloatDef(pieces.Strings[2],0,FPointSeparator)-65536.0)/128.0]));

       end
       else
         Form1.ReadingListBox.Items.Add(NoResultString);
    end;
    model_ADA: begin        //ADA
       if pieces.count=8 then begin
           Form1.ReadingListBox.Items.Add(Format('Frequency: %dHz',      [StrToIntDef(AnsiMidStr(pieces.Strings[1],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format(' Counter1: %dcounts',  [StrToIntDef(AnsiMidStr(pieces.Strings[2],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('    Time1: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[3],1,11),0,FPointSeparator)]));
           Form1.ReadingListBox.Items.Add(Format(' Counter2: %dcounts',  [StrToIntDef(AnsiMidStr(pieces.Strings[4],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('    Time2: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[5],1,11),0,FPointSeparator)]));
         end
       else
           Form1.ReadingListBox.Items.Add(NoResultString);
    end;

    model_C: begin         //Colour
       if ((pieces.count=9) or (pieces.count=11)) then begin
           Form1.ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas',[StrToFloatDef(AnsiMidStr(pieces.Strings[1],1,6),0,FPointSeparator)]));
           Form1.ReadingListBox.Items.Add(Format('Frequency: %dHz',      [StrToIntDef  (AnsiMidStr(pieces.Strings[2],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('  Counter: %dcounts',  [StrToIntDef  (AnsiMidStr(pieces.Strings[3],1,10),0)]));
           Form1.ReadingListBox.Items.Add(Format('     Time: %1.3fs',    [StrToFloatDef(AnsiMidStr(pieces.Strings[4],1,11),0,FPointSeparator)]));
           Form1.ReadingListBox.Items.Add(Format('     Tint: %1.1fC',    [StrToFloatDef(AnsiMidStr(pieces.Strings[5],0,6),0,FPointSeparator)]));

           //Report on colour settings
           Unit1.ColourUpdating:=True; //Prevent colour radios from self triggering.
             Unit1.SelectedColourScaling:=StrToInt(pieces.Strings[6]);
             Unit1.Form1.ColourScalingRadio.ItemIndex:=Unit1.SelectedColourScaling;
             case Unit1.SelectedColourScaling of
               0: compose:='Power down';
               1: compose:='2% Frequency scaling';
               2: compose:='20% Frequency scaling';
               3: compose:='100% Frequency scaling';
               else compose:='Error :'+ pieces.Strings[6];
             end;
             Form1.ReadingListBox.Items.Add(Format('  Scaling: %s',        [compose]));

             Unit1.SelectedColour:=StrToInt(pieces.Strings[7]);{*** put in variable}
             Unit1.Form1.ColourRadio.ItemIndex:=Unit1.SelectedColour;
             case Unit1.SelectedColour of
               0: compose:='Red';
               1: compose:='Blue';
               2: compose:='Clear';
               3: compose:='Green';
               else compose:='Error :'+ pieces.Strings[7];
             end;
             Form1.ReadingListBox.Items.Add(Format('   Colour: %s',        [compose]));

             {Get colour cycling flag, assume Fixed}
             case pieces.Strings[8] of
                'C': begin
                       ColourCyclingFlag:=True;
                       Unit1.Form1.ColourCyclingRadio.ItemIndex:=1;
                       Unit1.Form1.ColourRadio.Visible:=False;
                     end;
               else begin //Assume F (Fixed colour)
                       ColourCyclingFlag:=False;
                       Unit1.Form1.ColourCyclingRadio.ItemIndex:=0;
                       Unit1.Form1.ColourRadio.Visible:=True;
                     end;
               end;


             { Report freshness }
             if pieces.count=11 then begin
               ReadingUA:=StrToFloatDef(AnsiMidStr(pieces.Strings[9],1,6),0,FPointSeparator);
               Form1.ReadingListBox.Items.Add(Format('  Reading: %1.2fmpsas unaveraged',[ReadingUA]));
               case pieces.Strings[10] of
                 'F': Statustext:='Fresh frequency';
                 'P': Statustext:='Fresh period';
                 'S': Statustext:='Stale reading';
               end;
               Form1.ReadingListBox.Items.Add(Format('   Status: %s',[Statustext]));
            end;

           Unit1.ColourUpdating:=False; //Allow colour radios to be triggered.

         end

       else
           Form1.ReadingListBox.Items.Add('Expected 8 fields, got '+IntToStr(pieces.Count)+'.');

    end;
  otherwise
     Form1.ReadingListBox.Items.Add('Could not get version.');
  end;

  gettingreading:=False;
  end; //end of checking if getreading was called
 if Assigned(pieces) then FreeAndNil(pieces);

     finally
         IsInside:=False;
     end;

end;

procedure GetVersion;
const
  {$WRITEABLECONST ON}
      IsInside:Boolean=False;
  {$WRITEABLECONST OFF}
  {$IFDEF Linux}
      READ_BYTES = 2048;
  {$ENDIF}
var
   ixresult:string; {Information settings}
   Intvresult:string; {Interval settings}
   pieces,pieces2: TStringList;
   ResultCount:Integer;
   OurProcess: TProcess;
   MemStream: TMemoryStream;
   OutputLines: TStringList;
   NumBytes: LongInt;
   BytesRead: LongInt;
   RTCType:Integer=0;
   SnowResult:String;
begin
     if IsInSide then begin
       //StatusMessage('Is inside GetVersion already.');
       Exit;
     end;
     IsInside:=True;
     try

       StatusMessage('GetVersion called.');

       //Clear out existing results
       Form1.VersionListBox.Items.Clear;

       ResultCount:=0;

       pieces := TStringList.Create;
       pieces.Delimiter := ',';
       pieces2 := TStringList.Create;
       pieces2.Delimiter := ',';

       ixresult:=SendGet('ix');
       pieces.DelimitedText := ixresult;

       //Check size of array. 5 Sections normally.
       //There is a case where other software might be accessing the device,
       //  another immediate request should get the proper data, and a warning
       //  that possibly other software is accessing the device.
       if ((pieces.Count>=5) and (pieces.Strings[0]='i')) then begin
              ResultCount:=1;
         end
       else begin //Try once again. Sometimes dual responses get through here.
           ixresult:=SendGet('ix');
           pieces.DelimitedText := ixresult;
            if ((pieces.Count>=5) and (pieces.Strings[0]='i')) then
                 ResultCount:=2
            else begin
               { Check failed dialout usb connection; groups, suggest adduser then relogin. or administration->users and groups->manage groups->dialout->properties}
                 {$ifdef Linux}

                   MemStream := TMemoryStream.Create;
                   BytesRead := 0;

                   pieces := TStringList.Create;
                   pieces.Delimiter := '=';
                   OurProcess := TProcess.Create(nil);
                   OurProcess.Executable := 'groups';
                   OurProcess.Options := [poUsePipes];
                   OurProcess.Execute;
                   while OurProcess.Running do begin
                     MemStream.SetSize(BytesRead + READ_BYTES);
                     NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
                     if NumBytes > 0
                     then begin
                       Inc(BytesRead, NumBytes);
                     end
                     else begin
                       Sleep(100);
                     end;
                   end;
                   repeat
                     MemStream.SetSize(BytesRead + READ_BYTES);
                     NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
                     if NumBytes > 0 then begin
                       Inc(BytesRead, NumBytes);
                     end;
                   until NumBytes <= 0;
                     if BytesRead > 0 then
                        WriteLn; //blank line written for linux only.
                     MemStream.SetSize(BytesRead);

                     OutputLines := TStringList.Create;
                     OutputLines.LoadFromStream(MemStream);

                     if not AnsiContainsStr(OutputLines[0],'dialout') then begin
                         Form1.VersionListBox.Items.Add('This user is not part of the');
                         Form1.VersionListBox.Items.Add('  dialout group.');
                         Form1.VersionListBox.Items.Add('Use the "adduser", or');
                         Form1.VersionListBox.Items.Add('"sudo usermod -aG uucp username", or:');
                         Form1.VersionListBox.Items.Add('  administration->');
                         Form1.VersionListBox.Items.Add('    users and groups->');
                         Form1.VersionListBox.Items.Add('      manage groups->');
                         Form1.VersionListBox.Items.Add('        dialout->');
                         Form1.VersionListBox.Items.Add('          properties');
                         Form1.VersionListBox.Items.Add('add user name, then re-login');
                         StatusMessage('User not in dialout group.');
                       end;

                     OutputLines.Free;
                     OurProcess.Free;
                     MemStream.Free;
                 {$else}
                   Form1.VersionListBox.Items.Add('Other software may be ');
                   Form1.VersionListBox.Items.Add('accessing the device!');
                   StatusMessage('Other software may be accessing the device!');
                 {$endif}
               end;
           end;

       if ResultCount>0 then begin
           SelectedProtocol:=IntToStr(StrToIntDef(pieces.Strings[1],0));
           Form1.VersionListBox.Items.Add('Protocol: '+ SelectedProtocol);
           SelectedModel:=StrToIntDef(pieces.Strings[2],0);
           Case SelectedModel of
             model_ADA : SelectedModelDescription:='ADA';
             model_LELU: begin
               if Form1.CommNotebook.PageIndex=0 then
                 SelectedModelDescription:='SQM-LU'
               else
                 SelectedModelDescription:='SQM-LE';
               end;
             model_C : SelectedModelDescription:='SQM-C';
             model_LR: SelectedModelDescription:='SQM-LR';
             model_DL: begin
               if Form1.CommNotebook.PageIndex=0 then
                 SelectedModelDescription:='SQM-LU-DL'
               else
                 SelectedModelDescription:='SQM-W';
               end;
             model_GPS: SelectedModelDescription:='SQM-LU-GPS';
             model_GDM: SelectedModelDescription:='Magnetometer';
             model_TC : SelectedModelDescription:='Temp. Chamber';
             model_V  : SelectedModelDescription:='SQM-LU-DL-V';
             model_DLS: SelectedModelDescription:='SQM-LU-DLS';

           otherwise
             SelectedModelDescription:='Unknown';
           end;

           //Check if Selected model has an RTC
           case SelectedModel of
               model_DL, model_GPS, model_V, model_DLS:
             SelectedHasRTC:=True
           else
             SelectedHasRTC:=False;
           end;

           SelectedFeature:=IntToStr(StrToIntDef(pieces.Strings[3],0));
           SelectedUnitSerialNumber:=IntToStr(StrToIntDef(pieces.Strings[4],0));

           //Check for Lens model types;
           // L = default (as entered above)
           // 2 = 3D holder, Half-Ball lens, Interfernece filter
           // '' = no lens (remove the L from the description
           if StrToIntDef(SelectedFeature,0)>=35 then begin //Enable lens model selections
             LHFCheck('');
             //Results sent as m_x, received as m_,123
             //SelectedLH:=StrToInt(AnsiMidStr(SendGet('m0x'),4,3));
             //SelectedLens:=StrToInt(AnsiMidStr(SendGet('m1x'),4,3));
             //SelectedFilter:=StrToInt(AnsiMidStr(SendGet('m2x'),4,3));
             //replace L in 5th place with L2
             if ((SelectedLH=2) and (SelectedLens=2) and (SelectedFilter=2)) then
               SelectedModelDescription:=StuffString(SelectedModelDescription,5,1,'L2');
             //replace L with blank (no lens or holder, only Hoya filter)
             if ((SelectedLH=0) and (SelectedLens=0) and (SelectedFilter=1)) then
               SelectedModelDescription:=AnsiLeftStr(SelectedModelDescription,4) + AnsiRightStr(SelectedModelDescription,Length(SelectedModelDescription)-5);
             //replace L with blank (no lens, no holder, no filter), and add suffix -NF
             if ((SelectedLH=0) and (SelectedLens=0) and (SelectedFilter=0)) then begin
               SelectedModelDescription:=AnsiLeftStr(SelectedModelDescription,4) + AnsiRightStr(SelectedModelDescription,Length(SelectedModelDescription)-5);
               SelectedModelDescription:=SelectedModelDescription+'-NF';
               end;
           end;

           { Check for RTC type }
           if SelectedHasRTC then begin
              if StrToIntDef(SelectedFeature,0)>=38 then begin;
                pieces2.DelimitedText:=SendGet('Lvx');
                if pieces2.Count>0 then begin
                  SelectedRTC:=pieces2.Strings[1];
                  case StrToIntDef(SelectedRTC,0) of
                      0: begin
                         RTCType:=0;
                      end;
                      1: begin
                         RTCType:=1;
                         SelectedModelDescription:=SelectedModelDescription+'-R1';
                      end;
                      2: begin
                         RTCType:=2;
                         SelectedModelDescription:=SelectedModelDescription+'-R2';
                      end;
                  otherwise
                      RTCType:=-1;
                  end;
                end;
              end;
           end;

           //The continuous functions are available on feature version is 40 and higher
           if StrToIntDef(SelectedFeature,0)>=40 then begin
              Form1.ContCheckGroup.Visible:=True;//Show group of options
              Form1.ContCheck('Yx');//Show continuous selections
              Form1.DataNoteBook.Page[10].TabVisible:=True; //Show Accessory tab
           end else begin
              Form1.ContCheckGroup.Visible:=False;
              if Form1.DataNoteBook.ActivePageIndex=10 then
                Form1.DataNoteBook.ActivePageIndex:=0;//must not be on active page othewise UDM crashes.
              Form1.DataNoteBook.Page[10].TabVisible:=False; //Hide Accessory tab.
           end;

           //Snow settings
           if StrToIntDef(SelectedFeature,0)>=62 then begin
             SnowResult:=SendGet('A5x');
             Unit1.Form1.SnowLEDStatus(SnowResult);
           end;

           Form1.VersionListBox.Items.Add('   Model: '+ IntToStr(SelectedModel)+ ' ('+ SelectedModelDescription + ')');

           Form1.VersionListBox.Items.Add(' Feature: '+ SelectedFeature);

           DLHeaderForm.SerialNumber.Text:=SelectedUnitSerialNumber;

           { Read initialization file. }
           DLHeaderForm.ReadINI;

           Form1.VersionListBox.Items.Add('  Serial: '+ SelectedUnitSerialNumber);

           // List RTC type in version box if new RTC
           if SelectedHasRTC then begin
             case RTCType of
                0: Form1.VersionListBox.Items.Add('     RTC: '+'DS1305');
                1: Form1.VersionListBox.Items.Add('     RTC: '+'DS3234');
                2: Form1.VersionListBox.Items.Add('     RTC: '+'DS1390');
             otherwise
                Form1.VersionListBox.Items.Add('     RTC: '+'Unknown');
             end;
           end;


           //Vector tab: only for SQM-LU-DL-V
           if (SelectedModel=model_V) then
             begin
                 Form1.DataNoteBook.Page[9].TabVisible:=True;
             end;

         { Colour model }
         if SelectedModel=model_C then begin
           Unit1.ColourUpdating:=True;
           Unit1.Form1.ColourControls.Visible:=True;
           SelectedColourScaling:=-1;
           Unit1.Form1.ColourScalingRadio.ItemIndex:=SelectedColourScaling;
           SelectedColour:=-1;
           Unit1.Form1.ColourRadio.ItemIndex:=SelectedColour;
           Unit1.ColourUpdating:=False;
         end
         else
           Unit1.Form1.ColourControls.Visible:=False;

         //Datalogging tab: for: SQM-LU-DL, SQM-LU-GPS, SQM-LU-DL-V
         case SelectedModel of
           model_DL, model_GPS, model_V, model_DLS: begin
              form1.DLGetSettings();
              Form1.DataNoteBook.Page[4].TabVisible:=True;
              DataLoggingAvailable:=True;
             end;
           else begin
                  Form1.DataNoteBook.Page[4].TabVisible:=False;
                  DataLoggingAvailable:=False;
                end;
           end;


         //GPS tab: only for SQM-LU-GPS
         if (SelectedModel=model_GPS) then
              Form1.DataNoteBook.Page[6].TabVisible:=True
         else
              Form1.DataNoteBook.Page[6].TabVisible:=False;

         //StatusMessage('Checking lock visibility');//debug
         CheckLockVisibility();
         //StatusMessage('Checked lock visibility');//debug
       end;

       //Interval settings
       //StatusMessage('Checking Ix');//debug
       Intvresult:=SendGet('Ix');
       //StatusMessage('Checked Ix');//debug
       Form1.ParseReportInterval(Intvresult);

       gettingversion:=False;

       Application.ProcessMessages;

   UpdateCalReport;

   // header button enable if serial number is not null
   //if DLHeaderForm.SerialNumber.Text<>'' then
   //  Form1.HeaderButton.Enabled:=True;

   if Assigned(pieces) then FreeAndNil(pieces);
   if Assigned(pieces2) then FreeAndNil(pieces2);

     finally
         IsInside:=False;
     end;

     if StrToIntDef(SelectedUnitSerialNumber,0)>0 then begin
       StatusMessage('GetVersion result: ' + ixresult);
       StatusMessage('Reading Interval Settings: ' + Intvresult);
       end
     else begin
       StatusMessage('GetVersion: No serial number');
       end;
end;

{Write new genertic or appended serial-number-specific logfile ex. SN1234.log}
procedure StatusMessage(Mstring:string);
begin
   Form1.StatusBar1.Panels.Items[0].Text:=Mstring;
   if not(Mstring='') then begin
        if StrToIntDef(SelectedUnitSerialNumber,0)>0 then begin {A serial number has been selected}
          SNLogFileName:=RemoveMultiSlash(appsettings.LogsDirectory + DirectorySeparator)+'SN'+SelectedUnitSerialNumber+'.log';
          AssignFile(SNLogFile,SNLogFileName);
          if FileExists(SNLogFileName) then
            Append(SNLogFile)
          else
            Rewrite(SNLogFile);
          if IOResult <> 0 then
          begin
            writeln('Unable to open file: ',SNLogFileName);
            Exit;
          end;
          WriteLn(SNLogFile,FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',NowUTC()) + ' : SN ' + SelectedUnitSerialNumber + ' : ' + Mstring);
          Flush(SNLogFile);
          CloseFile(SNLogFile);
        end else begin {No serial number has been selected}
          { Write to udm.log logfile}
             Append(UDMLogFile); //File is opened for write, but NOT emptied. Any text written to it is appended.
             WriteLn(UDMLogFile,FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',NowUTC()) + ' : ' + Mstring); //Append message.
             Flush(UDMLogFile);
             Close(UDMLogFile); //Allow other instances to write to file.
        end;
        {Write to log viewer screen}
        ViewedLog.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',NowUTC()) + ' : ' + SelectedUnitSerialNumber + ' : ' + Mstring);
        if ViewingLog then begin
           Form5.SynEdit1.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',NowUTC()) + ' : ' + SelectedUnitSerialNumber + ' : ' + Mstring);
           {Automatically scroll up.}
           Form5.SynEdit1.TopLine:=Form5.SynEdit1.Lines.Count-Form5.SynEdit1.LinesInWindow+1;
           Form5.SynEdit1.Refresh;
        end;
      end;
   Application.ProcessMessages;
end;

procedure ClearLockVisibility;
begin
  Form1.CheckLockResult.Text:='';
  Form1.CheckLockButton.Visible:=False;
  Form1.CheckLockResult.Visible:=False;
  Form1.bXPortDefaults.Enabled:=False;
end;

procedure CheckLockVisibility;
const
   {$WRITEABLECONST ON}
   IsInside:Boolean=False;
   {$WRITEABLECONST OFF}
begin
   if IsInSide then begin
     StatusMessage('Is inside CheckLockVisibility already.');
     Exit;
   end;
   IsInside:=True;
   try

      StatusMessage('CheckLockVisibility()');

      //CheckLock enable/disable
      Form1.CheckLockResult.Text:='';
      if (((SelectedModel=model_LELU) or (SelectedModel=model_C)) and (Form1.CommNotebook.PageIndex=1)) then //SQM-LE/U or Colour, and Ethernet
         begin
           Form1.CheckLockButton.Visible:=True;
           Form1.CheckLockResult.Visible:=True;
         end
      else
          begin
           Form1.CheckLockButton.Visible:=False;
           Form1.CheckLockResult.Visible:=False;
          end;

      //Check XPort default button enable/disable
      if (Form1.CommNotebook.PageIndex=1) then //Ethernet
         begin
           Form1.bXPortDefaults.Enabled:=True;
         end
      else
          begin
           Form1.bXPortDefaults.Enabled:=False;
          end;

   finally
       IsInside:=False;
   end;

end;

procedure UpdateCalReport;
var
   AccCalPos: Integer; //Accelerometer position
begin
     Form1.ConfRecWarning.Caption:='';

     Application.ProcessMessages;
     if (Form1.FoundDevices.SelCount=0) and (Form1.CommNotebook.PageIndex<>2) then //not for RS232
       begin
            Form1.Panel1.Canvas.Clear;
       end
     else
       begin
         PrintingLine:=0;
         PrintLine(SelectedModelDescription+' Calibration data ');
         PrintLine(' Report Date ', format(' %s',[FormatDateTime('yyyy-mm-dd',Now())]));
         PrintLine(' Serial Number ', format(' %s',[SelectedUnitSerialNumber]));
         //check if USB device:
         if ((Form1.CommNotebook.PageIndex=0) and not (SelectedModel=model_LR)) then    // USB device but not RS232 model
            PrintLine(' USB Serial Number ', format(' %s',[Form1.USBSerialNumber.text]));
         //check if Ethernet device:
         if (Form1.CommNotebook.PageIndex=1) then
            PrintLine(' MAC ', format(' %s',[Form1.EthernetMAC.text]));
         PrintLine(' Model Number ', format(' %s',[Inttostr(SelectedModel) + ' ('+SelectedModelDescription+')']));
         PrintLine(' Feature version ', format(' %s',[SelectedFeature]));
         PrintLine(' Protocol version ', format(' %s',[SelectedProtocol]));
         if SelectedHasRTC then begin
           if StrToIntDef(SelectedFeature,0)>=38 then begin
             case StrToIntDef(SelectedRTC,0) of
                 0: PrintLine(' Real Time Clock ', ' DS1305 (±20ppm)');
                 1: PrintLine(' Real Time Clock ', ' DS3234 (±3.5ppm)');
                 2: PrintLine(' Real Time Clock ', ' DS1390 (±5ppm)');
                 otherwise
                   PrintLine(' Real Time Clock ', ' Unknown');
             end;
           end;
           PrintLine(' Data logging capacity ', Format(' %d ',[DLEStorageCapacity])+' records');
           //Warning if EEPROM is missing:
           if DLEStorageCapacity<1000 then begin
             Form1.ConfRecWarning.Caption:='Records';
             Form1.ConfRecWarning.Color:=clRed;
             Form1.ConfRecWarning.Font.Color:=clWhite;
           end
           else begin
              Form1.ConfRecWarning.Caption:='';
              Form1.ConfRecWarning.Color:=clNone;
              Form1.ConfRecWarning.Font.Color:=clNone;
           end;
         end;

         PrintLine(' Light calibration offset ', Format(' %2.2f ',[ConfCalmpsas])+' mags/arcsec²');
         PrintLine(' Light calibration temperature ', Format(' %2.1f ',[ConfCalLightTemp])+' °C');
         PrintLine(' Dark calibration period ', Format(' %2.3f ',[ConfCalPeriod])+' seconds');
         PrintLine(' Dark calibration temperature ', Format(' %2.1f ',[ConfCalDarkTemp])+' °C');
         PrintLine(' Calibration offset ', ' 8.71 mags/arcsec² ');
         if SelectedModel=model_V then begin
             for AccCalPos:=1 to 6 do begin
                PrintLine(format(' Acceleration position %d ',[AccCalPos]),
                   format('  %6.0f  %6.0f  %6.0f ',[
                        w.getv(AccCalPos-1, 0),
                        w.getv(AccCalPos-1, 1),
                        w.getv(AccCalPos-1, 2)]));
             end;
             PrintLine(' Magnetic maximum XYZ ',format(' %7.0f %7.0f %7.0f',[Mxmax,Mymax,Mzmax]));
             PrintLine(' Magnetic minimum XYZ ',format(' %7.0f %7.0f %7.0f',[Mxmin,Mymin,Mzmin]));
         end;

       end;
end;
procedure PrintLine(LabelText:String; DataText: String='');
var
  H,W,PageTop,PageWidth,CellMargin,CH: Integer;
  WhereTo:TCanvas;
begin
     if CalPrint then //output=printer
       begin
         WhereTo:=Printer.Canvas;
         PageTop:=500;
         CellMargin:=27;
         PageWidth:=Printer.PageWidth;
         H := WhereTo.TextHeight(LabelText);
         W := WhereTo.TextWidth(LabelText);
         CH:=H+2*CellMargin;
         //Draw the rectangle around the text:
         WhereTo.Rectangle(
            Rect(  PageWidth div 5,
                   PageTop + PrintingLine * CH,
                   4 * (PageWidth div 5),
                   PageTop + PrintingLine * CH + CH+1));
       end
     else //output = preview on screen
       begin
            WhereTo:=Form1.Panel1.Canvas;
            PageTop:=1;
            CellMargin:=5;
            PageWidth:=Form1.Panel1.Width;
            H := WhereTo.TextHeight(LabelText);
            W := WhereTo.TextWidth(LabelText);
            CH:=H+2*CellMargin;
            //Draw the rectangle around the text:
            WhereTo.Rectangle(
               Rect(  1,
                      PageTop + PrintingLine * CH,
                      PageWidth-1,
                      PageTop + PrintingLine * CH + CH+1));
       end;

     //Check for two parameters
     if Length(DataText)>0 then begin
       //Draw centerline
       WhereTo.Line(
                 PageWidth div 2,
                 PageTop + PrintingLine * CH,
                 PageWidth div 2,
                 PageTop + PrintingLine * CH + CH);
       //Place Left text:
       WhereTo.TextOut((
                 PageWidth div 2) - W - CellMargin,
                 PageTop + (PrintingLine * CH) + CellMargin,
                 LabelText);
       //Place Right text:
       WhereTo.TextOut((
                 PageWidth div 2) + CellMargin,
                 PageTop + (PrintingLine * CH) + CellMargin,
                 DataText);
       end
     else begin //one text paramater
       //Place the text:
       WhereTo.TextOut((
                 PageWidth div 2) - (W div 2),
                 PageTop + (PrintingLine * CH) + CellMargin,
                 LabelText);
     end;
     Inc(PrintingLine);
end;

function FixDate(incoming:AnsiString): AnsiString;
{ Fix the date from the DataLogging unit by converting the DOW value to
  a readable string. }
var
   dowval:Integer;
   weekday: Array[1..7] of string = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
begin
  dowval:=StrToInt(AnsiMidStr(incoming,10,1));
  if ((dowval>=1) and (dowval<=7)) then
    FixDate:=AnsiMidStr(incoming,1,9)+weekday[dowval]+AnsiMidStr(incoming,11,9)
  else
    FixDate:=AnsiMidStr(incoming,1,9)+'???'+AnsiMidStr(incoming,11,9);
end;
{Looks through command line pramaters for a command.
 Returns true if the comand is found.
 Parse the comma separated command into the global ParameterValue array.
 Startup options are also availble through the startup setting.
 Both, Paramater settings, and Startup options are considered here.
 }
function ParameterCommand(Command:String): Boolean;
var
   ParmeterPointer:Integer=1;
   StartupParmeterPointer:Integer=0;
   pieces: TStringList;
begin
  ParameterCommand:=False; { Initially assumes nothing was found.}

  { Read command line parameters. }
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  if (Paramcount > 0) then begin
      while ((ParmeterPointer>=1) and (ParmeterPointer<=Paramcount)) do begin
           pieces.DelimitedText:=ParamStr(ParmeterPointer);
           if pieces.Strings[0]=Command then begin
               ParameterValue.Assign(pieces);
               ParameterCommand:=True;
               ParmeterPointer:=0;
             end
           else
             inc(ParmeterPointer);
        end;
  end;
  pieces.Destroy;

  { Read Startup options. }
  pieces := TStringList.Create;
  pieces.Delimiter := ',';
  if (StartupParamcount > 0) then begin
      while ((StartupParmeterPointer>=0) and (StartupParmeterPointer<=(StartupParamcount-1))) do begin
           pieces.DelimitedText:=StartupParamStrings[StartupParmeterPointer];
           if pieces.Strings[0]=Command then begin
               ParameterValue.Assign(pieces);
               ParameterCommand:=True;
               StartupParmeterPointer:=-1;
             end
           else
             inc(StartupParmeterPointer);
        end;
  end;
  pieces.Destroy;


end;

end.