forked from rrobinett/wsprdaemon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
wsprdaemon.sh
executable file
·6247 lines (5711 loc) · 356 KB
/
wsprdaemon.sh
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
#!/bin/bash
### Wsprdaemon: A robust decoding and reporting system for WSPR
### Copyright (C) 2020 Robert S. Robinett
###
### 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 3 of the License, or
### (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with this program. If not, see <https://www.gnu.org/licenses/>.
[[ ${v:-yes} == "yes" ]] && echo "wsprdaemon.sh Copyright (C) 2020 Robert S. Robinett
This program comes with ABSOLUTELY NO WARRANTY; for details type './wsprdaemon.sh -h'
This is free software, and you are welcome to redistribute it under certain conditions. execute'./wsprdaemon.sh -h' for details.
wsprdaemon depends heavily upon the 'wsprd' program and other technologies developed by Joe Taylor K1JT and others, to whom we are grateful.
Goto https://physics.princeton.edu/pulsar/K1JT/wsjtx.html to learn more about WSJT-x
"
### This bash script logs WSPR spots from one or more Kiwi
### It differs from the autowspr mode built in to the Kiwi by:
### 1) Processing the uncompressed audio .wav file through the 'wsprd' utility program supplied as part of the WSJT-x distribution
### The latest 'wsprd' includes alogrithmic improvements over the version included in the Kiwi
### 2) Executing 'wsprd -d', a deep search mode which sometimes detects 10% or more signals in the .wav file
### 3) By executing on a more powerful CPU than the single core ARM in the Beaglebone, many more signals are extracted on busy WSPR bands,'
### e.g. 20M during daylight hours
###
### This script depends extensively upon the 'kiwirecorder.py' utility developed by John Seamons, the Kiwi author
### I owe him much thanks for his encouragement and support
### Feel free to email me with questions or problems at: [email protected]
### This script was originally developed on Mac OSX, but this version 0.1 has been tested only on the Raspberry Pi 3b+
### On the 3b+ I am easily running 6 similtaneous WSPR decode session and expect to be able to run 12 sessions covering a;; the
### LF/MF/HF WSPR bands on one Pi
###
### Rob Robinett AI6VN [email protected] July 1, 2018
###
### This software is provided for free but with no guarantees of its usefullness, functionality or reliability
### You are free to make and distribute copies and modifications as long as you include this disclaimer
### I welcome feedback about its performance and functionality
shopt -s -o nounset ### bash stops with error if undeclared variable is referenced
#declare -r VERSION=2.9e ### Fix exchanged values of ipass and nhdwrmin when posting to TS. This section of code runs only at wsprdaemon.org
#declare -r VERSION=2.9f ### Fix wsprnet upload client to support multiple CALL_GRID in conf file
### Fix CHU_14 frequency
### Tweek comments in prototype WD.conf file
### WD upload service (-u a/s/z) which runs on the wsprdaemon.org server has been enhanced to run 1000x faster, really! It now used batch mode to record 4000+ spots per second to TimeScale
### WD upload service better filters out corrupt spot lines.
#declare -r VERSION=2.9g ### Cleanup installation of WSJT-x which suppplies the 'wsprd' decoder
### Check for and install if needed 'ntp' and 'at'
### Cleanup systemctl setup so startup after boot functions on Ubuntu
### Wsprnet upload client daemon flushes files of completed cycles where no bands have spots
### Fix wrong start/stop args in wsprdeamon.service
### Stop checking the Pi OS version number, since we run on almost every Pi
### Add validation of spots in wspr_spots.txt and ALL_WSPR.TXT files
#declare -r VERSION=2.9h ### Install at beta sites
### WD server: Force call signs and rx_id to upppercase and grids to UUNNll
### Add support for VHF/UHF transverter ahead of rx device. Set KIWIRECORDER_FREQ_OFFSET to offset in KHz
#declare -r VERSION=2.9i ### Change to support per-Kiwi OFFSET defined in conf file
### Fix noise level calcs and graphs for transcoder-fed Kiwis
### Fix mirror deaemon to handle 50,000+ cached files
### Cleaup handling of WD spots and noise file mirroring to logs1...
### Fix bash arg overflow error when startup after long time finds 50,000+ tar files in the ftp/upload directory
#declare -r VERSION=2.9j ### WD server: fix recording of rx and tx GRID. Add recording of receiver name to each spot
#declare -r VERSION=2.10a ### Support Ubuntu 20.04 and streamline installation of wsprd by extracting only wsprd from the package file.
### Execute the astral python sunrise/sunset calculation script with python3
#declare -r VERSION=2.10b ### Fix installation problems on Ubuntu 20.04. Download and run 'wsprd' v2.3.0-rc0
#declare -r VERSION=2.10c ### Change default 'wsprd' to load 2.3.0-rc1
#declare -r VERSION=2.10d ### Check for and if missing install the libgfortran5 library used by wsprd V2.3.0xxx
#declare -r VERSION=2.10e ### Add GNU Public license
#declare -r VERSION=2.10f ### Add FSTW4-120 decoding on all bands if JT9_DECODE_ENABLED="yes" is in wd.conf file
#declare -r VERSION=2.10g ### Fix uninitialized veriable bug which was causing recording jobs to abort
#declare -r VERSION=2.10h ### Client mode: fix race condition on check for kiwirecorder.py
#declare -r VERSION=2.10i ### Client mode: On Ubuntu 20.04 LTS, Fix installation of python-numpy
#declare -r VERSION=2.10j ### Load WSJT-x V2.3.0 wsprd and jt9 commands and the libraries they need
declare -r VERSION=2.10k ### Add 'WD_...' rx site sw version number to spots uploaded to wsprnet.org
### TODO: Support FST4W decodomg through the use of 'jt9'
### TODO: Flush antique ~/signal_level log files
### TODO: Fix inode overflows when SIGNAL_LEVEL_UPLOAD="no" (e.g. at LX1DQ)
### TODO: Split Python utilities in seperate files maintained by git
### TODO: enhance config file validate_configuration_file() to check that all MERGEd receivers are defined.
### TODO: Try to extract grid for type 2 spots from ALL_WSPR.TXT
### TODO: Proxy upload of spots from wsprdaemon.org to wsprnet.org
### TODO: Add VOCAP support
### TODO: Add VHF/UHF support using Soapy API
if [[ $USER == "root" ]]; then
echo "ERROR: This command '$0' should NOT be run as user 'root' or non-root users will experience file permissions problems"
exit 1
fi
declare -r WSPRDAEMON_ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
declare -r WSPRDAEMON_ROOT_PATH="${WSPRDAEMON_ROOT_DIR}/${0##*/}"
export TZ=UTC LC_TIME=POSIX ### Ensures that log dates will be in UTC
lc_numeric=$(locale | sed -n '/LC_NUMERIC/s/.*="*\([^"]*\)"*/\1/p') ### There must be a better way, but locale sometimes embeds " in it output and this gets rid of them
if [[ "${lc_numeric}" != "POSIX" ]] && [[ "${lc_numeric}" != "en_US" ]] && [[ "${lc_numeric}" != "en_US.UTF-8" ]] && [[ "${lc_numeric}" != "en_GB.UTF-8" ]] && [[ "${lc_numeric}" != "C.UTF-8" ]] ; then
echo "WARNING: LC_NUMERIC '${lc_numeric}' on your server is not the expected value 'en_US.UTF-8'." ### Try to ensure that the numeric frequency comparisons use the format nnnn.nnnn
echo " If the spot frequencies reported by your server are not correct, you may need to change the 'locale' of your server"
fi
#############################################
declare -i verbosity=${verbosity:-0} ### default to level 0, but can be overridden on the cmd line. e.g "v=2 wsprdaemon.sh -V"
function verbosity_increment() {
verbosity=$(( $verbosity + 1))
echo "$(date): verbosity_increment() verbosity now = ${verbosity}"
}
function verbosity_decrement() {
[[ ${verbosity} -gt 0 ]] && verbosity=$(( $verbosity - 1))
echo "$(date): verbosity_decrement() verbosity now = ${verbosity}"
}
function setup_verbosity_traps() {
trap verbosity_increment SIGUSR1
trap verbosity_decrement SIGUSR2
}
function signal_verbosity() {
local up_down=$1
local pid_files=$(shopt -s nullglob ; echo *.pid)
if [[ -z "${pid_files}" ]]; then
echo "No *.pid files in $PWD"
return
fi
local file
for file in ${pid_files} ; do
local debug_pid=$(cat ${file})
if ! ps ${debug_pid} > /dev/null ; then
echo "PID ${debug_pid} from ${file} is not running"
else
echo "Signaling verbosity change to PID ${debug_pid} from ${file}"
kill -SIGUSR${up_down} ${debug_pid}
fi
done
}
### executed by cmd line '-d'
function increment_verbosity() {
signal_verbosity 1
}
### executed by cmd line '-D'
function decrement_verbosity() {
signal_verbosity 2
}
###################### Check OS ###################
if [[ "${OSTYPE}" == "linux-gnueabihf" ]] || [[ "${OSTYPE}" == "linux-gnu" ]] ; then
### We are running on a Rasperberry Pi or generic Debian server
declare -r GET_FILE_SIZE_CMD="stat --format=%s"
declare -r GET_FILE_MOD_TIME_CMD="stat -c %Y"
elif [[ "${OSTYPE}" == "darwin18" ]]; then
### We are running on a Mac, but as of 3/21/19 this code has not been verified to run on these systems
declare -r GET_FILE_SIZE_CMD="stat -f %z"
declare -r GET_FILE_MOD_TIME_CMD="stat -f %m"
else
### TODO:
echo "ERROR: We are running on a OS '${OSTYPE}' which is not yet supported"
exit 1
fi
################# Check that our recordings go to a tmpfs (i.e. RAM disk) file system ################
declare WSPRDAEMON_TMP_DIR=/tmp/wspr-captures
if df ${WSPRDAEMON_TMP_DIR} > /dev/null 2>&1; then
### Legacy name for /tmp file system. Leave it alone
true
else
WSPRDAEMON_TMP_DIR=/tmp/wsprdaemon
fi
function check_tmp_filesystem()
{
if [[ ! -d ${WSPRDAEMON_TMP_DIR} ]]; then
[[ $verbosity -ge 0 ]] && echo "The directrory system for WSPR recordings does not exist. Creating it"
if ! mkdir -p ${WSPRDAEMON_TMP_DIR} ; then
"ERROR: Can't create the directrory system for WSPR recordings '${WSPRDAEMON_TMP_DIR}'"
exit 1
fi
fi
if df ${WSPRDAEMON_TMP_DIR} | grep -q tmpfs ; then
[[ $verbosity -ge 1 ]] && echo "check_tmp_filesystem() found '${WSPRDAEMON_TMP_DIR}' is a tmpfs file system"
else
if [[ "${USE_TMPFS_FILE_SYSTEM-yes}" != "yes" ]]; then
echo "WARNING: configured to record to a non-ram file system"
else
echo "WARNING: This server is not configured so that '${WSPRDAEMON_TMP_DIR}' is a 300 MB ram file system."
echo " Every 2 minutes this program can write more than 200 Mbps to that file system which will prematurely wear out a microSD or SSD"
read -p "So do you want to modify your /etc/fstab to add that new file system? [Y/n]> "
REPLY=${REPLY:-Y} ### blank or no response change to 'Y'
if [[ ${REPLY^} != "Y" ]]; then
echo "WARNING: you have chosen to use to non-ram file system"
else
if ! grep -q ${WSPRDAEMON_TMP_DIR} /etc/fstab; then
sudo cp -p /etc/fstab /etc/fstab.save
echo "Modifying /etc/fstab. Original has been saved to /etc/fstab.save"
echo "tmpfs ${WSPRDAEMON_TMP_DIR} tmpfs defaults,noatime,nosuid,size=300m 0 0" | sudo tee -a /etc/fstab > /dev/null
fi
mkdir ${WSPRDAEMON_TMP_DIR}
if ! sudo mount -a ${WSPRDAEMON_TMP_DIR}; then
echo "ERROR: failed to mount ${WSPRDAEMON_TMP_DIR}"
exit
fi
echo "Your server has been configured so that '${WSPRDAEMON_TMP_DIR}' is a tmpfs (RAM disk)"
fi
fi
fi
}
check_tmp_filesystem
################## Check that kiwirecorder is installed and running #######################
declare -r DPKG_CMD="/usr/bin/dpkg"
declare -r GREP_CMD="/bin/grep"
declare KIWI_RECORD_DIR="${WSPRDAEMON_ROOT_DIR}/kiwiclient"
declare KIWI_RECORD_COMMAND="${KIWI_RECORD_DIR}/kiwirecorder.py"
declare KIWI_RECORD_TMP_LOG_FILE="./kiwiclient.log"
function check_for_kiwirecorder_cmd() {
local get_kiwirecorder="no"
local apt_update_done="no"
if [[ ! -x ${KIWI_RECORD_COMMAND} ]]; then
[[ ${verbosity} -ge 1 ]] && echo "$(date): check_for_kiwirecorder_cmd() found no ${KIWI_RECORD_COMMAND}"
get_kiwirecorder="yes"
else
## kiwirecorder.py has been installed. Check to see if kwr is missing some needed modules
[[ ${verbosity} -ge 1 ]] && echo "$(date): check_for_kiwirecorder_cmd() found ${KIWI_RECORD_COMMAND}"
local log_file=/tmp/${KIWI_RECORD_TMP_LOG_FILE}
if ! python3 ${KIWI_RECORD_COMMAND} --help >& ${log_file} ; then
echo "Currently installed version of kiwirecorder.py fails to run:"
cat ${log_file}
if ! ${GREP_CMD} "No module named 'numpy'" ${log_file}; then
echo "Found unknown error in ${log_file} when running 'python3 ${KIWI_RECORD_COMMAND}'"
exit 1
fi
if sudo apt install python3-numpy ; then
echo "Successfully installed numpy"
else
echo "'sudo apt install python3-numpy' failed to install numpy"
if ! pip3 install numpy; then
echo "Installation command 'pip3 install numpy' failed"
exit 1
fi
echo "Installation command 'pip3 install numpy' was successful"
if ! python3 ${KIWI_RECORD_COMMAND} --help >& ${log_file} ; then
echo "Currently installed version of kiwirecorder.py fails to run even after installing module numpy"
exit 1
fi
fi
fi
### kwirecorder.py ran successfully
if ! ${GREP_CMD} "ADC OV" ${log_file} > /dev/null 2>&1 ; then
get_kiwirecorder="yes"
echo "Currently installed version of kiwirecorder.py does not support overload reporting, so getting new version"
rm -rf ${KIWI_RECORD_DIR}.old
mv ${KIWI_RECORD_DIR} ${KIWI_RECORD_DIR}.old
else
[[ ${verbosity} -ge 1 ]] && echo "$(date): check_for_kiwirecorder_cmd() found ${KIWI_RECORD_COMMAND} supports 'ADC OV', so newest version is loaded"
fi
fi
if [[ ${get_kiwirecorder} == "yes" ]]; then
cd ${WSPRDAEMON_ROOT_DIR}
echo "Installing kiwirecorder in $PWD"
if ! ${DPKG_CMD} -l | ${GREP_CMD} -wq git ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt-get --yes install git
fi
git clone git://github.com/jks-prv/kiwiclient
echo "Downloading the kiwirecorder SW from Github..."
if [[ ! -x ${KIWI_RECORD_COMMAND} ]]; then
echo "ERROR: can't find the kiwirecorder.py command needed to communicate with a KiwiSDR. Download it from https://github.com/jks-prv/kiwiclient/tree/jks-v0.1"
echo " You may also need to install the Python library 'numpy' with: sudo apt-get install python-numpy"
exit 1
fi
if ! ${DPKG_CMD} -l | ${GREP_CMD} -wq python-numpy ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt --yes install python-numpy
fi
echo "Successfully installed kwirecorder.py"
cd - >& /dev/null
fi
}
if ! check_for_kiwirecorder_cmd ; then
echo "ERROR: failed to find or load Kiwi recording utility '${KIWI_RECORD_COMMAND}'"
exit 1
fi
################ Check for the existence of a config file and that it differss from the prototype conf file ################
declare -r WSPRDAEMON_CONFIG_FILE=${WSPRDAEMON_ROOT_DIR}/wsprdaemon.conf
declare -r WSPRDAEMON_CONFIG_TEMPLATE_FILE=${WSPRDAEMON_TMP_DIR}/template.conf
if [[ ! -f ${WSPRDAEMON_CONFIG_TEMPLATE_FILE} ]]; then
cat << 'EOF' > ${WSPRDAEMON_CONFIG_TEMPLATE_FILE}
# To enable these options, remove the leading '#' and modify SIGNAL_LEVEL_UPLOAD_ID from "AI6VN" to your call sign:
#SIGNAL_LEVEL_UPLOAD="proxy" ### If this variable is defined and not "no", AND SIGNAL_LEVEL_UPLOAD_ID is defined, then upload signal levels to the wsprdaemon cloud database
### SIGNAL_LEVEL_UPLOAD_MODE="no" => (Default) Upload spots directly to wsprnet.org
### SIGNAL_LEVEL_UPLOAD_MODE="noise" => Upload extended spots and noise data. Upload spots directly to wsprnet.org
### SIGNAL_LEVEL_UPLOAD_MODE="proxy" => In addition to "noise", don't upload to wsprnet.org from this server. Regenerate and upload spots to wsprnet.org on the wsprdaemon.org server
#SIGNAL_LEVEL_UPLOAD="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then upload extended spots and noise levels to the logs.wsprdaemon.org database and graphics file server.
#SIGNAL_LEVEL_UPLOAD_ID="AI6VN" ### The name put in upload log records, the the title bar of the graph, and the name used to view spots and noise at that server.
#SIGNAL_LEVEL_UPLOAD_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then FTP graphs of the last 24 hours to http://wsprdaemon.org/graphs/SIGNAL_LEVEL_UPLOAD_ID
#SIGNAL_LEVEL_LOCAL_GRAPHS="yes" ### If this variable is defined as "yes" AND SIGNAL_LEVEL_UPLOAD_ID is defined, then make graphs visible at http://localhost/
##############################################################
### The RECEIVER_LIST() array defines the physical (KIWI_xxx,AUDIO_xxx,SDR_xxx) and logical (MERG...) receive devices available on this server
### Each element of RECEIVER_LIST is a string with 5 space-seperated fields:
### " ID(no spaces) IP:PORT or RTL:n MyCall MyGrid KiwPassword Optional SIGNAL_LEVEL_ADJUSTMENTS
### [[DEFAULT:ADJUST,]BAND_0:ADJUST[,BAND_N:ADJUST_N]...]
### A comma-separated list of BAND:ADJUST pairs
### BAND is one of 2200..10, while ADJUST is in dBs TO BE ADDED to the raw data
### So If you have a +10 dB LNA, ADJUST '-10' will LOWER the reported level so that your reports reflect the level at the input of the LNA
### DEFAULT defaults to zero and is applied to all bands not specified with a BAND:ADJUST
declare RECEIVER_LIST=(
"KIWI_0 10.11.12.100:8073 AI6VN CM88mc NULL"
"KIWI_1 10.11.12.101:8073 AI6VN CM88mc foobar DEFAULT:-10,80:-12,30:-8,20:2,15:6"
"KIWI_2 10.11.12.102:8073 AI6VN CM88mc foobar"
"AUDIO_0 localhost:0,0 AI6VN CM88mc foobar" ### The id AUDIO_xxx is special and defines a local audio input device as the source of WSPR baseband 1400-1600 Hz signals
"AUDIO_1 localhost:1,0 AI6VN CM88mc foobar"
"SDR_0 RTL-SDR:0 AI6VN CM88mc foobar" ### The id SDR_xxx is special and defines a local RTL-SDR or other Soapy-suported device
"SDR_1 RTL-SDR:1 AI6VN CM88mc foobar"
"MERG_0 KIWI_1,KIWI2,AUDIO_1,SDR_1 AI6VN CM88mc foobar"
)
### This table defines a schedule of configurations which will be applied by '-j a,all' and thus by the watchdog daemon when it runs '-j a,all' ev ery odd two minutes
### The first field of each entry in the start time for the configuration defined in the following fields
### Start time is in the format HH:MM (e.g 13:15) and by default is in the time zone of the host server unless ',UDT' is appended, e.g '01:30,UDT'
### Following the time are one or more fields of the format 'RECEIVER,BAND'
### If the time of the first entry is not 00:00, then the latest (not necessarily the last) entry will be applied at time 00:00
### So the form of each line is "START_HH:MM[,UDT] RECEIVER,BAND... ". Here are some examples:
declare WSPR_SCHEDULE_simple=(
"00:00 KIWI_0,630 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
)
declare WSPR_SCHEDULE_merged=(
"00:00 MERG_0,630 MERG_0,160"
)
declare WSPR_SCHEDULE_complex=(
"sunrise-01:00 KIWI_0,630 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 "
"sunrise+01:00 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
"09:00 KIWI_0,630 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 "
"10:00 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
"11:00 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
"18:00 KIWI_0,2200 KIWI_0,630 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 "
"sunset-01:00 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
"sunset+01:00 KIWI_0,630 KIWI_0,160 KIWI_1,80 KIWI_2,80eu KIWI_2,60 KIWI_2,60eu KIWI_1,40 KIWI_1,30 KIWI_1,20 KIWI_1,17 KIWI_1,15 KIWI_1,12 KIWI_1,10"
)
### This array WSPR_SCHEDULE defines the running configuration. Here we make the simple configuration defined above the active one:
declare WSPR_SCHEDULE=( "${WSPR_SCHEDULE_simple[@]}" )
EOF
fi
### Check that there is a conf file
if [[ ! -f ${WSPRDAEMON_CONFIG_FILE} ]]; then
echo "WARNING: The configuration file '${WSPRDAEMON_CONFIG_FILE}' is missing, so it is being created from a template."
echo " Edit that file to match your Reciever(s) and the WSPR band(s) you wish to scan on it (them). Then run this again"
mv ${WSPRDAEMON_CONFIG_TEMPLATE_FILE} ${WSPRDAEMON_CONFIG_FILE}
exit
fi
### Check that it differs from the prototype
if diff -q ${WSPRDAEMON_CONFIG_TEMPLATE_FILE} ${WSPRDAEMON_CONFIG_FILE} > /dev/null; then
echo "WARNING: The configuration file '${WSPRDAEMON_CONFIG_FILE}' is the same as the template."
echo " Edit that file to match your Reciever(s) and the WSPR band(s) you wish to scan on it (them). Then run this again"
exit
fi
### Config file exists, now validate it.
### Validation requries that we have a list of valid BANDs
### These are the band frequencies taken from wsprnet.org
# ----------Band----------Dial Frequency----------TX Frequency center(+range)--------------
# 2190m--------------0.136000---------------0.137500 (+- 100Hz)
# 630m--------------0.474200---------------0.475700 (+- 100Hz)
# 160m--------------1.836600---------------1.838100 (+- 100Hz)
# 80m--------------3.568600---------------3.570100 (+- 100Hz) (this is the default frequency in WSJT-X v1.8.0 to be within the Japanese allocation.)
# 80m--------------3.592600---------------3.594100 (+- 100Hz) (No TX allowed for Japan; http://www.jarl.org/English/6_Band_Plan/JapaneseAmateurBandplans20150105...)
# 60m--------------5.287200---------------5.288700 (+- 100Hz) (please check local band plan if you're allowed to operate on this frequency!)
# 60m--------------5.364700---------------5.366200 (+- 100Hz) (valid for 60m band in Germany or other EU countries, check local band plan prior TX!)
# 40m--------------7.038600---------------7.040100 (+- 100Hz)
# 30m-------------10.138700--------------10.140200 (+- 100Hz)
# 20m-------------14.095600--------------14.097100 (+- 100Hz)
# 17m-------------18.104600--------------18.106100 (+- 100Hz)
# 15m-------------21.094600--------------21.096100 (+- 100Hz)
# 12m-------------24.924600--------------24.926100 (+- 100Hz)
# 10m-------------28.124600--------------28.126100 (+- 100Hz)
# 6m-------------50.293000--------------50.294500 (+- 100Hz)
# 4m-------------70.091000--------------70.092500 (+- 100Hz)
# 2m------------144.489000-------------144.490500 (+- 100Hz)
# 70cm------------432.300000-------------432.301500 (+- 100Hz)
# 23cm-----------1296.500000------------1296.501500 (+- 100Hz)
### These are the 'dial frequency' in KHz. The actual wspr tx frequenecies are these values + 1400 to 1600 Hz
declare WSPR_BAND_LIST=(
"2200 136.0"
"630 474.2"
"160 1836.6"
"80 3568.6"
"80eu 3592.6"
"60 5287.2"
"60eu 5364.7"
"40 7038.6"
"30 10138.7"
"20 14095.6"
"17 18104.6"
"15 21094.6"
"12 24924.6"
"10 28124.6"
"6 50293.0"
"4 70091.0"
"2 144489.0"
"1 432300.0"
"0 1296500.0"
"WWVB 58.5"
"WWV_2_5 2498.5"
"WWV_5 4998.5"
"WWV_10 9998.5"
"WWV_15 14998.5"
"WWV_20 19998.5"
"WWV_25 24998.5"
"CHU_3 3328.5"
"CHU_7 7848.5"
"CHU_14 14668.5"
)
function get_wspr_band_name_from_freq_hz() {
local band_freq_hz=$1
local band_freq_khz=$(bc <<< "scale = 1; ${band_freq_hz} / 1000")
local i
for i in $( seq 0 $(( ${#WSPR_BAND_LIST[*]} - 1)) ) ; do
local band_info=(${WSPR_BAND_LIST[i]})
local this_band=${band_info[0]}
local this_freq_khz=${band_info[1]}
if [[ ${band_freq_khz} == ${this_freq_khz} ]]; then
echo ${this_band}
return
fi
done
[[ ${verbosity} -ge 1 ]] && echo "$(date): get_wspr_band_name_from_freq_hz() ERROR, can't find band for band_freq_hz = '${band_freq_hz}'" 1>&2
echo ${band_freq_hz}
}
function get_wspr_band_freq(){
local target_band=$1
local i
for i in $( seq 0 $(( ${#WSPR_BAND_LIST[*]} - 1)) ) ; do
local band_info=(${WSPR_BAND_LIST[i]})
local this_band=${band_info[0]}
local this_freq_khz=${band_info[1]}
if [[ ${target_band} == ${this_band} ]]; then
echo ${this_freq_khz}
return
fi
done
}
### Validation requries that we have a list of valid RECEIVERs
###
function get_receiver_list_index_from_name() {
local new_receiver_name=$1
local i
for i in $(seq 0 $(( ${#RECEIVER_LIST[*]} - 1 )) ) ; do
local receiver_info=(${RECEIVER_LIST[i]})
local receiver_name=${receiver_info[0]}
local receiver_ip_address=${receiver_info[1]}
if [[ ${receiver_name} == ${new_receiver_name} ]]; then
echo ${i}
return 0
fi
done
}
function get_receiver_ip_from_name() {
local receiver_name=$1
local receiver_info=( ${RECEIVER_LIST[$(get_receiver_list_index_from_name ${receiver_name})]} )
echo ${receiver_info[1]}
}
function get_receiver_call_from_name() {
local receiver_name=$1
local receiver_info=( ${RECEIVER_LIST[$(get_receiver_list_index_from_name ${receiver_name})]} )
echo ${receiver_info[2]}
}
function get_receiver_grid_from_name() {
local receiver_name=$1
local receiver_info=( ${RECEIVER_LIST[$(get_receiver_list_index_from_name ${receiver_name})]} )
echo ${receiver_info[3]}
}
function get_receiver_af_list_from_name() {
local receiver_name=$1
local receiver_info=( ${RECEIVER_LIST[$(get_receiver_list_index_from_name ${receiver_name})]} )
echo ${receiver_info[5]-}
}
function get_receiver_khz_offset_list_from_name() {
local receiver_name=$1
local receiver_info=( ${RECEIVER_LIST[$(get_receiver_list_index_from_name ${receiver_name})]} )
local khz_offset=0
local khz_info=${receiver_info[6]-}
if [[ -n "${khz_info}" ]]; then
khz_offset=${khz_info##*:}
fi
echo ${khz_offset}
}
### Validation requires we check the time specified for each job
#### Input is HH:MM or {sunrise,sunset}{+,-}HH:MM
declare -r SUNTIMES_FILE=${WSPRDAEMON_ROOT_DIR}/suntimes ### cache sunrise HH:MM and sunset HH:MM for Reciever's Maidenhead grid
declare -r MAX_SUNTIMES_FILE_AGE_SECS=86400 ### refresh that cache file once a day
### Adds or subtracts two: HH:MM +/- HH:MM
function time_math() {
local -i index_hr=$((10#${1%:*})) ### Force all HH MM to be decimal number with no leading zeros
local -i index_min=$((10#${1#*:}))
local math_operation=$2 ### I expect only '+' or '-'
local -i offset_hr=$((10#${3%:*}))
local -i offset_min=$((10#${3#*:}))
local -i result_hr=$(($index_hr $2 $offset_hr))
local -i result_min=$((index_min $2 $offset_min))
if [[ $result_min -ge 60 ]]; then
(( result_min -= 60 ))
(( result_hr++ ))
fi
if [[ $result_min -lt 0 ]]; then
(( result_min += 60 ))
(( result_hr-- ))
fi
if [[ $result_hr -ge 24 ]]; then
(( result_hr -= 24 ))
fi
if [[ $result_hr -lt 0 ]]; then
(( result_hr += 24 ))
fi
printf "%02.0f:%02.0f\n" ${result_hr} $result_min
}
######### This block of code supports scheduling changes based upon local sunrise and/or sunset ############
declare A_IN_ASCII=65 ## Decimal value of 'A'
declare ZERO_IN_ASCII=48 ## Decimal value of '0'
function alpha_to_integer() {
echo $(( $( printf "%d" "'$1" ) - $A_IN_ASCII ))
}
function digit_to_integer() {
echo $(( $( printf "%d" "'$1" ) - $ZERO_IN_ASCII ))
}
### This returns the approximate lat/long of a Maidenhead 4 or 6 chancter locator
### Primarily useful in getting sunrise and sunset time
function maidenhead_to_long_lat() {
printf "%s %s\n" \
$(( $(( $(alpha_to_integer ${1:0:1}) * 20 )) + $(( $(digit_to_integer ${1:2:1}) * 2)) - 180))\
$(( $(( $(alpha_to_integer ${1:1:1}) * 10 )) + $(digit_to_integer ${1:3:1}) - 90))
}
declare ASTRAL_SUN_TIMES_SCRIPT=${WSPRDAEMON_TMP_DIR}/suntimes.py
function get_astral_sun_times() {
local lat=$1
local lon=$2
local zone=$3
## This is run only once every 24 hours, so recreate it to be sure it is from this version of WD
cat > ${ASTRAL_SUN_TIMES_SCRIPT} << EOF
import datetime, sys
from astral import Astral, Location
from datetime import date
lat=float(sys.argv[1])
lon=float(sys.argv[2])
zone=sys.argv[3]
l = Location(('wsprep', 'local', lat, lon, zone, 0))
l.sun()
d = date.today()
sun = l.sun(local=True, date=d)
print( str(sun['sunrise'])[11:16] + " " + str(sun['sunset'])[11:16] )
EOF
local sun_times=$(python3 ${ASTRAL_SUN_TIMES_SCRIPT} ${lat} ${lon} ${zone})
echo "${sun_times}"
}
function get_sunrise_sunset() {
local maiden=$1
local long_lat=( $(maidenhead_to_long_lat $maiden) )
[[ $verbosity -gt 2 ]] && echo "$(date): get_sunrise_sunset() for maidenhead ${maiden} at long/lat ${long_lat[@]}"
if [[ ${GET_SUNTIMES_FROM_ASTRAL-yes} == "yes" ]]; then
local long=${long_lat[0]}
local lat=${long_lat[1]}
local zone=$(timedatectl | awk '/Time/{print $3}')
if [[ "${zone}" == "n/a" ]]; then
zone="UTC"
fi
local astral_times=($(get_astral_sun_times ${lat} ${long} ${zone}))
local sunrise_hm=${astral_times[0]}
local sunset_hm=${astral_times[1]}
else
local querry_results=$( curl "https://api.sunrise-sunset.org/json?lat=${long_lat[1]}&lng=${long_lat[0]}&formatted=0" 2> /dev/null )
local query_lines=$( echo ${querry_results} | sed 's/[,{}]/\n/g' )
local sunrise=$(echo "$query_lines" | sed -n '/sunrise/s/^[^:]*//p'| sed 's/:"//; s/"//')
local sunset=$(echo "$query_lines" | sed -n '/sunset/s/^[^:]*//p'| sed 's/:"//; s/"//')
local sunrise_hm=$(date --date=$sunrise +%H:%M)
local sunset_hm=$(date --date=$sunset +%H:%M)
fi
echo "$sunrise_hm $sunset_hm"
}
function get_index_time() { ## If sunrise or sunset is specified, Uses Reciever's name to find it's maidenhead and from there lat/long leads to sunrise and sunset
local time_field=$1
local receiver_grid=$2
local hour
local minute
local -a time_field_array
if [[ ${time_field} =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]]; then
### This is a properly formatted HH:MM time spec
time_field_array=(${time_field/:/ })
hour=${time_field_array[0]}
minute=${time_field_array[1]}
echo "$((10#${hour}))${minute}"
return
fi
if [[ ! ${time_field} =~ sunrise|sunset ]]; then
echo "ERROR: time specification '${time_field}' is not valid"
exit 1
fi
## Sunrise or sunset has been specified. Uses Reciever's name to find it's maidenhead and from there lat/long leads to sunrise and sunset
if [[ ! -f ${SUNTIMES_FILE} ]] || [[ $(( $(date +"%s") - $( $GET_FILE_MOD_TIME_CMD ${SUNTIMES_FILE} ))) -gt ${MAX_SUNTIMES_FILE_AGE_SECS} ]] ; then
### Once per day, cache the sunrise/sunset times for the grids of all receivers
rm -f ${SUNTIMES_FILE}
local maidenhead_list=$( ( IFS=$'\n' ; echo "${RECEIVER_LIST[*]}") | awk '{print $4}' | sort | uniq)
for grid in ${maidenhead_list[@]} ; do
local suntimes=($(get_sunrise_sunset ${grid}))
if [[ ${#suntimes[@]} -ne 2 ]]; then
echo "ERROR: get_index_time() can't get sun up/down times"
exit 1
fi
echo "${grid} ${suntimes[@]}" >> ${SUNTIMES_FILE}
done
echo "$(date): Got today's sunrise and sunset times" 1>&2
fi
if [[ ${time_field} =~ sunrise ]] ; then
index_time=$(awk "/${receiver_grid}/{print \$2}" ${SUNTIMES_FILE} )
else ## == sunset
index_time=$(awk "/${receiver_grid}/{print \$3}" ${SUNTIMES_FILE} )
fi
local offset="00:00"
local sign="+"
if [[ ${time_field} =~ \+ ]] ; then
offset=${time_field#*+}
elif [[ ${time_field} =~ \- ]] ; then
offset=${time_field#*-}
sign="-"
fi
local offset_time=$(time_math $index_time $sign $offset)
if [[ ${offset_time} =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]]; then
echo ${offset_time}
else
### It would surprise me if we ever got to this line, since sunrise/sunset will be good and time_math() should always return a valid HH:MM
echo "ERROR: get_index_time() calculated an invalid sunrise/sunset job time '${offset_time}' from the specified field '${time_field}" 1>&2
fi
}
### Validate the schedule
###
function validate_configured_schedule()
{
local found_error="no"
local sched_index
for sched_index in $(seq 0 $((${#WSPR_SCHEDULE[*]} - 1 )) ); do
local sched_line=(${WSPR_SCHEDULE[${sched_index}]})
local sched_line_index_max=${#sched_line[@]}
if [[ ${sched_line_index_max} -lt 2 ]]; then
echo "ERROR: WSPR_SCHEDULE[@] line '${sched_line}' does not have the required minimum 2 fields"
exit 1
fi
[[ $verbosity -ge 5 ]] && echo "testing schedule line ${sched_line[@]}"
local job_time=${sched_line[0]}
local index
for index in $(seq 1 $(( ${#sched_line[@]} - 1 )) ); do
local job=${sched_line[${index}]}
[[ $verbosity -ge 5 ]] && echo "testing job $job"
local -a job_elements=(${job//,/ })
local job_elements_count=${#job_elements[@]}
if [[ $job_elements_count -ne 2 ]]; then
echo "ERROR: in WSPR_SCHEDULE line '${sched_line[@]}', job '${job}' doesn't have the form 'RECEIVER,BAND'"
exit 1
fi
local job_rx=${job_elements[0]}
local job_band=${job_elements[1]}
local rx_index
rx_index=$(get_receiver_list_index_from_name ${job_rx})
if [[ -z "${rx_index}" ]]; then
echo "ERROR: in WSPR_SCHEDULE line '${sched_line[@]}', job '${job}' specifies receiver '${job_rx}' not found in RECEIVER_LIST"
found_error="yes"
fi
band_freq=$(get_wspr_band_freq ${job_band})
if [[ -z "${band_freq}" ]]; then
echo "ERROR: in WSPR_SCHEDULE line '${sched_line[@]}', job '${job}' specifies band '${job_band}' not found in WSPR_BAND_LIST"
found_error="yes"
fi
local job_grid="$(get_receiver_grid_from_name ${job_rx})"
local job_time_resolved=$(get_index_time ${job_time} ${job_grid})
local ret_code=$?
if [[ ${ret_code} -ne 0 ]]; then
echo "ERROR: in WSPR_SCHEDULE line '${sched_line[@]}', time specification '${job_time}' is not valid"
exit 1
fi
if ${GREP_CMD} -qi ERROR <<< "${job_time_resolved}" ; then
echo "ERROR: in WSPR_SCHEDULE line '${sched_line[@]}', time specification '${job_time}' is not valid"
exit 1
fi
done
done
[[ ${found_error} == "no" ]] && return 0 || return 1
}
###
function validate_configuration_file()
{
if [[ ! -f ${WSPRDAEMON_CONFIG_FILE} ]]; then
echo "ERROR: configuratino file '${WSPRDAEMON_CONFIG_FILE}' does not exist"
exit 1
fi
source ${WSPRDAEMON_CONFIG_FILE}
if [[ -z "${RECEIVER_LIST[@]-}" ]]; then
echo "ERROR: configuration file '${WSPRDAEMON_CONFIG_FILE}' does not contain a definition of the RECEIVER_LIST[*] array or that array is empty"
exit 1
fi
local max_index=$(( ${#RECEIVER_LIST[@]} - 1 ))
if [[ ${max_index} -lt 0 ]]; then
echo "ERROR: configuration file '${WSPRDAEMON_CONFIG_FILE}' defines RECEIVER_LIST[*] but it contains no receiver definitions"
exit 1
fi
### Create a list of receivers and validate all are specifired to be in the same grid. More validation could be added later
local rx_name=""
local rx_grid=""
local first_rx_grid=""
local rx_line
local -a rx_line_info_fields=()
local -a rx_name_list=("")
local index
for index in $(seq 0 ${max_index}); do
rx_line_info_fields=(${RECEIVER_LIST[${index}]})
if [[ ${#rx_line_info_fields[@]} -lt 5 ]]; then
echo "ERROR: configuration file '${WSPRDAEMON_CONFIG_FILE}' contains 'RECEIVER_LIST[] configuration line '${rx_line_info_fields[@]}' which has fewer than the required 5 fields"
exit 1
fi
rx_name=${rx_line_info_fields[0]}
rx_grid=${rx_line_info_fields[3]}
if [[ -z "${first_rx_grid}" ]]; then
first_rx_grid=${rx_grid}
fi
if [[ $verbosity -gt 1 ]] && [[ "${rx_grid}" != "${first_rx_grid}" ]]; then
echo "INFO: configuration file '${WSPRDAEMON_CONFIG_FILE}' contains 'RECEIVER_LIST[] configuration line '${rx_line_info_fields[@]}'"
echo " that specifies grid '${rx_grid}' which differs from the grid '${first_rx_grid}' of the first receiver"
fi
### Validate file name, i.i don't allow ',' characters in the name
if [[ ${rx_name} =~ , ]]; then
echo "ERROR: the receiver '${rx_name}' defined in wsprdaemon.conf contains the invalid character ','"
exit 1
fi
rx_name_list=(${rx_name_list[@]} ${rx_name})
### More testing of validity of the fields on this line could be done
done
if [[ -z "${WSPR_SCHEDULE[@]-}" ]]; then
echo "ERROR: configuration file '${WSPRDAEMON_CONFIG_FILE}' exists, but does not contain a definition of the WSPR_SCHEDULE[*] array, or that array is empty"
exit 1
fi
local max_index=$(( ${#WSPR_SCHEDULE[@]} - 1 ))
if [[ ${max_index} -lt 0 ]]; then
echo "ERROR: configuration file '${WSPRDAEMON_CONFIG_FILE}' declares WSPR_SCHEDULE[@], but it contains no schedule definitions"
exit 1
fi
validate_configured_schedule
}
### Before proceeding further in the startup, validate the config file so the user sees any errors on the command line
if ! validate_configuration_file; then
exit 1
fi
source ${WSPRDAEMON_CONFIG_FILE}
### Additional bands can be defined in the conf file
WSPR_BAND_LIST+=( ${EXTRA_BAND_LIST[@]- } )
WSPR_BAND_CENTERS_IN_MHZ+=( ${EXTRA_BAND_CENTERS_IN_MHZ[@]- } )
### There is a valid config file.
### Only after the config file has been sourced, then check for utilities needed
################################### Noise level logging
declare -r SIGNAL_LEVELS_WWW_DIR=/var/www/html
declare -r SIGNAL_LEVELS_WWW_INDEX_FILE=${SIGNAL_LEVELS_WWW_DIR}/index.html
declare -r NOISE_GRAPH_FILENAME=noise_graph.png
declare -r SIGNAL_LEVELS_NOISE_GRAPH_FILE=${WSPRDAEMON_TMP_DIR}/${NOISE_GRAPH_FILENAME} ## If configured, this is the png graph copied to the graphs.wsprdaemon.org site and displayed by the local Apache server
declare -r SIGNAL_LEVELS_TMP_NOISE_GRAPH_FILE=${WSPRDAEMON_TMP_DIR}/wd_tmp.png ## If configured, this is the file created by the python graphing program
declare -r SIGNAL_LEVELS_WWW_NOISE_GRAPH_FILE=${SIGNAL_LEVELS_WWW_DIR}/${NOISE_GRAPH_FILENAME} ## If we have the Apache serivce running to locally display noise graphs, then this will be a symbolic link to ${SIGNAL_LEVELS_NOISE_GRAPH_FILE}
declare -r SIGNAL_LEVELS_TMP_CSV_FILE=${WSPRDAEMON_TMP_DIR}/wd_log.csv
function ask_user_to_install_sw() {
local prompt_string=$1
local is_requried_by_wd=${2:-}
echo ${prompt_string}
read -p "Do you want to proceed with the installation of that this software? [Yn] > "
REPLY=${REPLY:-Y}
REPLY=${REPLY:0:1}
if [[ ${REPLY^} != "Y" ]]; then
if [[ -n "${is_requried_by_wd}" ]]; then
echo "${is_requried_by_wd} is a software utility required by wsprdaemon and must be installed for it to run"
else
echo "WARNING: change wsprdaemon.conf to avoid installtion of this software"
fi
exit
fi
}
### To avoid conflicts with wsprd from WSJT-x which may be also installed on this PC, run a WD copy of wsprd
declare WSPRD_BIN_DIR=${WSPRDAEMON_ROOT_DIR}/bin
mkdir -p ${WSPRD_BIN_DIR}
declare WSPRD_CMD=${WSPRD_BIN_DIR}/wsprd
declare WSPRD_VERSION_CMD=${WSPRD_BIN_DIR}/wsprd.version
declare WSPRD_CMD_FLAGS="${WSPRD_CMD_FLAGS--C 500 -o 4 -d}"
declare WSJTX_REQUIRED_VERSION="${WSJTX_REQUIRED_VERSION:-2.3.0}"
### 10/14/20 RR: Always install the 'jt9', but only execute it if 'JT9_CMD_EANABLED="yes"' is added to wsprdaemon.conf
declare JT9_CMD=${WSPRD_BIN_DIR}/jt9
declare JT9_CMD_FLAGS="${JT9_CMD_FLAGS:---fst4w -p 120 -L 1400 -H 1600 -d 3}"
declare JT9_DECODE_EANABLED=${JT9_DECODE_EANABLED:-no}
function check_for_needed_utilities()
{
### TODO: Check for kiwirecorder only if there are kiwis receivers spec
local apt_update_done="no"
local dpkg_list=$(${DPKG_CMD} -l)
if ! [[ ${dpkg_list} =~ " at " ]] ; then
### Used by the optional wsprd_vpn service
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt-get install at --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'at' which is needed iby the wspd_vpn service"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " bc " ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt-get install bc --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'bc' which is needed for floating point frequency calculations"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " curl " ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt-get install curl --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'curl' which is needed for uploading to wsprnet.org and wsprdaemon.org"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " ntp " ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get --yes update && apt_update_done="yes"
sudo apt-get install ntp --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'ntp' which is needed to ensure synchronization with the 2 minute WSPR cycle"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " postgresql " ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get update && apt_update_done="yes"
sudo apt-get install postgresql libpq-dev postgresql-client postgresql-client-common --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'postgresql' which is needed for logging spots and noise to wsprdaemon.org"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " sox " ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get update && apt_update_done="yes"
sudo apt-get install sox --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'sox' which is needed for RMS noise level calculations"
exit 1
fi
fi
### WD uses the 'wsprd' binary from the WSJT-x package. The following section insures that one binary we use from that package is installed
### 9/16/20 RR - WSJT-x doesn't yet install on Ubuntu 20.04, so special case that.
### 'wsprd' doesn't report its version number (e.g. with wsprd -V), so on most systems we learn the version from 'dpkt -l'.
### On Ubuntu 20.04 we can't install the package, so we can't learn the version number from dpkg.
### So on Ubuntu 20.04 we assume that if wsprd is installed it is the correct version
### Perhaps I will save the version number of wsprd and use this process on all OSs
if ! [[ ${dpkg_list} =~ " libgfortran5:" ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get update && apt_update_done="yes"
sudo apt-get install libgfortran5 --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'libgfortran5' which is needed to run wsprd V2.3.xxxx"
exit 1
fi
fi
if ! [[ ${dpkg_list} =~ " qt5-default:" ]] ; then
[[ ${apt_update_done} == "no" ]] && sudo apt-get update && apt_update_done="yes"
sudo apt-get install qt5-default --assume-yes
local ret_code=$?
if [[ $ret_code -ne 0 ]]; then
echo "FATAL ERROR: Failed to install 'qt5-default' which is needed to run the 'jt9' copmmand in wsprd V2.3.xxxx"
exit 1
fi
fi
### If wsprd is installed, try to get its version number
declare WSPRD_VERSION_CMD=${WSPRD_CMD}.version ### Since WSJT-x wsprd doesn't have a '-V' to identify its version, save the version here
local wsprd_version=""
if [[ -x ${WSPRD_VERSION_CMD} ]]; then
wsprd_version=$( ${WSPRD_VERSION_CMD} )
else
wsprd_version=$(awk '/wsjtx /{print $3}' <<< "${dpkg_list}")
if [[ -n "${wsprd_version}" ]] && [[ -x ${WSPRD_CMD} ]] && [[ ! -x ${WSPRD_VERSION_CMD} ]]; then
sudo sh -c "echo 'echo ${wsprd_version}' > ${WSPRD_VERSION_CMD}"
sudo chmod +x ${WSPRD_VERSION_CMD}
fi
fi
### Now install wsprd if it doesn't exist or if it is the wrong version
if [[ ! -x ${WSPRD_CMD} ]] || [[ -z "${wsprd_version}" ]] || [[ ${wsprd_version} != ${WSJTX_REQUIRED_VERSION} ]]; then
local os_name=""
if [[ -f /etc/os-release ]]; then
### See if we are running on Ubuntu 20.04
### can't use 'source /etc/os-release' since the variable names in that conflict with variables in WD
os_name=$(awk -F = '/^VERSION=/{print $2}' /etc/os-release | sed 's/"//g')
fi
local cpu_arch=$(uname -m)
local wsjtx_pkg=""
case ${cpu_arch} in
x86_64)
wsjtx_pkg=wsjtx_${WSJTX_REQUIRED_VERSION}_amd64.deb
;;
armv7l)
# https://physics.princeton.edu/pulsar/K1JT/wsjtx_2.2.1_armhf.deb
wsjtx_pkg=wsjtx_${WSJTX_REQUIRED_VERSION}_armhf.deb
;;
aarch64)
wsjtx_pkg=wsjtx_${WSJTX_REQUIRED_VERSION}_arm64.deb
;;
*)
echo "ERROR: CPU architecture '${cpu_arch}' is not supported by this program"
exit 1
;;
esac
### Download WSJT-x and extract its files and copy wsprd to /usr/bin/
local wsjtx_dpkg_file=${WSPRDAEMON_TMP_DIR}/${wsjtx_pkg}
wget http://physics.princeton.edu/pulsar/K1JT/${wsjtx_pkg} -O ${wsjtx_dpkg_file}
if [[ ! -f ${wsjtx_dpkg_file} ]] ; then
echo "ERROR: failed to download wget http://physics.princeton.edu/pulsar/K1JT/${wsjtx_pkg}"
exit 1
fi
local dpkg_tmp_dir=${WSPRDAEMON_TMP_DIR}/dpkg_wsjt
mkdir -p ${dpkg_tmp_dir}
dpkg-deb -x ${wsjtx_dpkg_file} ${dpkg_tmp_dir}
ret_code=$?
if [[ ${ret_code} -ne 0 ]] ; then
echo "ERROR: on ${os_name} failed to extract files from package file ${wsjtx_pkg_file}"
exit 1
fi
local dpkg_wsprd_file=${dpkg_tmp_dir}/usr/bin/wsprd
if [[ ! -x ${dpkg_wsprd_file} ]]; then
echo "ERROR: failed to find executable '${dpkg_wsprd_file}' in the dowloaded WSJT-x package"
exit 1