Compare commits

...

275 commits

Author SHA1 Message Date
DJ2LS
f7bcdad47f finally working macOS build 2023-01-22 23:35:52 +01:00
DJ2LS
1b90d38ebf fixed wrong pyinstaller env detection for windows 2023-01-22 22:39:23 +01:00
DJ2LS
5a3f367a33 version update 2023-01-22 22:05:29 +01:00
DJ2LS
6ae542230f fixed path env 2023-01-22 22:05:07 +01:00
DJ2LS
f18604a38b possible hotfix for missing portaudio 2023-01-22 21:47:14 +01:00
DJ2LS
caaf613446 version update 2023-01-22 19:58:07 +01:00
DJ2LS
9b13efacba fixed macOS build folder path 2023-01-22 19:28:34 +01:00
DJ2LS
4ea78356b6 version update for using latest codec2 2023-01-22 09:49:59 +01:00
DJ2LS
545aaf031f set environment vars 2023-01-21 20:20:09 +01:00
DJ2LS
3086a42da9 restored .git filter 2023-01-21 19:32:04 +01:00
DJ2LS
6b853511f4 notarization hotfix 2023-01-21 19:18:22 +01:00
DJ2LS
9a772e28c2 version update 2023-01-21 18:14:27 +01:00
DJ2LS
eaca7c168e
Merge pull request #336 from DJ2LS/update_github_action
macOS code signing
2023-01-21 17:50:26 +01:00
DJ2LS
4e83d49144 increased attempts for build app 2023-01-21 17:20:12 +01:00
DJ2LS
6e863ddd22 moved entitlements to different folder 2023-01-21 16:03:52 +01:00
DJ2LS
19a473c577 moved entitlements to different folder 2023-01-21 16:03:12 +01:00
DJ2LS
7f7061dae3 removed dmg build step 2023-01-21 15:54:16 +01:00
DJ2LS
432d091c97 set fail-fast: false 2023-01-21 15:48:54 +01:00
DJ2LS
8e68aef7f9 improvements to package json 2023-01-21 15:39:15 +01:00
DJ2LS
0635d35443 version update 2023-01-21 15:16:47 +01:00
DJ2LS
1686f554b1 version update 2023-01-21 15:14:55 +01:00
DJ2LS
679da34ed7
Merge pull request #263 from DJ2LS/update_github_action
fixing macOS builds and code signing
2023-01-21 14:48:26 +01:00
DJ2LS
caa0bb2aac fixed package.json 2023-01-21 14:34:02 +01:00
DJ2LS
44bf16f15e another attempt remove all .git folders 2023-01-21 14:22:45 +01:00
DJ2LS
7794f1afd2 another attempt remove all .git folders 2023-01-21 14:22:27 +01:00
DJ2LS
7a32e464f4 another attempt remove all .git folders 2023-01-21 14:14:20 +01:00
DJ2LS
6dfc85e983 another attempt remove all .git folders 2023-01-21 13:35:16 +01:00
DJ2LS
3d8fe47270 another attempt remove all .git folders 2023-01-21 13:18:16 +01:00
DJ2LS
eb3c559ce2 added popperjs dependency 2023-01-21 13:15:23 +01:00
DJ2LS
d4a821ac39 remove .git folder recursively 2023-01-21 13:02:45 +01:00
DJ2LS
47dd3fc23a remove .git folder recursively 2023-01-21 13:01:35 +01:00
DJ2LS
0e465d8bbb remove .git folder which prevents elecron from code signing 2023-01-21 12:48:43 +01:00
DJ2LS
3044759569 updated freedata.spec 2023-01-21 12:07:50 +01:00
DJ2LS
2a8d1fd471 add entitlements.plist 2023-01-21 12:05:12 +01:00
DJ2LS
1d14f51854 add osx_certs 2023-01-21 12:03:13 +01:00
DJ2LS
da6dd1f045 version update which should now work 2023-01-20 20:56:10 +01:00
DJ2LS
21c98a8366 version update which should now work 2023-01-20 20:36:25 +01:00
DJ2LS
8e9b9c9199 outcommented python 3.12 as its not supported by nuitka yet 2023-01-20 20:16:19 +01:00
DJ2LS
059d23fa31 added python 3.12 to ctest 2023-01-20 16:33:18 +01:00
DJ2LS
784aa9cf82 added python 3.12 to ctest 2023-01-20 16:17:55 +01:00
DJ2LS
440fda917a update to python 3.11 2023-01-20 16:16:18 +01:00
DJ2LS
7326f871e5 remove duplicate build step, update to nodejs 18, update to python 3.10 2023-01-20 16:15:04 +01:00
DJ2LS
4687dd36e7 don't rename for macos 2023-01-20 13:24:17 +01:00
DJ2LS
93d238d4e1 force overwriting output files for pyinstaller 2023-01-19 21:49:35 +01:00
DJ2LS
4c9ee09e86
Merge branch 'main' into update_github_action 2023-01-19 21:27:05 +01:00
DJ2LS
1bd6537d64 attempt fixing macOS build with new build steps 2023-01-19 21:15:23 +01:00
DJ2LS
97143f25f6
Merge pull request #330 from Mashintime/tuj-wftheme 2023-01-17 21:47:06 +01:00
Mashintime
72d412891e bins variable was unused, removed 2023-01-16 17:45:06 -05:00
Mashintime
94b4889478 Set waterfall theme and small wf optimization 2023-01-16 17:34:42 -05:00
DJ2LS
1de42f6ff1 version update for hotfix 2023-01-16 11:51:42 +01:00
DJ2LS
310479fef4 version update 2023-01-16 10:16:49 +01:00
DJ2LS
3c0484cf46
Merge pull request #323 from DJ2LS/ls-arq
Further ARQ improvements + speed chart
2023-01-16 10:15:51 +01:00
DJ2LS
0ee9671a7d avoid double entris in speed chart list 2023-01-16 10:11:45 +01:00
DJ2LS
1c7d9d8d0c update speedChart only if data available 2023-01-16 10:06:03 +01:00
DJ2LS
81d434bfd1 update speedChart only if data available 2023-01-15 19:35:25 +01:00
dj2ls
6dcfe17c5f first version transmission chart 2023-01-13 00:14:42 +01:00
dj2ls
4904c5e7df arq session fixes 2023-01-11 16:18:29 +01:00
DJ2LS
32b181b3b0 if received ping, set dxgrid placeholder instead of default grid for avoiding wrong distance calculation 2023-01-09 18:37:00 +01:00
dj2ls
da1309a335 release channel busy state when receiving FreeDATA signals 2023-01-08 13:18:54 +01:00
DJ2LS
6ab2f0ce54
Merge pull request #326 from Mashintime/ls-arq 2023-01-07 16:15:27 +01:00
Mashintime
4e46577a57
Use consistent color theme on explorer button 2023-01-07 09:26:29 -05:00
dj2ls
ab96c8bc51 fixed gridsquare handler 2023-01-07 12:01:54 +01:00
awh@brainless.us
4988a92f52 Add an explorer button to the gui 2023-01-06 23:27:52 -05:00
dj2ls
44395c32f4 fixed wrong dxgrid information 2023-01-06 17:33:20 +01:00
dj2ls
034577144b fixed test frame 2023-01-06 17:02:21 +01:00
dj2ls
9497d323c8 improved timeout reset 2023-01-06 15:50:25 +01:00
dj2ls
41a45a9cd9 reduced busy counter release time 2023-01-06 15:42:34 +01:00
dj2ls
4a76e5fc1e display frame type when sending 2023-01-06 15:08:58 +01:00
dj2ls
3727c59584 display frame type when sending 2023-01-06 15:03:05 +01:00
dj2ls
aba556a996 fixed timeouts starting too early 2023-01-06 14:41:15 +01:00
dj2ls
cc61f6a602 added timestamp to explorer 2023-01-05 15:28:41 +01:00
dj2ls
8c63259c26 test with transform: translateZ(0); 2023-01-04 23:13:24 +01:00
DJ2LS
11826c492d fixed rigdummy 2023-01-04 22:49:24 +01:00
dj2ls
796294f03e pressing enter wont restart gui on saving frequency 2023-01-04 21:07:46 +01:00
dj2ls
5c50ca4803 small gui improvement 2023-01-04 20:52:00 +01:00
dj2ls
d54f3dcb31 updated input frequency 2023-01-04 20:47:00 +01:00
dj2ls
53697af6e2 set mode from gui 2023-01-04 20:12:03 +01:00
dj2ls
ee4528bf79 set frequency from gui - needed for better remote testing 2023-01-04 19:26:11 +01:00
dj2ls
ac1a82fc5e placeholder for changing frequency from gui 2023-01-04 14:23:42 +01:00
dj2ls
93d61b0f25 changed logging timestamp to "iso" 2023-01-04 12:48:36 +01:00
dj2ls
252fe73d68 don't stop connecting when busy 2023-01-04 11:23:30 +01:00
dj2ls
b1f4dadcc6 possible fix of timeout bug 2023-01-04 11:04:10 +01:00
dj2ls
df52828207 redesigned of rigctld driver 2023-01-04 08:34:42 +01:00
dj2ls
7ba1dbadb2 redesigned of rigctld driver 2023-01-04 08:33:25 +01:00
dj2ls
5d8b8a4bde adjusted logging for debugging byteorder error 2023-01-02 16:13:29 +01:00
DJ2LS
03705a041c removed decoding twice fix - seems its not a LDPC related error 2023-01-01 17:00:45 +01:00
DJ2LS
501ccd9de3 possible quick and dirty fix for solving wrong byteorder problem 2023-01-01 16:41:27 +01:00
DJ2LS
dc4ea2c5dd possible quick and dirty fix for solving wrong byteorder problem 2023-01-01 10:56:09 +01:00
DJ2LS
250943f63f Merge remote-tracking branch 'origin/ls-arq' into ls-arq 2022-12-31 16:05:24 +01:00
DJ2LS
4264f7d9bb print raw data on tx 2022-12-31 16:05:08 +01:00
dj2ls
bdc407aff9 some more fixes to time 2022-12-31 13:22:34 +01:00
dj2ls
7831cdaa16 some more fixes to time 2022-12-31 13:16:10 +01:00
dj2ls
4c53cdc79b time adjustment if smaller 0 2022-12-31 13:03:46 +01:00
dj2ls
bbb08ed7af small code cleanup 2022-12-31 13:01:54 +01:00
dj2ls
756101ebe1 first run with time left for gui 2022-12-31 12:59:10 +01:00
dj2ls
a43d90f7d8 calculate timeleft for transmission until finished 2022-12-31 12:38:46 +01:00
dj2ls
28e8aaa595 if frame NACK, show received data 2022-12-31 11:55:41 +01:00
dj2ls
36f80a5b0a reverted search area mode change 2022-12-31 11:53:24 +01:00
dj2ls
5c19d6f90e adjust search area to mode - this might avoid false positives when searching for already received data 2022-12-30 23:24:31 +01:00
dj2ls
d2dc5c98d4 reduced channel busy timeouts 2022-12-30 21:29:22 +01:00
dj2ls
1bd1781dd5 adjustment to session opener while connected 2022-12-29 23:27:16 +01:00
dj2ls
aa6f787630 updated version information 2022-12-29 23:24:42 +01:00
dj2ls
2cb7ee7893 attempt avoiding connecting while already connected 2022-12-29 19:16:47 +01:00
dj2ls
4ddfe52db9 ssid list fix when using config file 2022-12-29 18:57:52 +01:00
dj2ls
ab44e18e3e check channel busy state for ack/nack 2022-12-29 17:57:55 +01:00
dj2ls
e05577b492 return failed callsign check with bytes instead of str 2022-12-29 17:49:30 +01:00
dj2ls
5be2a88fd0 catch fft division by zero error 2022-12-29 17:49:13 +01:00
DJ2LS
a3f9dd6f45 updated version information 2022-12-29 12:55:03 +01:00
DJ2LS
9c57cc9eb7 updated version information 2022-12-29 09:39:41 +01:00
DJ2LS
0d13b638ad
Merge pull request #311 from DJ2LS/ls-arq
arq improvements from OTA tests
2022-12-29 09:38:42 +01:00
DJ2LS
71051fd3fa send disconnect frame on version missmatch 2022-12-29 09:19:35 +01:00
DJ2LS
4d2d0b93ac fix slice crashing nuitka 2022-12-28 23:47:13 +01:00
DJ2LS
3b8236826c fix slice crashing nuitka 2022-12-28 23:21:39 +01:00
DJ2LS
00d007b9b7 handle split char false positive 2022-12-28 18:03:05 +01:00
DJ2LS
2392ff3b53 handle split char false positive 2022-12-28 17:37:50 +01:00
DJ2LS
17977b5281 break connection attempts when version missmatch 2022-12-28 17:22:17 +01:00
DJ2LS
8717629b91 increased protocol version 2022-12-28 17:06:55 +01:00
DJ2LS
47e177f399 fix split_char 2022-12-28 13:37:37 +01:00
DJ2LS
e30f09f4de fix message order 2022-12-28 13:13:56 +01:00
DJ2LS
586c02f73b fix utf encoding error 2022-12-28 13:08:46 +01:00
DJ2LS
751e43eddb changing split char and split char order 2022-12-28 12:40:10 +01:00
DJ2LS
37267ac679 changing split char and split char order 2022-12-28 12:27:09 +01:00
DJ2LS
f2e5a11348 changing split char and split char order 2022-12-28 11:59:00 +01:00
DJ2LS
efd92cdd72 changing split char and split char order 2022-12-28 11:32:33 +01:00
DJ2LS
2ba8b77ea7 added crc check and sleeping time for processing bigger data 2022-12-28 00:05:12 +01:00
DJ2LS
4cce8aec5c added total bytes to rx message 2022-12-27 23:32:52 +01:00
DJ2LS
58342b975f more crc check improvements 2022-12-27 23:30:15 +01:00
DJ2LS
c6d99f8866 typo fix 2022-12-27 23:04:57 +01:00
DJ2LS
c8eb9bbf92 fix upper lower case 2022-12-27 23:02:52 +01:00
DJ2LS
c5a9229207 first attempt with message checksum 2022-12-27 22:58:18 +01:00
DJ2LS
b637b917bc first attempt with message checksum 2022-12-27 22:57:54 +01:00
DJ2LS
3dc06510c1 moving from raw to wave file format 2022-12-27 21:13:08 +01:00
DJ2LS
76522db082 process rigctld response only if needed 2022-12-27 18:17:12 +01:00
DJ2LS
d6df3007db process rigctld response only if needed 2022-12-27 18:04:29 +01:00
DJ2LS
5a0a766aa0 process rigctld response only if needed 2022-12-27 17:57:04 +01:00
DJ2LS
eaa16ed50b process rigctld response only if needed 2022-12-27 17:49:49 +01:00
DJ2LS
281731d890 process rigctld response only if needed 2022-12-27 17:48:06 +01:00
DJ2LS
d572772df3 use busy detection while opening a channel 2022-12-27 17:11:46 +01:00
DJ2LS
9dad094e47 reduced rigctld connection chunk size - attempt fixing the ptt delay problem 2022-12-27 13:13:11 +01:00
DJ2LS
cb78ed984a reduced rigctld connection chunk size - attempt fixing the ptt delay problem 2022-12-27 13:02:01 +01:00
DJ2LS
fd402d9bc2 catch error in dbfs calculation 2022-12-27 11:41:00 +01:00
DJ2LS
9a4401082c catching explorer type error 2022-12-27 11:33:16 +01:00
DJ2LS
c78fff4db1 catching explorer type error 2022-12-27 11:29:24 +01:00
DJ2LS
d78fcba4fb catching explorer type error 2022-12-27 10:37:34 +01:00
DJ2LS
7ed43fb3f9 moved utf8 encoding from entire data to just chat message 2022-12-27 09:53:21 +01:00
DJ2LS
f5a30e33e3 publish last heard stations to explorer 2022-12-26 21:14:23 +01:00
DJ2LS
a9139d035c fixed message order bug 2022-12-26 20:41:58 +01:00
DJ2LS
62309d608d improved audio callback modout queue 2022-12-26 17:14:23 +01:00
DJ2LS
4d8b8f7f46 accepted some sourcery suggestions 2022-12-26 12:49:01 +01:00
DJ2LS
7691ba09ac record audio update 2022-12-26 12:27:13 +01:00
DJ2LS
c0e4f14da0 record audio 2022-12-26 12:11:59 +01:00
DJ2LS
424384c7ed 3nd test run with saving data from tnc 2022-12-26 10:49:37 +01:00
DJ2LS
e2d4b58e30 2nd test run with saving data from tnc 2022-12-26 10:35:58 +01:00
DJ2LS
76f24f2b31 first test run with saving data from tnc 2022-12-26 10:25:50 +01:00
DJ2LS
af851d15f3 beacon now with full grid 2022-12-25 17:08:20 +01:00
DJ2LS
9ef19b7c51 moved ptt to audio callback 2022-12-25 15:20:46 +01:00
DJ2LS
914e2065b5 later ptt toggle test 2022-12-25 14:28:21 +01:00
DJ2LS
f896dd84c5 reduced connection attempts to 10 2022-12-25 14:24:39 +01:00
DJ2LS
f12bf7919d modem testings with disabled monitoring protection 2022-12-25 12:57:47 +01:00
DJ2LS
dcec4a4d17 attempt avoiding false positives for session id 2022-12-25 12:55:52 +01:00
DJ2LS
a946ca6555 dont send beacon when busy 2022-12-25 09:19:55 +01:00
DJ2LS
0f984c26b2 typo 2022-12-25 08:57:05 +01:00
DJ2LS
10d337b962 message parsing adjustment 2022-12-25 00:29:57 +01:00
DJ2LS
855159d150 small ctest adjustments 2022-12-24 23:13:27 +01:00
DJ2LS
443d7931b7 small ctest adjustments 2022-12-24 23:13:21 +01:00
DJ2LS
33e0c2d497 disabled updating last received timestamp when sending nack 2022-12-24 18:39:51 +01:00
DJ2LS
af7c9cbafb reduced amount of n max retries per burst to fit which fits more to 180s timeout 2022-12-24 18:07:49 +01:00
DJ2LS
a546f0f387 better logging for debugging data received when in wrong tnc state 2022-12-24 17:55:31 +01:00
DJ2LS
43eff325d1
Merge pull request #310 from DJ2LS/ls-gui
hotfix for getting mycall from tnc
2022-12-24 16:45:31 +01:00
DJ2LS
eba1cde5b4 save mycall and grid on input 2022-12-24 16:44:06 +01:00
DJ2LS
d26186e6fd
Merge pull request #308 from DJ2LS/ls-gui
small gui fixes
2022-12-24 16:14:41 +01:00
DJ2LS
495b988701 save mycall and grid on input 2022-12-24 15:51:26 +01:00
DJ2LS
f730fbe3d7 save mycall and grid on input 2022-12-24 15:48:57 +01:00
DJ2LS
6b60fd4275 increased protocol version 2022-12-24 11:27:56 +01:00
DJ2LS
d8255fbd2f
Merge pull request #299 from DJ2LS/ls-arq
fix ssid and response handling
2022-12-24 11:26:32 +01:00
DJ2LS
c143c87d64 fixing #305 2022-12-23 21:06:42 +01:00
DJ2LS
f979fb3d7b increased logging for finding reason for failing ctest 2022-12-23 10:30:32 +01:00
DJ2LS
934bb20010
attempt fixing disconnect 2022-12-19 21:57:57 +01:00
DJ2LS
9d26472607 dxsnr for cq/qrv 2022-12-19 16:46:18 +01:00
DJ2LS
847c3928df some ssid fixes 2022-12-19 16:04:30 +01:00
DJ2LS
f64c4ff0dd some ssid fixes 2022-12-19 16:00:47 +01:00
DJ2LS
1937606526 some ssid fixes 2022-12-19 15:42:48 +01:00
DJ2LS
771afabd7d updated cli tools cq and ping 2022-12-19 15:06:30 +01:00
DJ2LS
e069be45ba updated cli tools cq and ping 2022-12-19 15:05:04 +01:00
DJ2LS
242b4adc5f updated chmod+x 2022-12-19 14:54:29 +01:00
DJ2LS
12d1477c36 another adjustments to cli tools 2022-12-16 17:09:48 +01:00
DJ2LS
e672218594 adjustments to cli tools 2022-12-15 17:14:34 +01:00
DJ2LS
9c8237f505 message now freedata chat compatible 2022-12-15 16:46:12 +01:00
DJ2LS
db9c5b60b9 reuse codec2 mode init for transmission 2022-12-15 13:45:57 +01:00
DJ2LS
179f4f7265 increased protocol version because of lzma compression 2022-12-13 09:17:42 +01:00
DJ2LS
dfa07f9e77 increased frame timeouts and better logging for debugging protocol error 2022-12-13 09:10:40 +01:00
DJ2LS
50bbcfd7ff increased frame timeouts and better logging for debugging protocol error 2022-12-13 08:59:10 +01:00
DJ2LS
5d8b4f2d67 re-enable early error detection 2022-12-12 18:24:40 +01:00
DJ2LS
819832127e attempt using lzma instead of zlib 2022-12-12 15:02:57 +01:00
DJ2LS
6e3edb5b30 attempt using threading.Event().wait() instead of time.sleep() 2022-12-12 12:28:52 +01:00
DJ2LS
10b925d70b fixed bug in listening state machine which caused high CPU load 2022-12-12 11:43:42 +01:00
DJ2LS
2cdea7eaa9 adjusted search range for rx buffer 2022-12-12 10:06:45 +01:00
DJ2LS
19bcde01b0 increased logging for frame nack 2022-12-11 13:07:56 +01:00
DJ2LS
2e12b2ed92 increase amount of NACK frames for failed data frame 2022-12-11 12:23:37 +01:00
DJ2LS
bdeba55f8e updated config.ini 2022-12-11 12:08:34 +01:00
DJ2LS
11512d76bf reduced logging and added transmit queue size for debugging purposes 2022-12-11 11:57:37 +01:00
DJ2LS
d9a7d392ce attempt with reduced transmission timeout to 180s 2022-12-11 11:13:55 +01:00
DJ2LS
8c0c13f227 attempt with increased data frame ACK 2022-12-11 11:00:58 +01:00
DJ2LS
ddf6cb0ee0 report IRS SNR for Ping 2022-12-10 20:46:17 +01:00
DJ2LS
3d6bf72633 define individual name for config file 2022-12-10 13:34:26 +01:00
DJ2LS
331102e03d updated cli tools 2022-12-10 10:52:03 +01:00
DJ2LS
cc75d351ab updated cli tools 2022-12-09 22:48:29 +01:00
DJ2LS
f2dd278bce reduced minimum SNR for datac1 2022-12-09 14:55:09 +01:00
DJ2LS
af889f9460 fixed config for bool value 2022-12-09 14:49:43 +01:00
DJ2LS
dba907dabf codefactr fixes 2022-12-09 11:24:39 +01:00
DJ2LS
75c514f342 attempt fixing ctest 2022-12-09 11:15:41 +01:00
DJ2LS
362cef9b08 attempt fixing ctest 2022-12-09 10:39:34 +01:00
DJ2LS
56701347fc attempt fixing ctest 2022-12-09 10:31:25 +01:00
DJ2LS
51d7c681a6 updated network listener 2022-12-09 10:22:42 +01:00
DJ2LS
1b1bb18bb2 updated chat resending message 2022-12-09 10:22:03 +01:00
DJ2LS
4c611731a8 fix of #302 closes #302 2022-12-08 20:21:44 +01:00
DJ2LS
db315bae72 possible fix of #302 2022-12-08 20:05:50 +01:00
DJ2LS
c499a6a524 make sure own ssid is always part of ssid list 2022-12-08 18:46:19 +01:00
DJ2LS
9dcf0959b4 make sure own ssid is always part of ssid list 2022-12-08 18:44:29 +01:00
DJ2LS
d0d853d72b make sure own ssid is always part of ssid list 2022-12-08 18:17:01 +01:00
DJ2LS
85b16c7935 fixing config file when starting from daemon.py 2022-12-08 18:09:13 +01:00
DJ2LS
b543c914a0 fixing config file when starting from daemon.py 2022-12-08 16:26:33 +01:00
DJ2LS
4c69b748d5 fixing config file when starting from daemon.py 2022-12-08 16:14:59 +01:00
DJ2LS
83b3f6a71c process disconnect frame only while not disconnected 2022-12-07 17:02:24 +01:00
DJ2LS
12933e96b7 process disconnect frame only while not disconnected 2022-12-07 16:54:00 +01:00
DJ2LS
90c4bf9360 optimised ping and connection ssid 2022-12-07 16:30:59 +01:00
DJ2LS
29c1d0ad72 make sure RX_BUFFER.maxsize is always int 2022-12-07 15:52:22 +01:00
DJ2LS
c3f198fda9 make sure RX_BUFFER.maxsize is always int 2022-12-07 15:41:37 +01:00
DJ2LS
eeffb8c6e4 fft enabled by default 2022-12-05 17:37:09 +01:00
DJ2LS
9468ceb6fc fixed a bug in callsign parsing 2022-12-05 16:57:45 +01:00
DJ2LS
02c08a71a8 better logging for modem_error_state 2022-12-05 15:49:10 +01:00
DJ2LS
ac62fe4448 more logging for TypeError when saving data 2022-12-05 15:46:57 +01:00
DJ2LS
d5a0464fd9 first attempt with catching modem error states for early NACK 2022-12-05 15:23:03 +01:00
DJ2LS
a2fd010198 sample config file 2022-12-05 13:02:43 +01:00
DJ2LS
ea1b6cb27c sample config file 2022-12-05 13:01:49 +01:00
DJ2LS
2de80520e1 freedata network listener 2022-12-05 08:47:07 +01:00
DJ2LS
fa7360f1f3 catch python error for possible bug 2022-12-05 08:43:19 +01:00
DJ2LS
9941c7ea4d arq cleanup improvements 2022-12-05 08:23:18 +01:00
DJ2LS
6ea653c5aa changed config, moved to default enabled FFT 2022-12-05 07:44:51 +01:00
DJ2LS
8251b35ebb added respond to call 2022-12-04 16:56:32 +01:00
DJ2LS
8b2412de96 added respond to call 2022-12-04 16:56:12 +01:00
DJ2LS
4b5622647e improved config file error handling 2022-12-04 16:43:50 +01:00
DJ2LS
7cd01e1e74 check if selecting audio devicename by string or id 2022-12-04 16:22:11 +01:00
DJ2LS
45a5e60b2b small comment improvement 2022-12-04 16:12:56 +01:00
DJ2LS
bc72e5f3ba set audio device by name 2022-12-04 16:12:43 +01:00
DJ2LS
ca955b1ff2 catch harmless RuntimeError: Set changed size during iteration 2022-12-04 15:35:41 +01:00
DJ2LS
40beee630c changed mycallsign / dxcallsign order for network-messages 2022-12-04 13:05:20 +01:00
DJ2LS
37da268547 changed mycallsign / dxcallsign order for network-messages 2022-12-04 13:04:29 +01:00
DJ2LS
cf4b3bf9cd first version overriding own callsign ssid 2022-12-04 13:01:09 +01:00
DJ2LS
564f4c106e first version overriding own callsign ssid 2022-12-04 12:52:25 +01:00
DJ2LS
ba2e2b8ce9 first cli tool for sending file 2022-12-04 12:24:12 +01:00
DJ2LS
cd2b0dc133 fixed send raw callsign override 2022-12-04 12:22:35 +01:00
DJ2LS
93f951663e attempt fixing ctests 2022-12-04 10:14:34 +01:00
DJ2LS
1aa3073aa2 attempt fixing ctests 2022-12-04 10:05:56 +01:00
DJ2LS
ce19b9c2d9 attempt fixing ctests 2022-12-04 09:51:50 +01:00
DJ2LS
03d4cce4a7 attempt fixing ctests 2022-12-04 09:43:32 +01:00
DJ2LS
966198a9c3 handle foreign pings 2022-12-03 14:05:39 +01:00
DJ2LS
913bce2ea5 handle foreign pings 2022-12-03 14:04:09 +01:00
DJ2LS
048b5c85b5 introduced self.dxcallsign 2022-12-03 13:59:05 +01:00
dj2ls
a9c415c59e fixing codefactor problem 2022-10-16 18:17:09 +02:00
dj2ls
a68d2c176a attempt making bash available for windows... 2022-10-16 17:12:11 +02:00
dj2ls
53d0383696 attempt making bash available for windows... 2022-10-16 17:08:54 +02:00
dj2ls
9053e2f57f attempt making bash available for windows... 2022-10-16 16:44:59 +02:00
dj2ls
f21069e29d check if portaudio already downloaded 2022-10-16 15:58:03 +02:00
dj2ls
18f670bac7 check if portaudio already downlaoded 2022-10-16 15:56:14 +02:00
dj2ls
03f5f8dcce fixed folder cleanup for macOS 2022-10-13 10:36:04 +02:00
dj2ls
c93cba9bc0 moved to pyinstaller for macOS 2022-10-13 10:15:51 +02:00
dj2ls
a67d6cb450 moved to pyinstaller for macOS 2022-10-13 10:03:47 +02:00
dj2ls
5516985f49 moved to pyinstaller for macOS 2022-10-13 09:53:53 +02:00
dj2ls
7efad9479c changed source folder 2022-10-13 08:58:18 +02:00
dj2ls
b125f36a13 attempt with virtualenv 2022-10-12 19:20:23 +02:00
dj2ls
32a01a5859 attempt with virtualenv 2022-10-12 19:03:05 +02:00
dj2ls
63d47cb745 attempt with virtualenv 2022-10-12 17:07:10 +02:00
dj2ls
b97db66736 attempt with virtualenv 2022-10-12 16:38:23 +02:00
dj2ls
19bff8cb4e attempt fixing macOS builds 2022-10-12 12:00:54 +02:00
36 changed files with 2481 additions and 704 deletions

View file

@ -100,9 +100,6 @@ jobs:
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }} name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
path: codec2/tempfiles/* path: codec2/tempfiles/*
BUILD_ARM: BUILD_ARM:
# The host should always be linux # The host should always be linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -199,6 +196,7 @@ jobs:
name: Build FreeDATA packages name: Build FreeDATA packages
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
os: [ubuntu-20.04, macos-11, windows-latest] os: [ubuntu-20.04, macos-11, windows-latest]
include: include:
@ -236,7 +234,7 @@ jobs:
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
- name: Create tnc/dist - name: Create tnc/dist
working-directory: tnc working-directory: tnc
@ -268,7 +266,7 @@ jobs:
# if: matrix.os == 'ubuntu-20.04' # if: matrix.os == 'ubuntu-20.04'
if: ${{startsWith(matrix.os, 'ubuntu')}} if: ${{startsWith(matrix.os, 'ubuntu')}}
run: | run: |
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
- name: Install MacOS pyAudio - name: Install MacOS pyAudio
if: ${{startsWith(matrix.os, 'macos')}} if: ${{startsWith(matrix.os, 'macos')}}
@ -282,27 +280,25 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
- name: Add MacOS certs
if: ${{startsWith(matrix.os, 'macos')}}
run: chmod +x add-osx-cert.sh && ./add-osx-cert.sh
env:
CERTIFICATE_OSX_APPLICATION: ${{ secrets.CERTIFICATE_OSX_APPLICATION }}
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
# - name: Install Pyaudio Windows - name: Build binaries macOS
# if: ${{startsWith(matrix.os, 'windows')}} if: ${{startsWith(matrix.os, 'macos')}}
# working-directory: tnc/lib/pyaudio/windows working-directory: tnc
# run: | run: |
# pip install PyAudio-0.2.11-cp39-cp39-win_amd64.whl # now build tnc binaries
pyinstaller -y freedata.spec
# and to some final cleanup
# cp -r -f dist/tnc/* dist/
# rm -r dist/tnc
# - name: Display structure of downloaded files - name: Build binaries Linux and Windows
# run: ls -R if: ${{!startsWith(matrix.os, 'macos')}}
# - name: cleanup codec2
# working-directory: tnc/lib/
# run: |
# mkdir codec2
# cd codec2
# - uses: actions/download-artifact@v3
# with:
# path: tnc/lib/codec2
- name: Build binaries
working-directory: tnc working-directory: tnc
run: | run: |
# pyinstaller freedata.spec # pyinstaller freedata.spec
@ -311,8 +307,8 @@ jobs:
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone daemon.py
python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --standalone main.py
- name: Copy binaries - Linux and MacOS - name: Copy binaries - Linux
if: ${{!startsWith(matrix.os, 'windows')}} if: ${{startsWith(matrix.os, 'ubuntu')}}
working-directory: tnc working-directory: tnc
run: | run: |
cp -r -f daemon.dist/* dist/tnc cp -r -f daemon.dist/* dist/tnc
@ -327,6 +323,8 @@ jobs:
cp -r -Force main.dist/* dist/tnc cp -r -Force main.dist/* dist/tnc
- name: Rename tnc binaries - name: Rename tnc binaries
# we don't need renaming for pyinstaller builds as output name is defined
if: ${{!startsWith(matrix.os, 'macos')}}
working-directory: tnc working-directory: tnc
run: | run: |
mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }} mv dist/tnc/daemon* dist/tnc/${{ matrix.daemon_binary_name }}
@ -336,62 +334,43 @@ jobs:
with: with:
path: tnc/dist/tnc path: tnc/dist/tnc
- name: LIST ALL FILES - name: LIST ALL FILES
run: ls -R run: ls -R
- name: Download Portaudio binaries - name: Download Portaudio binaries Linux macOS
if: ${{!startsWith(matrix.os, 'windows')}}
working-directory: tnc working-directory: tnc
run: | run: |
if ! test -d "dist/tnc/_sounddevice_data"; then
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
fi
- name: Download Portaudio binaries Windows
if: ${{startsWith(matrix.os, 'windows')}}
working-directory: tnc
run: |
if(Test-Path -Path "dist/_sounddevice_data"){
echo "sounddevice folder already exists"
} else {
git clone https://github.com/spatialaudio/portaudio-binaries dist/tnc/_sounddevice_data/portaudio-binaries
}
- name: LIST ALL FILES - name: LIST ALL FILES
run: ls -R run: ls -R
#- name: Compress TNC - name: cleanup on macos before code signing
# # if: ${{!startsWith(matrix.os, 'windows') }} if: ${{startsWith(matrix.os, 'macos')}}
# shell: bash run: |
# run: | ls -l
# cd ./tnc/dist # find . -type d -name .git -exec rm -r {} \;
# zip -r ./${{ matrix.zip_name }}.zip * find . -type d -o -name ".git" -delete
##- name: Copy TNC to GUI
## run: |
## # cp -R ./tnc/dist/tnc ./gui/tnc
## cp -R ./tnc/dist ./gui/tnc
##- name: LIST ALL FILES
## run: ls -R
##- name: Compress TNC
## uses: thedoctor0/zip-release@master
## with:
## type: 'zip'
## filename: '${{ matrix.zip_name }}'
## # directory: ./tnc/dist/tnc
## directory: ./tnc/dist/tnc
## path: .
## # exclusions: '*.git* /*node_modules/* .editorconfig'
##- name: LIST ALL FILES
## run: ls -R
##- name: Upload TNC artifacts
## uses: actions/upload-artifact@v3
## with:
## name: ${{ matrix.zip_name }}.zip
## # path: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
## path: ./tnc/dist/${{ matrix.zip_name }}.zip
##- name: Release TNC
## uses: softprops/action-gh-release@v1
## if: startsWith(github.ref, 'refs/tags/v')
## with:
## files: ./tnc/dist/tnc/${{ matrix.zip_name }}.zip
## #files: ./tnc/dist/${{ matrix.zip_name }}.zip
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
with: with:
package_root: "./gui/" package_root: "./gui/"
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}
@ -399,6 +378,7 @@ jobs:
# release the app after building # release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }} release: ${{ startsWith(github.ref, 'refs/tags/v') }}
args: ${{ matrix.electron_parameters }} args: ${{ matrix.electron_parameters }}
max_attempts: 3
- name: Compress TNC - name: Compress TNC
uses: thedoctor0/zip-release@master uses: thedoctor0/zip-release@master

View file

@ -21,6 +21,7 @@ jobs:
- python-version: "3.9" - python-version: "3.9"
- python-version: "3.10" - python-version: "3.10"
- python-version: "3.11" - python-version: "3.11"
- python-version: "3.12-dev"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

23
add-osx-cert.sh Normal file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env sh
KEY_CHAIN=build.keychain
CERTIFICATE_P12=certificate.p12
# Recreate the certificate from the secure environment variable
echo $CERTIFICATE_OSX_APPLICATION | base64 --decode > $CERTIFICATE_P12
#create a keychain
security create-keychain -p actions $KEY_CHAIN
# Make the keychain the default so identities are found
security default-keychain -s $KEY_CHAIN
# Unlock the keychain
security unlock-keychain -p actions $KEY_CHAIN
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign;
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
# remove certs
rm -fr *.p12

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>

View file

@ -92,7 +92,8 @@ const configDefaultSettings = '{\
"tuning_range_fmax" : "50.0",\ "tuning_range_fmax" : "50.0",\
"respond_to_cq" : "True",\ "respond_to_cq" : "True",\
"rx_buffer_size" : "16", \ "rx_buffer_size" : "16", \
"enable_explorer" : "False" \ "enable_explorer" : "False", \
"wftheme": 2 \
}'; }';
if (!fs.existsSync(configPath)) { if (!fs.existsSync(configPath)) {

View file

@ -1,6 +1,6 @@
{ {
"name": "FreeDATA", "name": "FreeDATA",
"version": "0.6.7-alpha.1", "version": "0.6.12-alpha.6",
"description": "FreeDATA ", "description": "FreeDATA ",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@ -28,50 +28,54 @@
}, },
"homepage": "https://freedata.app", "homepage": "https://freedata.app",
"dependencies": { "dependencies": {
"@electron/asar": "^3.2.3",
"@electron/osx-sign": "^1.0.4",
"@popperjs/core": "^2.11.6",
"blob-util": "^2.0.2", "blob-util": "^2.0.2",
"bootstrap": "^5.2.1", "bootstrap": "^5.2.1",
"bootstrap-icons": "^1.9.1", "bootstrap-icons": "^1.9.1",
"bootswatch": "^5.2.0", "bootswatch": "^5.2.0",
"chart.js": "^3.9.1", "chart.js": "^4.0.0",
"chartjs-plugin-annotation": "^2.0.1", "chartjs-plugin-annotation": "^2.1.2",
"electron-log": "^4.4.8", "electron-log": "^4.4.8",
"electron-updater": "^5.2.1", "electron-updater": "^5.2.1",
"emoji-picker-element": "^1.12.1", "emoji-picker-element": "^1.12.1",
"emoji-picker-element-data": "^1.3.0", "emoji-picker-element-data": "^1.3.0",
"express-pouchdb": "^4.2.0",
"mime": "^3.0.0", "mime": "^3.0.0",
"pouchdb": "^7.3.0", "pouchdb": "^7.3.0",
"pouchdb-browser": "^7.3.0", "pouchdb-browser": "^7.3.0",
"pouchdb-express-router": "^0.0.11",
"pouchdb-find": "^7.3.0", "pouchdb-find": "^7.3.0",
"pouchdb-replication": "^8.0.0",
"qth-locator": "^2.1.0", "qth-locator": "^2.1.0",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@electron/notarize": "^1.2.3",
"electron": "^20.1.3", "electron": "^20.1.3",
"electron-builder": "^23.3.3" "electron-builder": "^23.3.3",
"electron-builder-notarize": "^1.5.0"
}, },
"build": { "build": {
"productName": "FreeDATA", "productName": "FreeDATA",
"appId": "app.freedata", "appId": "app.freedata",
"afterSign": "electron-builder-notarize",
"npmRebuild": "false", "npmRebuild": "false",
"directories": { "directories": {
"buildResources": "build", "buildResources": "build",
"output": "dist" "output": "dist"
}, },
"dmg": { "mac": {
"target": [
"default"
],
"icon": "build/icon.png", "icon": "build/icon.png",
"contents": [ "hardenedRuntime": true,
{ "entitlements": "build/entitlements.plist",
"x": 130, "entitlementsInherit": "build/entitlements.plist",
"y": 220 "gatekeeperAssess": false
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
}, },
"win": { "win": {
"icon": "build/icon.png", "icon": "build/icon.png",
@ -95,7 +99,8 @@
"from": "../tnc/dist/tnc/", "from": "../tnc/dist/tnc/",
"to": "tnc", "to": "tnc",
"filter": [ "filter": [
"**/*" "**/*",
"!**/.git"
] ]
} }
] ]

View file

@ -34,7 +34,7 @@ const dateFormatHours = new Intl.DateTimeFormat('en-GB', {
hour12: false, hour12: false,
}); });
// split character // split character
const split_char = '\0;' const split_char = '\0;\1;'
// global for our selected file we want to transmit // global for our selected file we want to transmit
// ----------------- some chat globals // ----------------- some chat globals
var filetype = ''; var filetype = '';
@ -61,25 +61,51 @@ try{
} }
PouchDB.plugin(require('pouchdb-find')); PouchDB.plugin(require('pouchdb-find'));
var db = new PouchDB(chatDB); //PouchDB.plugin(require('pouchdb-replication'));
var remoteDB = new PouchDB('http://192.168.178.79:5984/chatDB')
db.sync(remoteDB, { var db = new PouchDB(chatDB);
/*
// REMOTE SYNC ATTEMPTS
var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB')
// we need express packages for running pouchdb sync "express-pouchdb"
var express = require('express');
var app = express();
//app.use('/chatDB', require('express-pouchdb')(PouchDB));
//app.listen(5984);
app.use('/chatDB', require('pouchdb-express-router')(PouchDB));
app.listen(5984);
db.sync('http://172.20.10.4:5984/jojo', {
//var sync = PouchDB.sync('chatDB', 'http://172.20.10.4:5984/chatDB', {
live: true, live: true,
retry: true retry: false
}).on('change', function (change) { }).on('change', function (change) {
// yo, something changed! // yo, something changed!
console.log(change) console.log(change)
}).on('paused', function (info) { }).on('paused', function (err) {
// replication was paused, usually because of a lost connection // replication was paused, usually because of a lost connection
console.log(info) console.log(err)
}).on('active', function (info) { }).on('active', function (info) {
// replication was resumed // replication was resumed
console.log(info) console.log(info)
}).on('error', function (err) { }).on('error', function (err) {
// totally unhandled error (shouldn't happen) // totally unhandled error (shouldn't happen)
console.log(error) console.log(err)
}).on('denied', function (err) {
// a document failed to replicate (e.g. due to permissions)
console.log(err)
}).on('complete', function (info) {
// handle complete;
console.log(info)
}); });
*/
var dxcallsigns = new Set(); var dxcallsigns = new Set();
db.createIndex({ db.createIndex({
@ -297,7 +323,9 @@ db.post({
} }
var timestamp = Math.floor(Date.now() / 1000); var timestamp = Math.floor(Date.now() / 1000);
var data_with_attachment = chatmessage + split_char + filename + split_char + filetype + split_char + file + split_char + timestamp; var file_checksum = crc32(file).toString(16).toUpperCase();
console.log(file_checksum)
var data_with_attachment = timestamp + split_char + chatmessage + split_char + filename + split_char + filetype + split_char + file;
document.getElementById('selectFilesButton').innerHTML = ``; document.getElementById('selectFilesButton').innerHTML = ``;
var uuid = uuidv4(); var uuid = uuidv4();
@ -308,7 +336,7 @@ db.post({
mode: 255, mode: 255,
frames: 1, frames: 1,
data: data_with_attachment, data: data_with_attachment,
checksum: '123', checksum: file_checksum,
uuid: uuid uuid: uuid
}; };
ipcRenderer.send('run-tnc-command', Data); ipcRenderer.send('run-tnc-command', Data);
@ -318,7 +346,7 @@ db.post({
dxcallsign: dxcallsign, dxcallsign: dxcallsign,
dxgrid: 'null', dxgrid: 'null',
msg: chatmessage, msg: chatmessage,
checksum: 'null', checksum: file_checksum,
type: "transmit", type: "transmit",
status: 'transmit', status: 'transmit',
uuid: uuid, uuid: uuid,
@ -404,7 +432,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
//handle ping //handle ping
if (item.ping == 'received') { if (item.ping == 'received') {
obj.timestamp = item.timestamp; obj.timestamp = parseInt(item.timestamp);
obj.dxcallsign = item.dxcallsign; obj.dxcallsign = item.dxcallsign;
obj.dxgrid = item.dxgrid; obj.dxgrid = item.dxgrid;
obj.uuid = item.uuid; obj.uuid = item.uuid;
@ -421,12 +449,9 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
add_obj_to_database(obj) add_obj_to_database(obj)
update_chat_obj_by_uuid(obj.uuid); update_chat_obj_by_uuid(obj.uuid);
// handle beacon // handle beacon
} else if (item.beacon == 'received') { } else if (item.beacon == 'received') {
obj.timestamp = item.timestamp; obj.timestamp = parseInt(item.timestamp);
obj.dxcallsign = item.dxcallsign; obj.dxcallsign = item.dxcallsign;
obj.dxgrid = item.dxgrid; obj.dxgrid = item.dxgrid;
obj.uuid = item.uuid; obj.uuid = item.uuid;
@ -451,20 +476,20 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
console.log(splitted_data) console.log(splitted_data)
obj.timestamp = splitted_data[8]; obj.timestamp = parseInt(splitted_data[4]);
obj.dxcallsign = item.dxcallsign; obj.dxcallsign = item.dxcallsign;
obj.dxgrid = item.dxgrid; obj.dxgrid = item.dxgrid;
obj.command = splitted_data[1]; obj.command = splitted_data[1];
obj.checksum = splitted_data[2]; obj.checksum = splitted_data[2];
// convert message to unicode from utf8 because of emojis // convert message to unicode from utf8 because of emojis
obj.uuid = utf8.decode(splitted_data[3]); obj.uuid = utf8.decode(splitted_data[3]);
obj.msg = utf8.decode(splitted_data[4]); obj.msg = utf8.decode(splitted_data[5]);
obj.status = 'null'; obj.status = 'null';
obj.snr = 'null'; obj.snr = 'null';
obj.type = 'received'; obj.type = 'received';
obj.filename = utf8.decode(splitted_data[5]); obj.filename = utf8.decode(splitted_data[6]);
obj.filetype = utf8.decode(splitted_data[6]); obj.filetype = utf8.decode(splitted_data[7]);
obj.file = btoa(utf8.decode(splitted_data[7])); obj.file = btoa(splitted_data[8]);
add_obj_to_database(obj); add_obj_to_database(obj);
update_chat_obj_by_uuid(obj.uuid); update_chat_obj_by_uuid(obj.uuid);
@ -827,16 +852,21 @@ update_chat = function(obj) {
//var file = atob(obj._attachments[filename]["data"]) //var file = atob(obj._attachments[filename]["data"])
db.getAttachment(obj._id, filename).then(function(data) { db.getAttachment(obj._id, filename).then(function(data) {
console.log(data)
// convert blob data to binary string
blobUtil.blobToBinaryString(data).then(function (binaryString) {
console.log(binaryString)
}).catch(function (err) {
// error
console.log(err);
binaryString = blobUtil.arrayBufferToBinaryString(data);
var file = blobUtil.arrayBufferToBinaryString(data) }).then(function(){
// converting back to blob for debugging console.log(binaryString)
// length must be equal of file size console.log(binaryString.length)
var blob = blobUtil.binaryStringToBlob(file);
console.log(blob)
var data_with_attachment = doc.timestamp + split_char + utf8.encode(doc.msg) + split_char + filename + split_char + filetype + split_char + binaryString;
var data_with_attachment = doc.msg + split_char + filename + split_char + filetype + split_char + file + split_char + doc.timestamp;
let Data = { let Data = {
command: "send_message", command: "send_message",
dxcallsign: doc.dxcallsign, dxcallsign: doc.dxcallsign,
@ -850,6 +880,7 @@ update_chat = function(obj) {
ipcRenderer.send('run-tnc-command', Data); ipcRenderer.send('run-tnc-command', Data);
}); });
});
}).catch(function(err) { }).catch(function(err) {
console.log(err); console.log(err);
}); });
@ -935,7 +966,7 @@ update_chat_obj_by_uuid = function(uuid) {
add_obj_to_database = function(obj){ add_obj_to_database = function(obj){
db.put({ db.put({
_id: obj.uuid, _id: obj.uuid,
timestamp: obj.timestamp, timestamp: parseInt(obj.timestamp),
uuid: obj.uuid, uuid: obj.uuid,
dxcallsign: obj.dxcallsign, dxcallsign: obj.dxcallsign,
dxgrid: obj.dxgrid, dxgrid: obj.dxgrid,
@ -965,3 +996,36 @@ function scrollMessagesToBottom() {
var messageBody = document.getElementById('message-container'); var messageBody = document.getElementById('message-container');
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight; messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
} }
// CRC CHECKSUMS
// https://stackoverflow.com/a/50579690
// crc32 calculation
//console.log(crc32('abc'));
//var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
var makeCRCTable = function(){
var c;
var crcTable = [];
for(var n =0; n < 256; n++){
c = n;
for(var k =0; k < 8; k++){
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
crcTable[n] = c;
}
return crcTable;
}
var crc32 = function(str) {
var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
var crc = 0 ^ (-1);
for (var i = 0; i < str.length; i++ ) {
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
};

View file

@ -1,5 +1,5 @@
const path = require('path'); const path = require('path');
const {ipcRenderer} = require('electron'); const {ipcRenderer, shell} = require('electron');
const exec = require('child_process').spawn; const exec = require('child_process').spawn;
const sock = require('./sock.js'); const sock = require('./sock.js');
const daemon = require('./daemon.js'); const daemon = require('./daemon.js');
@ -37,6 +37,87 @@ var dbfs_level_raw = 0
// WINDOW LISTENER // WINDOW LISTENER
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
// save frequency event listener
document.getElementById("saveFrequency").addEventListener("click", () => {
var freq = document.getElementById("newFrequency").value;
console.log(freq)
let Data = {
type: "set",
command: "frequency",
frequency: freq,
};
ipcRenderer.send('run-tnc-command', Data);
});
// enter button for input field
document.getElementById("newFrequency").addEventListener("keypress", function(event) {
if (event.key === "Enter") {
event.preventDefault();
document.getElementById("saveFrequency").click();
}
});
// save mode event listener
document.getElementById("saveModePKTUSB").addEventListener("click", () => {
let Data = {
type: "set",
command: "mode",
mode: "PKTUSB",
};
ipcRenderer.send('run-tnc-command', Data);
});
// save mode event listener
document.getElementById("saveModeUSB").addEventListener("click", () => {
let Data = {
type: "set",
command: "mode",
mode: "USB",
};
ipcRenderer.send('run-tnc-command', Data);
});
// save mode event listener
document.getElementById("saveModeLSB").addEventListener("click", () => {
let Data = {
type: "set",
command: "mode",
mode: "LSB",
};
ipcRenderer.send('run-tnc-command', Data);
});
// save mode event listener
document.getElementById("saveModeAM").addEventListener("click", () => {
let Data = {
type: "set",
command: "mode",
mode: "AM",
};
ipcRenderer.send('run-tnc-command', Data);
});
// save mode event listener
document.getElementById("saveModeFM").addEventListener("click", () => {
let Data = {
type: "set",
command: "mode",
mode: "FM",
};
ipcRenderer.send('run-tnc-command', Data);
});
// start stop audio recording event listener
document.getElementById("startStopRecording").addEventListener("click", () => {
let Data = {
type: "set",
command: "record_audio",
};
ipcRenderer.send('run-tnc-command', Data);
});
document.getElementById('received_files_folder').addEventListener('click', () => { document.getElementById('received_files_folder').addEventListener('click', () => {
@ -96,8 +177,8 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', ()
// hamlib settings // hamlib settings
document.getElementById('hamlib_deviceid').value = config.hamlib_deviceid; document.getElementById('hamlib_deviceid').value = config.hamlib_deviceid;
set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport) set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport)
set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port) set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed; document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed;
set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed) set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed)
@ -208,16 +289,57 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
if (config.spectrum == 'waterfall') { if (config.spectrum == 'waterfall') {
document.getElementById("waterfall-scatter-switch1").checked = true; document.getElementById("waterfall-scatter-switch1").checked = true;
document.getElementById("waterfall-scatter-switch2").checked = false; document.getElementById("waterfall-scatter-switch2").checked = false;
document.getElementById("scatter").style.visibility = 'hidden'; document.getElementById("waterfall-scatter-switch3").checked = false;
document.getElementById("waterfall").style.visibility = 'visible'; document.getElementById("waterfall").style.visibility = 'visible';
document.getElementById("waterfall").style.height = '100%'; document.getElementById("waterfall").style.height = '100%';
} else { document.getElementById("waterfall").style.display = 'block';
document.getElementById("scatter").style.height = '0px';
document.getElementById("scatter").style.visibility = 'hidden';
document.getElementById("scatter").style.display = 'none';
document.getElementById("chart").style.height = '0px';
document.getElementById("chart").style.visibility = 'hidden';
document.getElementById("chart").style.display = 'none';
} else if (config.spectrum == 'scatter'){
document.getElementById("waterfall-scatter-switch1").checked = false; document.getElementById("waterfall-scatter-switch1").checked = false;
document.getElementById("waterfall-scatter-switch2").checked = true; document.getElementById("waterfall-scatter-switch2").checked = true;
document.getElementById("scatter").style.visibility = 'visible'; document.getElementById("waterfall-scatter-switch3").checked = false;
document.getElementById("waterfall").style.visibility = 'hidden'; document.getElementById("waterfall").style.visibility = 'hidden';
document.getElementById("waterfall").style.height = '0px'; document.getElementById("waterfall").style.height = '0px';
document.getElementById("waterfall").style.display = 'none';
document.getElementById("scatter").style.height = '100%';
document.getElementById("scatter").style.visibility = 'visible';
document.getElementById("scatter").style.display = 'block';
document.getElementById("chart").style.visibility = 'hidden';
document.getElementById("chart").style.height = '0px';
document.getElementById("chart").style.display = 'none';
} else {
document.getElementById("waterfall-scatter-switch1").checked = false;
document.getElementById("waterfall-scatter-switch2").checked = false;
document.getElementById("waterfall-scatter-switch3").checked = true;
document.getElementById("waterfall").style.visibility = 'hidden';
document.getElementById("waterfall").style.height = '0px';
document.getElementById("waterfall").style.display = 'none';
document.getElementById("scatter").style.height = '0px';
document.getElementById("scatter").style.visibility = 'hidden';
document.getElementById("scatter").style.display = 'none';
document.getElementById("chart").style.visibility = 'visible';
document.getElementById("chart").style.height = '100%';
document.getElementById("chart").style.display = 'block';
} }
// radio control element // radio control element
@ -291,10 +413,13 @@ set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_ha
// Create spectrum object on canvas with ID "waterfall" // Create spectrum object on canvas with ID "waterfall"
global.spectrum = new Spectrum( global.spectrum = new Spectrum(
"waterfall", { "waterfall", {
spectrumPercent: 0 spectrumPercent: 0,
wf_rows: 192 //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container
}); });
//Set waterfalltheme
document.getElementById("wftheme_selector").value = config.wftheme;
spectrum.setColorMap(config.wftheme);
// on click radio control toggle view // on click radio control toggle view
// disabled // disabled
@ -690,21 +815,55 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
// on click waterfall scatter toggle view // on click waterfall scatter toggle view
// waterfall // waterfall
document.getElementById("waterfall-scatter-switch1").addEventListener("click", () => { document.getElementById("waterfall-scatter-switch1").addEventListener("click", () => {
document.getElementById("chart").style.visibility = 'hidden';
document.getElementById("chart").style.display = 'none';
document.getElementById("chart").style.height = '0px';
document.getElementById("scatter").style.height = '0px';
document.getElementById("scatter").style.display = 'none';
document.getElementById("scatter").style.visibility = 'hidden'; document.getElementById("scatter").style.visibility = 'hidden';
document.getElementById("waterfall").style.display = 'block';
document.getElementById("waterfall").style.visibility = 'visible'; document.getElementById("waterfall").style.visibility = 'visible';
document.getElementById("waterfall").style.height = '100%'; document.getElementById("waterfall").style.height = '100%';
config.spectrum = 'waterfall'; config.spectrum = 'waterfall';
fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}); });
// scatter // scatter
document.getElementById("waterfall-scatter-switch2").addEventListener("click", () => { document.getElementById("waterfall-scatter-switch2").addEventListener("click", () => {
document.getElementById("scatter").style.display = 'block';
document.getElementById("scatter").style.visibility = 'visible'; document.getElementById("scatter").style.visibility = 'visible';
document.getElementById("scatter").style.height = '100%';
document.getElementById("waterfall").style.visibility = 'hidden'; document.getElementById("waterfall").style.visibility = 'hidden';
document.getElementById("waterfall").style.height = '0px'; document.getElementById("waterfall").style.height = '0px';
document.getElementById("waterfall").style.display = 'none';
document.getElementById("chart").style.visibility = 'hidden';
document.getElementById("chart").style.height = '0px';
document.getElementById("chart").style.display = 'none';
config.spectrum = 'scatter'; config.spectrum = 'scatter';
fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
}); });
// chart
document.getElementById("waterfall-scatter-switch3").addEventListener("click", () => {
document.getElementById("waterfall").style.visibility = 'hidden';
document.getElementById("waterfall").style.height = '0px';
document.getElementById("waterfall").style.display = 'none';
document.getElementById("scatter").style.height = '0px';
document.getElementById("scatter").style.visibility = 'hidden';
document.getElementById("scatter").style.display = 'none';
document.getElementById("chart").style.height = '100%';
document.getElementById("chart").style.display = 'block';
document.getElementById("chart").style.visibility = 'visible';
config.spectrum = 'chart';
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
});
// on click remote tnc toggle view // on click remote tnc toggle view
@ -798,12 +957,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
ipcRenderer.send('run-tnc-command', Data); ipcRenderer.send('run-tnc-command', Data);
}); });
// saveMyCall button clicked // saveMyCall button clicked
document.getElementById("saveMyCall").addEventListener("click", () => { document.getElementById("myCall").addEventListener("input", () => {
callsign = document.getElementById("myCall").value; callsign = document.getElementById("myCall").value;
ssid = document.getElementById("myCallSSID").value; ssid = document.getElementById("myCallSSID").value;
callsign_ssid = callsign.toUpperCase() + '-' + ssid; callsign_ssid = callsign.toUpperCase() + '-' + ssid;
config.mycall = callsign_ssid; config.mycall = callsign_ssid;
// split document title by looking for Call then split and update it // split document title by looking for Call then split and update it
var documentTitle = document.title.split('Call:') var documentTitle = document.title.split('Call:')
document.title = documentTitle[0] + 'Call: ' + callsign_ssid; document.title = documentTitle[0] + 'Call: ' + callsign_ssid;
@ -813,7 +971,7 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
}); });
// saveMyGrid button clicked // saveMyGrid button clicked
document.getElementById("saveMyGrid").addEventListener("click", () => { document.getElementById("myGrid").addEventListener("input", () => {
grid = document.getElementById("myGrid").value; grid = document.getElementById("myGrid").value;
config.mygrid = grid; config.mygrid = grid;
fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
@ -967,6 +1125,14 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
}); });
// Waterfall theme selector changed
document.getElementById("wftheme_selector").addEventListener("change", () => {
var wftheme = document.getElementById("wftheme_selector").value;
spectrum.setColorMap(wftheme);
config.wftheme = wftheme;
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
});
// Update channel selector clicked // Update channel selector clicked
document.getElementById("update_channel_selector").addEventListener("click", () => { document.getElementById("update_channel_selector").addEventListener("click", () => {
config.update_channel = document.getElementById("update_channel_selector").value; config.update_channel = document.getElementById("update_channel_selector").value;
@ -996,6 +1162,11 @@ document.getElementById('hamlib_rigctld_stop').addEventListener('click', () => {
sock.stopBeacon(); sock.stopBeacon();
}); });
// Explorer button clicked
document.getElementById("openExplorer").addEventListener("click", () => {
shell.openExternal('https://explorer.freedata.app/?myCall=' + document.getElementById("myCall").value);
});
// startTNC button clicked // startTNC button clicked
document.getElementById("startTNC").addEventListener("click", () => { document.getElementById("startTNC").addEventListener("click", () => {
@ -1314,28 +1485,24 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
} }
if (typeof(arg.mycallsign) !== 'undefined') {
// TOE TIME OF EXECUTION --> How many time needs a command to be executed until data arrives // split document title by looking for Call then split and update it
// deactivated this feature, beacuse its useless at this time. maybe it is getting more interesting, if we are working via network var documentTitle = document.title.split('Call:')
// but for this we need to find a nice place for this on the screen document.title = documentTitle[0] + 'Call: ' + arg.mycallsign;
/* }
if (typeof(arg.toe) == 'undefined') {
var toe = 0 // update mygrid information with data from tnc
} else { if (typeof(arg.mygrid) !== 'undefined') {
var toe = arg.toe document.getElementById("myGrid").value = arg.mygrid;
} }
document.getElementById("toe").innerHTML = toe + ' ms'
*/
// DATA STATE // DATA STATE
global.rxBufferLengthTnc = arg.rx_buffer_length global.rxBufferLengthTnc = arg.rx_buffer_length
// SCATTER DIAGRAM PLOTTING
//global.myChart.destroy();
//console.log(arg.scatter.length) // START OF SCATTER CHART
const config = { const scatterConfig = {
plugins: { plugins: {
legend: { legend: {
display: false, display: false,
@ -1389,16 +1556,12 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
} }
} }
} }
var scatterData = arg.scatter
var newScatterData = {
var data = arg.scatter
var newdata = {
datasets: [{ datasets: [{
//label: 'constellation diagram', //label: 'constellation diagram',
data: data, data: scatterData,
options: config, options: scatterConfig,
backgroundColor: 'rgb(255, 99, 132)' backgroundColor: 'rgb(255, 99, 132)'
}], }],
}; };
@ -1408,24 +1571,121 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
} else { } else {
var scatterSize = arg.scatter.length; var scatterSize = arg.scatter.length;
} }
if (global.data != newdata && scatterSize > 0) {
try {
global.myChart.destroy();
} catch (e) {
// myChart not yet created
console.log(e);
}
global.data = newdata; if (global.scatterData != newScatterData && scatterSize > 0) {
global.scatterData = newScatterData;
if (typeof(global.scatterChart) == 'undefined') {
var ctx = document.getElementById('scatter').getContext('2d'); var scatterCtx = document.getElementById('scatter').getContext('2d');
global.myChart = new Chart(ctx, { global.scatterChart = new Chart(scatterCtx, {
type: 'scatter', type: 'scatter',
data: global.data, data: global.scatterData,
options: config options: scatterConfig
}); });
} else {
global.scatterChart.data = global.scatterData;
global.scatterChart.update();
} }
}
// END OF SCATTER CHART
// START OF SPEED CHART
var speedDataTime = []
if (typeof(arg.speed_list) == 'undefined') {
var speed_listSize = 0;
} else {
var speed_listSize = arg.speed_list.length;
}
for (var i=0; i < speed_listSize; i++) {
var timestamp = arg.speed_list[i].timestamp * 1000
var h = new Date(timestamp).getHours();
var m = new Date(timestamp).getMinutes();
var s = new Date(timestamp).getSeconds();
var time = h + ':' + m + ':' + s;
speedDataTime.push(time)
}
var speedDataBpm = []
for (var i=0; i < speed_listSize; i++) {
speedDataBpm.push(arg.speed_list[i].bpm)
}
var speedDataSnr = []
for (var i=0; i < speed_listSize; i++) {
speedDataSnr.push(arg.speed_list[i].snr)
}
var speedChartConfig = {
type: 'line',
};
var newSpeedData = {
labels: speedDataTime,
datasets: [
{
type: 'line',
label: 'SNR[dB]',
data: speedDataSnr,
borderColor: 'rgb(255, 99, 132, 1.0)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
order: 1,
yAxisID: 'SNR',
},
{
type: 'bar',
label: 'Speed[bpm]',
data: speedDataBpm,
borderColor: 'rgb(120, 100, 120, 1.0)',
backgroundColor: 'rgba(120, 100, 120, 0.2)',
order: 0,
yAxisID: 'SPEED',
}
],
};
var speedChartOptions = {
responsive: true,
animations: true,
cubicInterpolationMode: 'monotone',
tension: 0.4,
scales: {
SNR:{
type: 'linear',
ticks: { beginAtZero: true, color: 'rgb(255, 99, 132)' },
position: 'right',
},
SPEED :{
type: 'linear',
ticks: { beginAtZero: true, color: 'rgb(120, 100, 120)' },
position: 'left',
grid: {
drawOnChartArea: false, // only want the grid lines for one axis to show up
},
},
x: { ticks: { beginAtZero: true } },
}
}
if (typeof(global.speedChart) == 'undefined') {
var speedCtx = document.getElementById('chart').getContext('2d');
global.speedChart = new Chart(speedCtx, {
data: newSpeedData,
options: speedChartOptions
});
} else {
if(speedDataSnr.length > 0){
global.speedChart.data = newSpeedData;
global.speedChart.update();
}
}
// END OF SPEED CHART
// PTT STATE // PTT STATE
if (arg.ptt_state == 'True') { if (arg.ptt_state == 'True') {
@ -1436,6 +1696,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
document.getElementById("ptt_state").className = "btn btn-sm btn-secondary"; document.getElementById("ptt_state").className = "btn btn-sm btn-secondary";
} }
// AUDIO RECORDING
if (arg.audio_recording == 'True') {
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
document.getElementById("startStopRecording").innerHTML = "Stop Rec"
} else if (arg.ptt_state == 'False') {
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
document.getElementById("startStopRecording").innerHTML = "Start Rec"
} else {
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
document.getElementById("startStopRecording").innerHTML = "Start Rec"
}
// CHANNEL BUSY STATE // CHANNEL BUSY STATE
if (arg.channel_busy == 'True') { if (arg.channel_busy == 'True') {
document.getElementById("channel_busy").className = "btn btn-sm btn-danger"; document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
@ -1532,7 +1808,10 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
} }
// SET FREQUENCY // SET FREQUENCY
document.getElementById("frequency").innerHTML = arg.frequency; // https://stackoverflow.com/a/2901298
var freq = arg.frequency.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
document.getElementById("frequency").innerHTML = freq;
//document.getElementById("newFrequency").value = arg.frequency;
// SET MODE // SET MODE
document.getElementById("mode").innerHTML = arg.mode; document.getElementById("mode").innerHTML = arg.mode;
@ -1556,6 +1835,29 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
} }
document.getElementById("bytes_per_min_compressed").innerHTML = arq_bytes_per_minute_compressed; document.getElementById("bytes_per_min_compressed").innerHTML = arq_bytes_per_minute_compressed;
// SET TIME LEFT UNTIL FINIHED
if (typeof(arg.arq_seconds_until_finish) == 'undefined') {
var time_left = 0;
} else {
var arq_seconds_until_finish = arg.arq_seconds_until_finish
var hours = Math.floor(arq_seconds_until_finish / 3600);
var minutes = Math.floor((arq_seconds_until_finish % 3600) / 60 );
var seconds = arq_seconds_until_finish % 60;
if(hours < 0) {
hours = 0;
}
if(minutes < 0) {
minutes = 0;
}
if(seconds < 0) {
seconds = 0;
}
var time_left = "time left: ~ "+ minutes + "min" + " " + seconds + "s";
}
document.getElementById("transmission_timeleft").innerHTML = time_left;
// SET SPEED LEVEL // SET SPEED LEVEL
@ -1577,6 +1879,7 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
// SET TOTAL BYTES // SET TOTAL BYTES
if (typeof(arg.total_bytes) == 'undefined') { if (typeof(arg.total_bytes) == 'undefined') {
var total_bytes = 0; var total_bytes = 0;
@ -2070,10 +2373,20 @@ ipcRenderer.on('run-tnc-command', (event, arg) => {
if (arg.command == 'set_tx_audio_level') { if (arg.command == 'set_tx_audio_level') {
sock.setTxAudioLevel(arg.tx_audio_level); sock.setTxAudioLevel(arg.tx_audio_level);
} }
if (arg.command == 'record_audio') {
sock.record_audio();
}
if (arg.command == 'send_test_frame') { if (arg.command == 'send_test_frame') {
sock.sendTestFrame(); sock.sendTestFrame();
} }
if (arg.command == 'frequency') {
sock.set_frequency(arg.frequency);
}
if (arg.command == 'mode') {
sock.set_mode(arg.mode);
}
}); });

View file

@ -19,7 +19,7 @@ var client = new net.Socket();
var socketchunk = ''; // Current message, per connection. var socketchunk = ''; // Current message, per connection.
// split character // split character
const split_char = '\0;' const split_char = '\0;\1;'
// globals for getting new data only if available so we are saving bandwidth // globals for getting new data only if available so we are saving bandwidth
var rxBufferLengthTnc = 0 var rxBufferLengthTnc = 0
@ -195,6 +195,8 @@ client.on('data', function(socketdata) {
rxMsgBufferLengthTnc = data['rx_msg_buffer_length'] rxMsgBufferLengthTnc = data['rx_msg_buffer_length']
let Data = { let Data = {
mycallsign: data['mycallsign'],
mygrid: data['mygrid'],
ptt_state: data['ptt_state'], ptt_state: data['ptt_state'],
busy_state: data['tnc_state'], busy_state: data['tnc_state'],
arq_state: data['arq_state'], arq_state: data['arq_state'],
@ -220,12 +222,17 @@ client.on('data', function(socketdata) {
arq_rx_n_current_arq_frame: data['arq_rx_n_current_arq_frame'], arq_rx_n_current_arq_frame: data['arq_rx_n_current_arq_frame'],
arq_n_arq_frames_per_data_frame: data['arq_n_arq_frames_per_data_frame'], arq_n_arq_frames_per_data_frame: data['arq_n_arq_frames_per_data_frame'],
arq_bytes_per_minute: data['arq_bytes_per_minute'], arq_bytes_per_minute: data['arq_bytes_per_minute'],
arq_seconds_until_finish: data['arq_seconds_until_finish'],
arq_compression_factor: data['arq_compression_factor'], arq_compression_factor: data['arq_compression_factor'],
total_bytes: data['total_bytes'], total_bytes: data['total_bytes'],
arq_transmission_percent: data['arq_transmission_percent'], arq_transmission_percent: data['arq_transmission_percent'],
stations: data['stations'], stations: data['stations'],
beacon_state: data['beacon_state'], beacon_state: data['beacon_state'],
hamlib_status: data['hamlib_status'], hamlib_status: data['hamlib_status'],
listen: data['listen'],
audio_recording: data['audio_recording'],
speed_list: data['speed_list'],
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
}; };
ipcRenderer.send('request-update-tnc-state', Data); ipcRenderer.send('request-update-tnc-state', Data);
@ -514,20 +521,25 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data,
// Send Message // Send Message
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) { exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) {
socketLog.info(data) //socketLog.info(data)
// Disabled this here
// convert message to plain utf8 because of unicode emojis // convert message to plain utf8 because of unicode emojis
data = utf8.encode(data) //data = utf8.encode(data)
socketLog.info(data)
//socketLog.info(data)
var datatype = "m" var datatype = "m"
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
socketLog.info(data) //socketLog.info(data)
console.log(data)
console.log("CHECKSUM" + checksum)
//socketLog.info(btoa(data))
socketLog.info(btoa(data))
data = btoa(data) data = btoa(data)
//command = '{"type" : "arq", "command" : "send_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}' //command = '{"type" : "arq", "command" : "send_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}'
command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}' command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}'
socketLog.info(command) socketLog.info(command)
@ -583,7 +595,24 @@ exports.sendTestFrame = function() {
writeTncCommand(command) writeTncCommand(command)
} }
// RECORD AUDIO
exports.record_audio = function() {
command = '{"type" : "set", "command" : "record_audio"}'
writeTncCommand(command)
}
// SET FREQUENCY
exports.set_frequency = function(frequency) {
command = '{"type" : "set", "command" : "frequency", "frequency": '+ frequency +'}'
writeTncCommand(command)
}
// SET MODE
exports.set_mode = function(mode) {
command = '{"type" : "set", "command" : "mode", "mode": "'+ mode +'"}'
console.log(command)
writeTncCommand(command)
}
ipcRenderer.on('action-update-tnc-ip', (event, arg) => { ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
client.destroy(); client.destroy();
@ -602,3 +631,11 @@ ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
connectTNC(); connectTNC();
}); });
// https://stackoverflow.com/a/50579690
// crc32 calculation
//console.log(crc32('abc'));
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};

View file

@ -44,6 +44,7 @@
<button class="btn btn-sm btn-danger" id="stop_transmission_connection" type="button"> <i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i> STOP </button> <button class="btn btn-sm btn-danger" id="stop_transmission_connection" type="button"> <i class="bi bi-x-octagon-fill" style="font-size: 1rem; color: white;"></i> STOP </button>
</div> </div>
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<button class="btn btn-sm btn-primary me-4 position-relative" id="openExplorer" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View explorer map"> <strong>Explorer</strong> <i class="bi bi-pin-map-fill" style="font-size: 1rem; color: white;"></i></button>
<button class="btn btn-sm btn-primary me-4 position-relative" id="openRFChat" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Open the HF chat module. This is currently just a test and not finished, yet!"> <strong>RF Chat</strong> <i class="bi bi-chat-left-text-fill" style="font-size: 1rem; color: white;"></i> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">.</span> </button> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View the received files. This is currently under development!"> <button class="btn btn-sm btn-primary me-4 position-relative" id="openRFChat" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="Open the HF chat module. This is currently just a test and not finished, yet!"> <strong>RF Chat</strong> <i class="bi bi-chat-left-text-fill" style="font-size: 1rem; color: white;"></i> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">.</span> </button> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="View the received files. This is currently under development!">
<!-- <!--
@ -731,7 +732,6 @@
<option value="14">14</option> <option value="14">14</option>
<option value="15">15</option> <option value="15">15</option>
</select> </select>
<button class="btn btn-sm btn-success" id="saveMyCall" type="button"> <i class="bi bi-check2" style="font-size: 1rem; color: white;"></i> </button>
</div> </div>
</div> </div>
<div class="col-md-auto"> <div class="col-md-auto">
@ -739,7 +739,6 @@
<i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i> <i class="bi bi-house-fill" style="font-size: 1rem; color: black;"></i>
</span> </span>
<input type="text" class="form-control mr-1" style="max-width: 6rem" placeholder="locator" id="myGrid" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon"> <input type="text" class="form-control mr-1" style="max-width: 6rem" placeholder="locator" id="myGrid" maxlength="6" aria-label="Input group" aria-describedby="btnGroupAddon">
<button class="btn btn-sm btn-success" id="saveMyGrid" type="button"> <i class="bi bi-check2" style="font-size: 1rem; color: white;"></i> </button>
</div> </div>
</div> </div>
</div> </div>
@ -770,6 +769,8 @@
<div class="card text-dark mb-1"> <div class="card text-dark mb-1">
<div class="card-header p-1"><i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i> <strong>AUDIO LEVEL</strong> <div class="card-header p-1"><i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i> <strong>AUDIO LEVEL</strong>
<button type="button" id="audioModalButton" data-bs-toggle="modal" data-bs-target="#audioModal" class="btn btn-sm btn-secondary">Tune</button> <button type="button" id="audioModalButton" data-bs-toggle="modal" data-bs-target="#audioModal" class="btn btn-sm btn-secondary">Tune</button>
<button type="button" id="startStopRecording" class="btn btn-sm btn-danger">Rec</button>
</div> </div>
<div class="card-body p-2"> <div class="card-body p-2">
<div class="progress mb-0" style="height: 15px;"> <div class="progress mb-0" style="height: 15px;">
@ -837,37 +838,19 @@
<div class="card-header p-1"> <div class="card-header p-1">
<div class="btn-group btn-group-sm" role="group" aria-label="waterfall-scatter-switch toggle button group"> <div class="btn-group btn-group-sm" role="group" aria-label="waterfall-scatter-switch toggle button group">
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch1" autocomplete="off" checked> <input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch1" autocomplete="off" checked>
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch1"><strong>WATERFALL</strong> </label> <label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch1"><strong><i class="bi bi-water"></i></strong> </label>
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch2" autocomplete="off"> <input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch2" autocomplete="off">
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch2"><strong>SCATTER</strong> </label> <label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch2"><strong><i class="bi bi-border-outer"></i></strong> </label>
<input type="radio" class="btn-check" name="waterfall-scatter-switch" id="waterfall-scatter-switch3" autocomplete="off">
<label class="btn btn-sm btn-outline-secondary" for="waterfall-scatter-switch3"><strong><i class="bi bi-graph-up-arrow"></i></strong> </label>
</div> </div>
<button class="btn btn-sm btn-secondary" id="channel_busy" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>">busy</button> <button class="btn btn-sm btn-secondary" id="channel_busy" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>">busy</button>
</div> </div>
<div class="card-body p-1" style="height: 200px"> <div class="card-body p-1" style="height: 200px">
<!-- TEST FOR WATERFALL OVERLAY
<div class="opacity-100 w-100 h-100 p-0 m-0 position-absolute" style="height: 190px;z-index: 10">
<div class="row m-0 p-0 w-100 h-100">
<div class="col m-0 p-0 col-3 ">
-
</div>
<div class="col border border-danger m-0 p-0 col-2">
1800Hz
</div>
<div class="col border border-danger m-0 p-0" style="width: 190px;">
500Hz
</div>
<div class="col border border-danger m-0 p-0 col-2">
-
</div>
<div class="col m-0 p-0 col-3">
-
</div>
</div>
</div>
-->
<!--278px--> <!--278px-->
<canvas id="waterfall" style="position: relative; z-index: 2;"></canvas> <canvas id="waterfall" style="position: relative; z-index: 2; transform: translateZ(0);"></canvas>
<canvas id="scatter" style="position: relative; z-index: 1;"></canvas> <canvas id="scatter" style="position: relative; z-index: 1; transform: translateZ(0);"></canvas>
<canvas id="chart" style="position: relative; z-index: 1; transform: translateZ(0);"></canvas>
</div> </div>
</div> </div>
</div> </div>
@ -1066,28 +1049,64 @@
<nav class="navbar fixed-bottom navbar-light bg-light"> <nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid"> <div class="container-fluid">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group btn-group-sm me-2" role="group"> <div class="btn-group btn-group-sm me-1" role="group">
<button class="btn btn-sm btn-secondary" id="ptt_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="PTT state:<strong class='text-success'>RECEIVING</strong> / <strong class='text-danger'>TRANSMITTING</strong>"> <i class="bi bi-broadcast-pin" style="font-size: 0.8rem; color: white;"></i> </button> <button class="btn btn-sm btn-secondary" id="ptt_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="PTT state:<strong class='text-success'>RECEIVING</strong> / <strong class='text-danger'>TRANSMITTING</strong>"> <i class="bi bi-broadcast-pin" style="font-size: 0.8rem; color: white;"></i> </button>
</div> </div>
<div class="btn-group btn-group-sm me-2" role="group"> <div class="btn-group btn-group-sm me-1" role="group">
<button class="btn btn-sm btn-secondary" id="busy_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="TNC busy state: <strong class='text-success'>IDLE</strong> / <strong class='text-danger'>BUSY</strong>"> <i class="bi bi-cpu" style="font-size: 0.8rem; color: white;"></i> </button> <button class="btn btn-sm btn-secondary" id="busy_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="TNC busy state: <strong class='text-success'>IDLE</strong> / <strong class='text-danger'>BUSY</strong>"> <i class="bi bi-cpu" style="font-size: 0.8rem; color: white;"></i> </button>
</div> </div>
<div class="btn-group btn-group-sm me-2" role="group"> <div class="btn-group btn-group-sm me-1" role="group">
<button class="btn btn-sm btn-secondary" id="arq_session" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="ARQ SESSION state: <strong class='text-warning'>OPEN</strong>"> <i class="bi bi-arrow-left-right" style="font-size: 0.8rem; color: white;"></i> </button> <button class="btn btn-sm btn-secondary" id="arq_session" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="ARQ SESSION state: <strong class='text-warning'>OPEN</strong>"> <i class="bi bi-arrow-left-right" style="font-size: 0.8rem; color: white;"></i> </button>
</div> </div>
<div class="btn-group btn-group-sm me-2" role="group"> <div class="btn-group btn-group-sm me-1" role="group">
<button class="btn btn-sm btn-secondary" id="arq_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="DATA-CHANNEL state: <strong class='text-warning'>OPEN</strong>"> <i class="bi bi-file-earmark-binary" style="font-size: 0.8rem; color: white;"></i> </button> <button class="btn btn-sm btn-secondary" id="arq_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="DATA-CHANNEL state: <strong class='text-warning'>OPEN</strong>"> <i class="bi bi-file-earmark-binary" style="font-size: 0.8rem; color: white;"></i> </button>
</div> </div>
<div class="btn-group btn-group-sm me-2" role="group"> <div class="btn-group btn-group-sm me-1" role="group">
<button class="btn btn-sm btn-secondary" id="rigctld_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"> <i class="bi bi-usb-symbol" style="font-size: 0.8rem; color: white;"></i> </button> <button class="btn btn-sm btn-secondary" id="rigctld_state" type="button" data-bs-placement="top" data-bs-toggle="tooltip" data-bs-html="true" title="rigctld state: <strong class='text-success'>CONNECTED</strong> / <strong class='text-secondary'>UNKNOWN</strong>"> <i class="bi bi-usb-symbol" style="font-size: 0.8rem; color: white;"></i> </button>
</div> </div>
</div> </div>
<div class="container-fluid p-0" style="width:15rem"> <div class="container-fluid p-0" style="width:20rem">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<!--<span class="input-group-text" id="basic-addon1"><strong>Freq</strong></span>--><span class="input-group-text" id="frequency">---</span>
<!--<span class="input-group-text" id="basic-addon1"><strong>Mode</strong></span>--><span class="input-group-text" id="mode">---</span> <div class="btn-group dropup me-1">
<!--<span class="input-group-text" id="basic-addon1"><strong>BW</strong></span>--><span class="input-group-text" id="bandwidth">---</span> </div> <button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="frequency">
---
</button>
<form class="dropdown-menu p-2">
<div class="input-group input-group-sm">
<input type="text" class="form-control" style="max-width: 6rem;" placeholder="7063000" pattern="[0-9]*" id="newFrequency" maxlength="11" aria-label="Input group" aria-describedby="btnGroupAddon">
<span class="input-group-text">Hz</span>
<button class="btn btn-sm btn-success" id="saveFrequency" type="button" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="save frequency"> <i class="bi bi-check-lg" style="font-size: 0.8rem; color: white;"></i> </button>
</div>
</form>
</div>
<div class="btn-group dropup me-1">
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="mode">
---
</button>
<form class="dropdown-menu p-2">
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set FM" id="saveModeFM">FM</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set AM" type="button" id="saveModeAM">AM</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set LSB" type="button" id="saveModeLSB">LSB</button>
<hr>
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set USB" type="button" id="saveModeUSB">USB</button>
<button type="button" class="btn btn-sm btn-secondary" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="set PKTUSB" type="button" id="saveModePKTUSB">PKTUSB</button>
</form>
</div>
<div class="btn-group dropup">
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="bandwidth">
---
</button>
<form class="dropdown-menu p-2">
<div class="input-group input-group-sm">
...soon...
</div>
</form>
</div>
</div>
</div> </div>
<div class="container-fluid p-0" style="width:12rem"> <div class="container-fluid p-0" style="width:12rem">
<div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><i class="bi bi-speedometer2" style="font-size: 1rem; color: black;"></i></span> <span class="input-group-text" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="actual speed level"> <div class="input-group input-group-sm"> <span class="input-group-text" id="basic-addon1"><i class="bi bi-speedometer2" style="font-size: 1rem; color: black;"></i></span> <span class="input-group-text" data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-html="false" title="actual speed level">
@ -1100,6 +1119,7 @@
<div class="progress" style="height: 30px;"> <div class="progress" style="height: 30px;">
<div class="progress-bar progress-bar-striped bg-primary" id="transmission_progress" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> <div class="progress-bar progress-bar-striped bg-primary" id="transmission_progress" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
<!--<p class="justify-content-center d-flex position-absolute w-100">PROGRESS</p>--> <!--<p class="justify-content-center d-flex position-absolute w-100">PROGRESS</p>-->
<p class="justify-content-center mt-2 d-flex position-absolute w-100" id="transmission_timeleft">---</p>
</div> </div>
</div> </div>
</div> </div>
@ -1107,8 +1127,8 @@
<!-- bootstrap --> <!-- bootstrap -->
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<!-- chart.js --> <!-- chart.js -->
<script src="../node_modules/chart.js/dist/chart.min.js"></script> <script src="../node_modules/chart.js/dist/chart.umd.js"></script>
<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script> <!--<script src="../node_modules/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js"></script>-->
<!--<script src="../ui.js"></script>--> <!--<script src="../ui.js"></script>-->
<!-- WATERFALL --> <!-- WATERFALL -->
<script src="waterfall/colormap.js"></script> <script src="waterfall/colormap.js"></script>
@ -1172,6 +1192,17 @@
<option value="zephyr">Zephyr</option> <option value="zephyr">Zephyr</option>
</select> </select>
</div> </div>
<div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Waterfall Theme</span>
<select class="form-select form-select-sm w-50" id="wftheme_selector">
<option value="2">Default</option>
<option value="0">Turbo</option>
<option value="1">Fosphor</option>
<option value="3">Inferno</option>
<option value="4">Magma</option>
<option value="5">Jet</option>
<option value="6">Binary</option>
</select>
</div>
<div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Update channel</span> <div class="input-group input-group-sm mb-1"> <span class="input-group-text w-50" id="basic-addon1">Update channel</span>
<select class="form-select form-select-sm w-50" id="update_channel_selector"> <select class="form-select form-select-sm w-50" id="update_channel_selector">
<option value="latest">stable</option> <option value="latest">stable</option>

View file

@ -8,6 +8,7 @@ body {
/*Progress bars with centered text*/ /*Progress bars with centered text*/
.progress { .progress {
position: relative; position: relative;
transform: translateZ(0);
} }
.progress span { .progress span {

View file

@ -68,7 +68,8 @@ Spectrum.prototype.drawFFT = function(bins) {
this.ctx.stroke(); this.ctx.stroke();
} }
Spectrum.prototype.drawSpectrum = function(bins) { //Spectrum.prototype.drawSpectrum = function(bins) {
Spectrum.prototype.drawSpectrum = function() {
var width = this.ctx.canvas.width; var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height; var height = this.ctx.canvas.height;
@ -88,33 +89,24 @@ Spectrum.prototype.drawSpectrum = function(bins) {
this.ctx_wf.stroke() this.ctx_wf.stroke()
*/ */
// 586Hz LINES // 586Hz and 1700Hz LINES
var bandwidth = 568; var linePositionLow = 121.6; //150 - bandwith/20
var linePositionLow = 150 - ((bandwidth/2)/10); var linePositionHigh = 178.4; //150 + bandwidth/20
var linePositionHigh = 150 + ((bandwidth/2)/10); var linePositionLow2 = 65; //150 - bandwith/20
var linePositionHigh2 = 235; //150 + bandwith/20
this.ctx_wf.beginPath(); this.ctx_wf.beginPath();
this.ctx_wf.moveTo(linePositionLow,0); this.ctx_wf.moveTo(linePositionLow,0);
this.ctx_wf.lineTo(linePositionLow, height); this.ctx_wf.lineTo(linePositionLow, height);
this.ctx_wf.moveTo(linePositionHigh,0); this.ctx_wf.moveTo(linePositionHigh,0);
this.ctx_wf.lineTo(linePositionHigh, height); this.ctx_wf.lineTo(linePositionHigh, height);
this.ctx_wf.moveTo(linePositionLow2,0);
this.ctx_wf.lineTo(linePositionLow2, height);
this.ctx_wf.moveTo(linePositionHigh2,0);
this.ctx_wf.lineTo(linePositionHigh2, height);
this.ctx_wf.lineWidth = 1; this.ctx_wf.lineWidth = 1;
this.ctx_wf.strokeStyle = '#C3C3C3'; this.ctx_wf.strokeStyle = '#C3C3C3';
this.ctx_wf.stroke() this.ctx_wf.stroke()
// 1700Hz LINES
var bandwidth = 1700;
var linePositionLow = 150 - ((bandwidth/2)/10);
var linePositionHigh = 150 + ((bandwidth/2)/10);
this.ctx_wf.beginPath();
this.ctx_wf.moveTo(linePositionLow,0);
this.ctx_wf.lineTo(linePositionLow, height);
this.ctx_wf.moveTo(linePositionHigh,0);
this.ctx_wf.lineTo(linePositionHigh, height);
this.ctx_wf.lineWidth = 1;
this.ctx_wf.strokeStyle = '#C3C3C3';
this.ctx_wf.stroke()
// ---- END OF MODIFICATION ------ // ---- END OF MODIFICATION ------
@ -122,6 +114,9 @@ Spectrum.prototype.drawSpectrum = function(bins) {
this.ctx.fillStyle = "white"; this.ctx.fillStyle = "white";
this.ctx.fillRect(0, 0, width, height); this.ctx.fillRect(0, 0, width, height);
//Commenting out the remainder of this code, it's not needed and unused as of 6.9.11 and saves three if statements
return;
/*
// FFT averaging // FFT averaging
if (this.averaging > 0) { if (this.averaging > 0) {
if (!this.binsAverage || this.binsAverage.length != bins.length) { if (!this.binsAverage || this.binsAverage.length != bins.length) {
@ -174,6 +169,12 @@ Spectrum.prototype.drawSpectrum = function(bins) {
// Copy axes from offscreen canvas // Copy axes from offscreen canvas
this.ctx.drawImage(this.ctx_axes.canvas, 0, 0); this.ctx.drawImage(this.ctx_axes.canvas, 0, 0);
*/
}
//Allow setting colormap
Spectrum.prototype.setColorMap = function(index) {
this.colormap = colormaps[index];
} }
Spectrum.prototype.updateAxes = function() { Spectrum.prototype.updateAxes = function() {
@ -242,7 +243,8 @@ Spectrum.prototype.addData = function(data) {
this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height); this.ctx_wf.fillRect(0, 0, this.wf.width, this.wf.height);
this.imagedata = this.ctx_wf.createImageData(data.length, 1); this.imagedata = this.ctx_wf.createImageData(data.length, 1);
} }
this.drawSpectrum(data); //this.drawSpectrum(data);
this.drawSpectrum();
this.addWaterfallRow(data); this.addWaterfallRow(data);
this.resize(); this.resize();
} }

View file

@ -7,6 +7,7 @@ sounddevice
structlog structlog
ujson ujson
requests requests
chardet
colorama colorama
ordered-set ordered-set
nuitka nuitka
@ -17,9 +18,9 @@ autopep8
black black
isort isort
pycodestyle pycodestyle
pyinstaller
pytest pytest
pytest-cov pytest-cov
pytest-cover pytest-cover
pytest-coverage pytest-coverage
pytest-rerunfailures pytest-rerunfailures
pick

View file

@ -93,7 +93,7 @@ def t_create_session_close_old(mycall: str, dxcall: str) -> bytearray:
return t_create_frame(223, mycall, dxcall) return t_create_frame(223, mycall, dxcall)
def t_create_session_close(session_id: bytes) -> bytearray: def t_create_session_close(session_id: bytes, dxcall: str) -> bytearray:
""" """
Generate the session_close frame. Generate the session_close frame.
@ -102,10 +102,16 @@ def t_create_session_close(session_id: bytes) -> bytearray:
:return: Bytearray of the requested frame :return: Bytearray of the requested frame
:rtype: bytearray :rtype: bytearray
""" """
dxcallsign_bytes = helpers.callsign_to_bytes(dxcall)
dxcallsign = helpers.bytes_to_callsign(dxcallsign_bytes)
dxcallsign_crc = helpers.get_crc_24(dxcallsign)
# return t_create_frame(223, mycall, dxcall) # return t_create_frame(223, mycall, dxcall)
frame = bytearray(14) frame = bytearray(14)
frame[:1] = bytes([223]) frame[:1] = bytes([223])
frame[1:2] = session_id frame[1:2] = session_id
frame[2:5] = dxcallsign_crc
return frame return frame
@ -183,7 +189,7 @@ def t_foreign_disconnect(mycall: str, dxcall: str):
wrong_session = np.random.bytes(1) wrong_session = np.random.bytes(1)
while wrong_session == open_session: while wrong_session == open_session:
wrong_session = np.random.bytes(1) wrong_session = np.random.bytes(1)
close_frame = t_create_session_close(wrong_session) close_frame = t_create_session_close(wrong_session, dxcall)
print_frame(close_frame) print_frame(close_frame)
# assert ( # assert (
@ -254,7 +260,10 @@ def t_valid_disconnect(mycall: str, dxcall: str):
# Create packet to be 'received' by this station. # Create packet to be 'received' by this station.
# close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall) # close_frame = t_create_session_close_old(mycall=dxcall, dxcall=mycall)
open_session = create_frame[1:2] open_session = create_frame[1:2]
close_frame = t_create_session_close(open_session) print(dxcall)
print("#####################################################")
close_frame = t_create_session_close(open_session, mycall)
print(close_frame[2:5])
print_frame(close_frame) print_frame(close_frame)
tnc.received_session_close(close_frame) tnc.received_session_close(close_frame)

View file

@ -296,6 +296,6 @@ def t_datac0_2(
assert item in str( assert item in str(
sock.SOCKET_QUEUE.queue sock.SOCKET_QUEUE.queue
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}" ), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
# TODO: Not sure why we need this for every test run
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue) # assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
log.warning("station2: Exiting!") log.warning("station2: Exiting!")

View file

@ -287,7 +287,10 @@ def t_datac0_2(
# Allow enough time for this side to receive the disconnect frame. # Allow enough time for this side to receive the disconnect frame.
timeout = time.time() + timeout_duration timeout = time.time() + timeout_duration
while '"arq":"session","status":"close"' not in str(sock.SOCKET_QUEUE.queue): while '"arq":"session", "status":"close"' not in str(sock.SOCKET_QUEUE.queue):
if time.time() > timeout: if time.time() > timeout:
log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue)) log.warning("station2", TIMEOUT=True, queue=str(sock.SOCKET_QUEUE.queue))
break break
@ -302,6 +305,6 @@ def t_datac0_2(
assert item not in str( assert item not in str(
sock.SOCKET_QUEUE.queue sock.SOCKET_QUEUE.queue
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}" ), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
# TODO: Not sure why we need this for every test run
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue) # assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
log.warning("station2: Exiting!") log.warning("station2: Exiting!")

View file

@ -60,16 +60,19 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
# Check if we are running in a pyinstaller environment # Check if we are running in a pyinstaller environment
if hasattr(sys, "_MEIPASS"): #if hasattr(sys, "_MEIPASS"):
sys.path.append(getattr(sys, "_MEIPASS")) # sys.path.append(getattr(sys, "_MEIPASS"))
else: #else:
sys.path.append(os.path.abspath(".")) sys.path.append(os.path.abspath("."))
log.info("[C2 ] Searching for libcodec2...") log.info("[C2 ] Searching for libcodec2...")
if sys.platform == "linux": if sys.platform == "linux":
files = glob.glob(r"**/*libcodec2*", recursive=True) files = glob.glob(r"**/*libcodec2*", recursive=True)
files.append("libcodec2.so") files.append("libcodec2.so")
elif sys.platform == "darwin": elif sys.platform == "darwin":
if hasattr(sys, "_MEIPASS"):
files = glob.glob(getattr(sys, "_MEIPASS") + '/**/*libcodec2*', recursive=True)
else:
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True) files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
elif sys.platform in ["win32", "win64"]: elif sys.platform in ["win32", "win64"]:
files = glob.glob(r"**\*libcodec2*.dll", recursive=True) files = glob.glob(r"**\*libcodec2*.dll", recursive=True)

33
tnc/config.ini Normal file
View file

@ -0,0 +1,33 @@
[NETWORK]
#network settings
tncport = 3000
[STATION]
#station settings
mycall = DJ2LS-4
mygrid = JN48cs
ssid_list = [0,1,2,3,4,5]
[AUDIO]
#audio settings
rx = hw:2,0
tx = USB Audio CODEC
txaudiolevel = 120
[RADIO]
#radio settings
radiocontrol = rigctld
rigctld_ip = 127.0.0.1
rigctld_port = 4532
[TNC]
#tnc settings
scatter = True
fft = True
narrowband = False
fmin = -50.0
fmax = 50.0
qrv = True
rxbuffersize = 16
explorer = False

View file

@ -7,12 +7,17 @@ class CONFIG:
""" """
def __init__(self): def __init__(self, configfile: str):
# set up logger # set up logger
self.log = structlog.get_logger("CONFIG") self.log = structlog.get_logger("CONFIG")
# init configparser # init configparser
self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True) self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
try:
self.config_name = configfile
except Exception:
self.config_name = "config.ini" self.config_name = "config.ini"
self.log.info("[CFG] logfile init", file=self.config_name) self.log.info("[CFG] logfile init", file=self.config_name)
@ -45,7 +50,8 @@ class CONFIG:
self.config['STATION'] = {'#Station settings': None, self.config['STATION'] = {'#Station settings': None,
'mycall': data[1], 'mycall': data[1],
'mygrid': data[2] 'mygrid': data[2],
'ssid_list': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # list(data[26])
} }
self.config['AUDIO'] = {'#Audio settings': None, self.config['AUDIO'] = {'#Audio settings': None,

View file

@ -93,7 +93,7 @@ class DAEMON:
"[DMN] update_audio_devices: Exception gathering audio devices:", "[DMN] update_audio_devices: Exception gathering audio devices:",
e=err1, e=err1,
) )
time.sleep(1) threading.Event().wait(1)
def update_serial_devices(self): def update_serial_devices(self):
""" """
@ -114,7 +114,7 @@ class DAEMON:
) )
static.SERIAL_DEVICES = serial_devices static.SERIAL_DEVICES = serial_devices
time.sleep(1) threading.Event().wait(1)
except Exception as err1: except Exception as err1:
self.log.error( self.log.error(
"[DMN] update_serial_devices: Exception gathering serial devices:", "[DMN] update_serial_devices: Exception gathering serial devices:",
@ -156,7 +156,8 @@ class DAEMON:
# data[22] tx-audio-level # data[22] tx-audio-level
# data[23] respond_to_cq # data[23] respond_to_cq
# data[24] rx_buffer_size # data[24] rx_buffer_size
# data[25] explorer
# data[26] ssid_list
if data[0] == "STARTTNC": if data[0] == "STARTTNC":
self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6]) self.log.warning("[DMN] Starting TNC", rig=data[5], port=data[6])
@ -251,13 +252,36 @@ class DAEMON:
if data[25] == "True": if data[25] == "True":
options.append("--explorer") options.append("--explorer")
# wen want our ssid like this: --ssid 1 2 3 4
ssid_list = ""
for i in data[26]:
ssid_list += str(i) + " "
options.append("--ssid")
options.append(ssid_list)
# safe data to config file # safe data to config file
config.write_entire_config(data) config.write_entire_config(data)
import os, sys
import sys
# Try running tnc from binary, else run from source # Try running tnc from binary, else run from source
# This helps running the tnc in a developer environment # This helps running the tnc in a developer environment
try: try:
command = [] command = []
if (getattr(sys, 'frozen', False) or hasattr(sys, "_MEIPASS")) and sys.platform in ["darwin"]:
# If the application is run as a bundle, the PyInstaller bootloader
# extends the sys module by a flag frozen=True and sets the app
# path into variable _MEIPASS'.
application_path = sys._MEIPASS
command.append(application_path + '/freedata-tnc')
else:
if sys.platform in ["linux", "darwin"]: if sys.platform in ["linux", "darwin"]:
command.append("./freedata-tnc") command.append("./freedata-tnc")
elif sys.platform in ["win32", "win64"]: elif sys.platform in ["win32", "win64"]:
@ -272,6 +296,7 @@ class DAEMON:
except FileNotFoundError as err1: except FileNotFoundError as err1:
self.log.info("[DMN] worker: ", e=err1) self.log.info("[DMN] worker: ", e=err1)
command = [] command = []
if sys.platform in ["linux", "darwin"]: if sys.platform in ["linux", "darwin"]:
command.append("python3") command.append("python3")
elif sys.platform in ["win32", "win64"]: elif sys.platform in ["win32", "win64"]:
@ -279,6 +304,7 @@ class DAEMON:
command.append("main.py") command.append("main.py")
command += options command += options
print(command)
proc = subprocess.Popen(command) proc = subprocess.Popen(command)
atexit.register(proc.kill) atexit.register(proc.kill)
@ -407,7 +433,7 @@ if __name__ == "__main__":
mainlog.error("[DMN] logger init error", exception=err) mainlog.error("[DMN] logger init error", exception=err)
# init config # init config
config = config.CONFIG() config = config.CONFIG("config.ini")
try: try:
mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT) mainlog.info("[DMN] Starting TCP/IP socket", port=static.DAEMONPORT)
@ -434,4 +460,4 @@ if __name__ == "__main__":
version=static.VERSION, version=static.VERSION,
) )
while True: while True:
time.sleep(1) threading.Event().wait(1)

File diff suppressed because it is too large Load diff

View file

@ -28,15 +28,11 @@ class explorer():
def interval(self): def interval(self):
while True: while True:
self.push() self.push()
time.sleep(self.publish_interval) threading.Event().wait(self.publish_interval)
def push(self): def push(self):
frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY
if static.HAMLIB_FREQUENCY is not None:
frequency = static.HAMLIB_FREQUENCY
else:
frequency = 0
band = "USB" band = "USB"
callsign = str(static.MYCALLSIGN, "utf-8") callsign = str(static.MYCALLSIGN, "utf-8")
gridsquare = str(static.MYGRID, "utf-8") gridsquare = str(static.MYGRID, "utf-8")
@ -47,7 +43,21 @@ class explorer():
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth) log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon} station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []}
for i in static.HEARD_STATIONS:
try:
callsign = str(i[0], "UTF-8")
grid = str(i[1], "UTF-8")
timestamp = i[2]
try:
snr = i[4].split("/")[1]
except AttributeError:
snr = str(i[4])
station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr, "timestamp": timestamp})
except Exception as e:
log.debug("[EXPLORER] not publishing station", e=e)
station_data = json.dumps(station_data) station_data = json.dumps(station_data)
try: try:
response = requests.post(self.explorer_url, json=station_data, headers=headers) response = requests.post(self.explorer_url, json=station_data, headers=headers)

View file

@ -25,6 +25,7 @@ daemon_exe = EXE(daemon_pyz,
[], [],
exclude_binaries=True, exclude_binaries=True,
name='freedata-daemon', name='freedata-daemon',
bundle_identifier='com.dj2ls.freedata-daemon',
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
@ -62,6 +63,7 @@ tnc_exe = EXE(tnc_pyz,
[], [],
exclude_binaries=True, exclude_binaries=True,
name='freedata-tnc', name='freedata-tnc',
bundle_identifier='com.dj2ls.freedata-tnc',
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,

View file

@ -5,11 +5,12 @@ Created on Fri Dec 25 21:25:14 2020
@author: DJ2LS @author: DJ2LS
""" """
import time import time
from datetime import datetime,timezone
import crcengine import crcengine
import static import static
import structlog import structlog
import numpy as np import numpy as np
import threading
log = structlog.get_logger("helpers") log = structlog.get_logger("helpers")
@ -25,7 +26,7 @@ def wait(seconds: float) -> bool:
timeout = time.time() + seconds timeout = time.time() + seconds
while time.time() < timeout: while time.time() < timeout:
time.sleep(0.01) threading.Event().wait(0.01)
return True return True
@ -131,7 +132,7 @@ def add_to_heard_stations(dxcallsign, dxgrid, datatype, snr, offset, frequency):
# check if buffer empty # check if buffer empty
if len(static.HEARD_STATIONS) == 0: if len(static.HEARD_STATIONS) == 0:
static.HEARD_STATIONS.append( static.HEARD_STATIONS.append(
[dxcallsign, dxgrid, int(time.time()), datatype, snr, offset, frequency] [dxcallsign, dxgrid, int(datetime.now(timezone.utc).timestamp()), datatype, snr, offset, frequency]
) )
# if not, we search and update # if not, we search and update
else: else:
@ -315,7 +316,7 @@ def check_callsign(callsign: bytes, crc_to_check: bytes):
log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid) log.debug("[HLP] check_callsign matched:", call_with_ssid=call_with_ssid)
return [True, bytes(call_with_ssid)] return [True, bytes(call_with_ssid)]
return [False, ""] return [False, b'']
def check_session_id(id: bytes, id_to_check: bytes): def check_session_id(id: bytes, id_to_check: bytes):
@ -330,6 +331,8 @@ def check_session_id(id: bytes, id_to_check: bytes):
True True
False False
""" """
if id_to_check == b'\x00':
return False
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check) log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
return id == id_to_check return id == id_to_check
@ -391,11 +394,7 @@ def decode_grid(b_code_word: bytes):
int_val = int(code_word & 0b111111111) int_val = int(code_word & 0b111111111)
int_first, int_sec = divmod(int_val, 18) int_first, int_sec = divmod(int_val, 18)
# int_first = int_val // 18 return chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
# int_sec = int_val % 18
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
return grid
def encode_call(call): def encode_call(call):

View file

@ -14,7 +14,7 @@ def setup_logging(filename: str = "", level: str = "DEBUG"):
""" """
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") timestamper = structlog.processors.TimeStamper(fmt="iso")
pre_chain = [ pre_chain = [
# Add the log level and a timestamp to the event_dict if the log entry # Add the log level and a timestamp to the event_dict if the log entry
# is not from structlog. # is not from structlog.

View file

@ -31,6 +31,7 @@ import modem
import static import static
import structlog import structlog
import explorer import explorer
import json
log = structlog.get_logger("main") log = structlog.get_logger("main")
@ -57,12 +58,28 @@ if __name__ == "__main__":
# --------------------------------------------GET PARAMETER INPUTS # --------------------------------------------GET PARAMETER INPUTS
PARSER = argparse.ArgumentParser(description="FreeDATA TNC") PARSER = argparse.ArgumentParser(description="FreeDATA TNC")
#PARSER.add_argument(
# "--use-config",
# dest="configfile",
# action="store_true",
# help="Use the default config file config.ini",
#)
PARSER.add_argument( PARSER.add_argument(
"--use-config", "--use-config",
dest="configfile", dest="configfile",
action="store_true", default=False,
type=str,
help="Use the default config file config.ini", help="Use the default config file config.ini",
) )
PARSER.add_argument(
"--save-to-folder",
dest="savetofolder",
default=False,
action="store_true",
help="Save received data to local folder",
)
PARSER.add_argument( PARSER.add_argument(
"--mycall", "--mycall",
dest="mycall", dest="mycall",
@ -90,14 +107,14 @@ if __name__ == "__main__":
dest="audio_input_device", dest="audio_input_device",
default=0, default=0,
help="listening sound card", help="listening sound card",
type=int, type=str,
) )
PARSER.add_argument( PARSER.add_argument(
"--tx", "--tx",
dest="audio_output_device", dest="audio_output_device",
default=0, default=0,
help="transmitting sound card", help="transmitting sound card",
type=int, type=str,
) )
PARSER.add_argument( PARSER.add_argument(
"--port", "--port",
@ -261,48 +278,13 @@ if __name__ == "__main__":
help="Enable sending tnc data to https://explorer.freedata.app", help="Enable sending tnc data to https://explorer.freedata.app",
) )
ARGS = PARSER.parse_args() ARGS = PARSER.parse_args()
if ARGS.configfile:
# init config
config = config.CONFIG().read_config()
# additional step for being sure our callsign is correctly # set save to folder state for allowing downloading files to local file system
# in case we are not getting a station ssid static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder
# then we are forcing a station ssid = 0
mycallsign = bytes(config['STATION']['mycall'], "utf-8")
mycallsign = helpers.callsign_to_bytes(mycallsign)
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
static.SSID_LIST = config['STATION']['ssid'] if not ARGS.configfile:
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
static.PORT = int(config['NETWORK']['tncport'])
# TODO: disabled because we don't need these settings anymore.
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
static.ENABLE_SCATTER = config['TNC']['scatter']
static.ENABLE_FFT = config['TNC']['fft']
static.ENABLE_FSK = False
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband']
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
static.RESPOND_TO_CQ = config['TNC']['qrv']
static.RX_BUFFER_SIZE = config['TNC']['rxbuffersize']
static.ENABLE_EXPLORER = config['TNC']['explorer']
else:
# additional step for being sure our callsign is correctly # additional step for being sure our callsign is correctly
# in case we are not getting a station ssid # in case we are not getting a station ssid
# then we are forcing a station ssid = 0 # then we are forcing a station ssid = 0
@ -311,11 +293,24 @@ if __name__ == "__main__":
mycallsign = helpers.callsign_to_bytes(mycallsign) mycallsign = helpers.callsign_to_bytes(mycallsign)
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign) static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN) static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
static.SSID_LIST = ARGS.ssid_list static.SSID_LIST = ARGS.ssid_list
# check if own ssid is always part of ssid list
own_ssid = int(static.MYCALLSIGN.split(b"-")[1])
if own_ssid not in static.SSID_LIST:
static.SSID_LIST.append(own_ssid)
static.MYGRID = bytes(ARGS.mygrid, "utf-8") static.MYGRID = bytes(ARGS.mygrid, "utf-8")
# check if we have an int or str as device name
try:
static.AUDIO_INPUT_DEVICE = int(ARGS.audio_input_device)
except ValueError:
static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device static.AUDIO_INPUT_DEVICE = ARGS.audio_input_device
try:
static.AUDIO_OUTPUT_DEVICE = int(ARGS.audio_output_device)
except ValueError:
static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device static.AUDIO_OUTPUT_DEVICE = ARGS.audio_output_device
static.PORT = ARGS.socket_port static.PORT = ARGS.socket_port
static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name static.HAMLIB_DEVICE_NAME = ARGS.hamlib_device_name
static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port static.HAMLIB_DEVICE_PORT = ARGS.hamlib_device_port
@ -341,6 +336,67 @@ if __name__ == "__main__":
except Exception as e: except Exception as e:
log.error("[DMN] Error reading config file", exception=e) log.error("[DMN] Error reading config file", exception=e)
else:
configfile = ARGS.configfile
# init config
config = config.CONFIG(configfile).read_config()
try:
# additional step for being sure our callsign is correctly
# in case we are not getting a station ssid
# then we are forcing a station ssid = 0
mycallsign = bytes(config['STATION']['mycall'], "utf-8")
mycallsign = helpers.callsign_to_bytes(mycallsign)
static.MYCALLSIGN = helpers.bytes_to_callsign(mycallsign)
static.MYCALLSIGN_CRC = helpers.get_crc_24(static.MYCALLSIGN)
#json.loads = for converting str list to list
static.SSID_LIST = json.loads(config['STATION']['ssid_list'])
static.MYGRID = bytes(config['STATION']['mygrid'], "utf-8")
# check if we have an int or str as device name
try:
static.AUDIO_INPUT_DEVICE = int(config['AUDIO']['rx'])
except ValueError:
static.AUDIO_INPUT_DEVICE = config['AUDIO']['rx']
try:
static.AUDIO_OUTPUT_DEVICE = int(config['AUDIO']['tx'])
except ValueError:
static.AUDIO_OUTPUT_DEVICE = config['AUDIO']['tx']
static.PORT = int(config['NETWORK']['tncport'])
# TODO: disabled because we don't need these settings anymore.
#static.HAMLIB_DEVICE_NAME = config['RADIO']['devicename']
#static.HAMLIB_DEVICE_PORT = config['RADIO']['deviceport']
#static.HAMLIB_PTT_TYPE = config['RADIO']['pttprotocol']
#static.HAMLIB_PTT_PORT = config['RADIO']['pttport']
#static.HAMLIB_SERIAL_SPEED = str(config['RADIO']['serialspeed'])
#static.HAMLIB_DATA_BITS = str(config['RADIO']['data_bits'])
#static.HAMLIB_STOP_BITS = str(config['RADIO']['stop_bits'])
#static.HAMLIB_HANDSHAKE = config['RADIO']['handshake']
static.HAMLIB_RADIOCONTROL = config['RADIO']['radiocontrol']
static.HAMLIB_RIGCTLD_IP = config['RADIO']['rigctld_ip']
static.HAMLIB_RIGCTLD_PORT = str(config['RADIO']['rigctld_port'])
static.ENABLE_SCATTER = config['TNC']['scatter'] in ["True", "true", True]
static.ENABLE_FFT = config['TNC']['fft'] in ["True", "true", True]
static.ENABLE_FSK = False
static.LOW_BANDWIDTH_MODE = config['TNC']['narrowband'] in ["True", "true", True]
static.TUNING_RANGE_FMIN = float(config['TNC']['fmin'])
static.TUNING_RANGE_FMAX = float(config['TNC']['fmax'])
static.TX_AUDIO_LEVEL = config['AUDIO']['txaudiolevel']
static.RESPOND_TO_CQ = config['TNC']['qrv'] in ["True", "true", True]
static.RX_BUFFER_SIZE = int(config['TNC']['rxbuffersize'])
static.ENABLE_EXPLORER = config['TNC']['explorer'] in ["True", "true", True]
except KeyError as e:
log.warning("[CFG] Error reading config file near", key=str(e))
except Exception as e:
log.warning("[CFG] Error", e=e)
# make sure the own ssid is always part of the ssid list
my_ssid = int(static.MYCALLSIGN.split(b'-')[1])
if my_ssid not in static.SSID_LIST:
static.SSID_LIST.append(my_ssid)
# we need to wait until we got all parameters from argparse first before we can load the other modules # we need to wait until we got all parameters from argparse first before we can load the other modules
import sock import sock
@ -399,4 +455,4 @@ if __name__ == "__main__":
log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err) log.error("[TNC] Starting TCP/IP socket failed", port=static.PORT, e=err)
sys.exit(1) sys.exit(1)
while True: while True:
time.sleep(1) threading.Event().wait(1)

View file

@ -5,6 +5,7 @@ Created on Wed Dec 23 07:04:24 2020
@author: DJ2LS @author: DJ2LS
""" """
# pylint: disable=invalid-name, line-too-long, c-extension-no-member # pylint: disable=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
@ -15,15 +16,16 @@ import sys
import threading import threading
import time import time
from collections import deque from collections import deque
import wave
import codec2 import codec2
import itertools
import numpy as np import numpy as np
import sock import sock
import sounddevice as sd import sounddevice as sd
import static import static
import structlog import structlog
import ujson as json import ujson as json
from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE
TESTMODE = False TESTMODE = False
RXCHANNEL = "" RXCHANNEL = ""
@ -38,6 +40,13 @@ RECEIVE_DATAC1 = False
RECEIVE_DATAC3 = False RECEIVE_DATAC3 = False
RECEIVE_FSK_LDPC_1 = False RECEIVE_FSK_LDPC_1 = False
# state buffer
SIG0_DATAC0_STATE = []
SIG1_DATAC0_STATE = []
DAT0_DATAC1_STATE = []
DAT0_DATAC3_STATE = []
class RF: class RF:
"""Class to encapsulate interactions between the audio device and codec2""" """Class to encapsulate interactions between the audio device and codec2"""
@ -60,6 +69,7 @@ class RF:
self.AUDIO_CHANNELS = 1 self.AUDIO_CHANNELS = 1
self.MODE = 0 self.MODE = 0
# Locking state for mod out so buffer will be filled before we can use it # Locking state for mod out so buffer will be filled before we can use it
# https://github.com/DJ2LS/FreeDATA/issues/127 # https://github.com/DJ2LS/FreeDATA/issues/127
# https://github.com/DJ2LS/FreeDATA/issues/99 # https://github.com/DJ2LS/FreeDATA/issues/99
@ -141,6 +151,10 @@ class RF:
codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV codec2.api.FREEDV_MODE_FSK_LDPC_1_ADV
) )
# INIT TX MODES
self.freedv_datac0_tx = open_codec2_instance(14)
self.freedv_datac1_tx = open_codec2_instance(10)
self.freedv_datac3_tx = open_codec2_instance(12)
# --------------------------------------------CREATE PYAUDIO INSTANCE # --------------------------------------------CREATE PYAUDIO INSTANCE
if not TESTMODE: if not TESTMODE:
try: try:
@ -266,6 +280,13 @@ class RF:
) )
hamlib_thread.start() hamlib_thread.start()
hamlib_set_thread = threading.Thread(
target=self.set_rig_data, name="HAMLIB_SET_THREAD", daemon=True
)
hamlib_set_thread.start()
# self.log.debug("[MDM] Starting worker_receive") # self.log.debug("[MDM] Starting worker_receive")
worker_received = threading.Thread( worker_received = threading.Thread(
target=self.worker_received, name="WORKER_THREAD", daemon=True target=self.worker_received, name="WORKER_THREAD", daemon=True
@ -284,7 +305,7 @@ class RF:
depositing the data into the codec data buffers. depositing the data into the codec data buffers.
""" """
while True: while True:
time.sleep(0.01) threading.Event().wait(0.01)
# -----read # -----read
data_in48k = bytes() data_in48k = bytes()
with open(RXCHANNEL, "rb") as fifo: with open(RXCHANNEL, "rb") as fifo:
@ -307,7 +328,7 @@ class RF:
# (self.fsk_ldpc_buffer_1, static.ENABLE_FSK), # (self.fsk_ldpc_buffer_1, static.ENABLE_FSK),
]: ]:
if ( if (
not data_buffer.nbuffer + length_x > data_buffer.size not (data_buffer.nbuffer + length_x) > data_buffer.size
and receive and receive
): ):
data_buffer.push(x) data_buffer.push(x)
@ -315,14 +336,10 @@ class RF:
def mkfifo_write_callback(self) -> None: def mkfifo_write_callback(self) -> None:
"""Support testing by writing the audio data to a pipe.""" """Support testing by writing the audio data to a pipe."""
while True: while True:
time.sleep(0.01) threading.Event().wait(0.01)
# -----write # -----write
if len(self.modoutqueue) <= 0 or self.mod_out_locked: if len(self.modoutqueue) > 0 and not self.mod_out_locked:
# data_out48k = np.zeros(self.AUDIO_FRAMES_PER_BUFFER_RX, dtype=np.int16)
pass
else:
data_out48k = self.modoutqueue.popleft() data_out48k = self.modoutqueue.popleft()
# print(len(data_out48k)) # print(len(data_out48k))
@ -348,8 +365,14 @@ class RF:
x = np.frombuffer(data_in48k, dtype=np.int16) x = np.frombuffer(data_in48k, dtype=np.int16)
x = self.resampler.resample48_to_8(x) x = self.resampler.resample48_to_8(x)
# audio recording for debugging purposes
if static.AUDIO_RECORD:
#static.AUDIO_RECORD_FILE.write(x)
static.AUDIO_RECORD_FILE.writeframes(x)
# Avoid decoding when transmitting to reduce CPU # Avoid decoding when transmitting to reduce CPU
if not static.TRANSMITTING: # TODO: Overriding this for testing purposes
# if not static.TRANSMITTING:
length_x = len(x) length_x = len(x)
# Avoid buffer overflow by filling only if buffer for # Avoid buffer overflow by filling only if buffer for
@ -362,16 +385,24 @@ class RF:
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4), (self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5), (self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
]: ]:
if audiobuffer.nbuffer + length_x > audiobuffer.size: if (audiobuffer.nbuffer + length_x) > audiobuffer.size:
static.BUFFER_OVERFLOW_COUNTER[index] += 1 static.BUFFER_OVERFLOW_COUNTER[index] += 1
elif receive: elif receive:
audiobuffer.push(x) audiobuffer.push(x)
# end of "not static.TRANSMITTING" if block
if len(self.modoutqueue) <= 0 or self.mod_out_locked: if not self.modoutqueue or self.mod_out_locked:
# if not self.modoutqueue or self.mod_out_locked:
data_out48k = np.zeros(frames, dtype=np.int16) data_out48k = np.zeros(frames, dtype=np.int16)
self.fft_data = x self.fft_data = x
else: else:
if not static.PTT_STATE:
# TODO: Moved to this place for testing
# Maybe we can avoid moments of silence before transmitting
static.PTT_STATE = self.hamlib.set_ptt(True)
jsondata = {"ptt": "True"}
data_out = json.dumps(jsondata)
sock.SOCKET_QUEUE.put(data_out)
data_out48k = self.modoutqueue.popleft() data_out48k = self.modoutqueue.popleft()
self.fft_data = data_out48k self.fft_data = data_out48k
@ -395,18 +426,37 @@ class RF:
frames: frames:
""" """
self.log.debug("[MDM] transmit", mode=mode)
"""
sig0 = 14
sig1 = 14
datac0 = 14
datac1 = 10
datac3 = 12
fsk_ldpc = 9
fsk_ldpc_0 = 200
fsk_ldpc_1 = 201
"""
if mode == 14:
freedv = self.freedv_datac0_tx
elif mode == 10:
freedv = self.freedv_datac1_tx
elif mode == 12:
freedv = self.freedv_datac3_tx
else:
return False
static.TRANSMITTING = True static.TRANSMITTING = True
start_of_transmission = time.time() start_of_transmission = time.time()
# TODO: Moved ptt toggle some steps before audio is ready for testing
# Toggle ptt early to save some time and send ptt state via socket # Toggle ptt early to save some time and send ptt state via socket
static.PTT_STATE = self.hamlib.set_ptt(True) # static.PTT_STATE = self.hamlib.set_ptt(True)
jsondata = {"ptt": "True"} # jsondata = {"ptt": "True"}
data_out = json.dumps(jsondata) # data_out = json.dumps(jsondata)
sock.SOCKET_QUEUE.put(data_out) # sock.SOCKET_QUEUE.put(data_out)
# Open codec2 instance # Open codec2 instance
self.MODE = mode self.MODE = mode
freedv = open_codec2_instance(self.MODE)
# Get number of bytes per frame for mode # Get number of bytes per frame for mode
bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8) bytes_per_frame = int(codec2.api.freedv_get_bits_per_modem_frame(freedv) / 8)
@ -431,11 +481,12 @@ class RF:
) )
# Add empty data to handle ptt toggle time # Add empty data to handle ptt toggle time
data_delay_mseconds = 0 # milliseconds #data_delay_mseconds = 0 # milliseconds
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore #data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
mod_out_silence = ctypes.create_string_buffer(data_delay * 2) #mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
txbuffer = bytes(mod_out_silence) #txbuffer = bytes(mod_out_silence)
# TODO: Disabled this one for testing
txbuffer = bytes()
self.log.debug( self.log.debug(
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame "[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
) )
@ -499,8 +550,10 @@ class RF:
txbuffer_48k = self.resampler.resample8_to_48(x) txbuffer_48k = self.resampler.resample8_to_48(x)
# Explicitly lock our usage of mod_out_queue if needed # Explicitly lock our usage of mod_out_queue if needed
# Deactivated for testing purposes # This could avoid audio problems on slower CPU
self.mod_out_locked = False # we will fill our modout list with all data, then start
# processing it in audio callback
self.mod_out_locked = True
# ------------------------------- # -------------------------------
chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800 chunk_length = self.AUDIO_FRAMES_PER_BUFFER_TX # 4800
@ -518,11 +571,11 @@ class RF:
self.modoutqueue.append(c) self.modoutqueue.append(c)
# Release our mod_out_lock so we can use the queue # Release our mod_out_lock, so we can use the queue
self.mod_out_locked = False self.mod_out_locked = False
while self.modoutqueue: while self.modoutqueue:
time.sleep(0.01) threading.Event().wait(0.01)
static.PTT_STATE = self.hamlib.set_ptt(False) static.PTT_STATE = self.hamlib.set_ptt(False)
@ -534,7 +587,6 @@ class RF:
# After processing, set the locking state back to true to be prepared for next transmission # After processing, set the locking state back to true to be prepared for next transmission
self.mod_out_locked = True self.mod_out_locked = True
self.c_lib.freedv_close(freedv)
self.modem_transmit_queue.task_done() self.modem_transmit_queue.task_done()
static.TRANSMITTING = False static.TRANSMITTING = False
threading.Event().set() threading.Event().set()
@ -550,13 +602,15 @@ class RF:
freedv: ctypes.c_void_p, freedv: ctypes.c_void_p,
bytes_out, bytes_out,
bytes_per_frame, bytes_per_frame,
state_buffer,
mode_name,
) -> int: ) -> int:
""" """
De-modulate supplied audio stream with supplied codec2 instance. De-modulate supplied audio stream with supplied codec2 instance.
Decoded audio is placed into `bytes_out`. Decoded audio is placed into `bytes_out`.
:param buffer: Incoming audio :param audiobuffer: Incoming audio
:type buffer: codec2.audio_buffer :type audiobuffer: codec2.audio_buffer
:param nin: Number of frames codec2 is expecting :param nin: Number of frames codec2 is expecting
:type nin: int :type nin: int
:param freedv: codec2 instance :param freedv: codec2 instance
@ -565,6 +619,10 @@ class RF:
:type bytes_out: _type_ :type bytes_out: _type_
:param bytes_per_frame: Number of bytes per frame :param bytes_per_frame: Number of bytes per frame
:type bytes_per_frame: int :type bytes_per_frame: int
:param state_buffer: modem states
:type state_buffer: int
:param mode_name: mode name
:type mode_name: str
:return: NIN from freedv instance :return: NIN from freedv instance
:rtype: int :rtype: int
""" """
@ -577,17 +635,39 @@ class RF:
nbytes = codec2.api.freedv_rawdatarx( nbytes = codec2.api.freedv_rawdatarx(
freedv, bytes_out, audiobuffer.buffer.ctypes freedv, bytes_out, audiobuffer.buffer.ctypes
) )
# get current modem states and write to list
# 1 trial
# 2 sync
# 3 trial sync
# 6 decoded
# 10 error decoding == NACK
rx_status = codec2.api.freedv_get_rx_status(freedv)
if rx_status != 0:
# if we're receiving FreeDATA signals, reset channel busy state
static.CHANNEL_BUSY = False
self.log.debug(
"[MDM] [demod_audio] modem state", mode=mode_name, rx_status=rx_status, sync_flag=codec2.api.rx_sync_flags_to_text[rx_status]
)
if rx_status == 10:
state_buffer.append(rx_status)
audiobuffer.pop(nin) audiobuffer.pop(nin)
nin = codec2.api.freedv_nin(freedv) nin = codec2.api.freedv_nin(freedv)
if nbytes == bytes_per_frame: if nbytes == bytes_per_frame:
# process commands only if static.LISTEN = True # process commands only if static.LISTEN = True
if static.LISTEN: if static.LISTEN:
self.log.debug( self.log.debug(
"[MDM] [demod_audio] Pushing received data to received_queue" "[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
) )
self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame]) self.modem_received_queue.put([bytes_out, freedv, bytes_per_frame])
self.get_scatter(freedv) self.get_scatter(freedv)
self.calculate_snr(freedv) self.calculate_snr(freedv)
state_buffer = []
else: else:
self.log.warning( self.log.warning(
"[MDM] [demod_audio] received frame but ignored processing", "[MDM] [demod_audio] received frame but ignored processing",
@ -675,6 +755,8 @@ class RF:
self.sig0_datac0_freedv, self.sig0_datac0_freedv,
self.sig0_datac0_bytes_out, self.sig0_datac0_bytes_out,
self.sig0_datac0_bytes_per_frame, self.sig0_datac0_bytes_per_frame,
SIG0_DATAC0_STATE,
"sig0-datac0"
) )
def audio_sig1_datac0(self) -> None: def audio_sig1_datac0(self) -> None:
@ -685,6 +767,8 @@ class RF:
self.sig1_datac0_freedv, self.sig1_datac0_freedv,
self.sig1_datac0_bytes_out, self.sig1_datac0_bytes_out,
self.sig1_datac0_bytes_per_frame, self.sig1_datac0_bytes_per_frame,
SIG1_DATAC0_STATE,
"sig1-datac0"
) )
def audio_dat0_datac1(self) -> None: def audio_dat0_datac1(self) -> None:
@ -695,6 +779,8 @@ class RF:
self.dat0_datac1_freedv, self.dat0_datac1_freedv,
self.dat0_datac1_bytes_out, self.dat0_datac1_bytes_out,
self.dat0_datac1_bytes_per_frame, self.dat0_datac1_bytes_per_frame,
DAT0_DATAC1_STATE,
"dat0-datac1"
) )
def audio_dat0_datac3(self) -> None: def audio_dat0_datac3(self) -> None:
@ -705,6 +791,8 @@ class RF:
self.dat0_datac3_freedv, self.dat0_datac3_freedv,
self.dat0_datac3_bytes_out, self.dat0_datac3_bytes_out,
self.dat0_datac3_bytes_per_frame, self.dat0_datac3_bytes_per_frame,
DAT0_DATAC3_STATE,
"dat0-datac3"
) )
def audio_fsk_ldpc_0(self) -> None: def audio_fsk_ldpc_0(self) -> None:
@ -730,9 +818,14 @@ class RF:
def worker_transmit(self) -> None: def worker_transmit(self) -> None:
"""Worker for FIFO queue for processing frames to be transmitted""" """Worker for FIFO queue for processing frames to be transmitted"""
while True: while True:
# print queue size for debugging purposes
# TODO: Lets check why we have several frames in our transmit queue which causes sometimes a double transmission
# we could do a cleanup after a transmission so theres no reason sending twice
queuesize = self.modem_transmit_queue.qsize()
self.log.debug("[MDM] self.modem_transmit_queue", qsize=queuesize)
data = self.modem_transmit_queue.get() data = self.modem_transmit_queue.get()
self.log.debug("[MDM] worker_transmit", mode=data[0]) # self.log.debug("[MDM] worker_transmit", mode=data[0])
self.transmit( self.transmit(
mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3] mode=data[0], repeats=data[1], repeat_delay=data[2], frames=data[3]
) )
@ -783,8 +876,16 @@ class RF:
) )
scatterdata = [] scatterdata = []
for i in range(codec2.MODEM_STATS_NC_MAX): # original function before itertool
for j in range(1, codec2.MODEM_STATS_NR_MAX, 2): #for i in range(codec2.MODEM_STATS_NC_MAX):
# for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
# # print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
# xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
# ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
# if xsymbols != 0.0 and ysymbols != 0.0:
# scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
for i, j in itertools.product(range(codec2.MODEM_STATS_NC_MAX), range(1, codec2.MODEM_STATS_NR_MAX, 2)):
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}") # print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000) xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
ysymbols = round(modemStats.rx_symbols[i][j] // 1000) ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
@ -798,7 +899,6 @@ class RF:
# only take every tenth data point # only take every tenth data point
static.SCATTER = scatterdata[::10] static.SCATTER = scatterdata[::10]
def calculate_snr(self, freedv: ctypes.c_void_p) -> float: def calculate_snr(self, freedv: ctypes.c_void_p) -> float:
""" """
Ask codec2 for data about the received signal and calculate Ask codec2 for data about the received signal and calculate
@ -832,6 +932,20 @@ class RF:
static.SNR = 0 static.SNR = 0
return static.SNR return static.SNR
def set_rig_data(self) -> None:
"""
Set rigctld parameters like frequency, mode
THis needs to be processed in a queue
"""
while True:
cmd = RIGCTLD_COMMAND_QUEUE.get()
if cmd[0] == "set_frequency":
# [1] = Frequency
self.hamlib.set_frequency(cmd[1])
if cmd[0] == "set_mode":
# [1] = Mode
self.hamlib.set_mode(cmd[1])
def update_rig_data(self) -> None: def update_rig_data(self) -> None:
""" """
Request information about the current state of the radio via hamlib Request information about the current state of the radio via hamlib
@ -841,7 +955,7 @@ class RF:
- static.HAMLIB_BANDWIDTH - static.HAMLIB_BANDWIDTH
""" """
while True: while True:
threading.Event().wait(0.5) threading.Event().wait(0.25)
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency() static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
static.HAMLIB_MODE = self.hamlib.get_mode() static.HAMLIB_MODE = self.hamlib.get_mode()
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth() static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
@ -859,7 +973,7 @@ class RF:
rms_counter = 0 rms_counter = 0
while True: while True:
# time.sleep(0.01) # threading.Event().wait(0.01)
threading.Event().wait(0.01) threading.Event().wait(0.01)
# WE NEED TO OPTIMIZE THIS! # WE NEED TO OPTIMIZE THIS!
@ -892,12 +1006,22 @@ class RF:
# calculate dbfs every 50 cycles for reducing CPU load # calculate dbfs every 50 cycles for reducing CPU load
rms_counter += 1 rms_counter += 1
if rms_counter > 50: if rms_counter > 50:
d = np.frombuffer(self.fft_data, np.int16).astype(np.float) d = np.frombuffer(self.fft_data, np.int16).astype(np.float32)
# calculate RMS and then dBFS # calculate RMS and then dBFS
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen # TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs # https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
# try except for avoiding runtime errors by division/0
try:
rms = int(np.sqrt(np.max(d ** 2))) rms = int(np.sqrt(np.max(d ** 2)))
if rms == 0:
raise ZeroDivisionError
static.AUDIO_DBFS = 20 * np.log10(rms / 32768) static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
except Exception as e:
self.log.warning(
"[MDM] fft calculation error - please check your audio setup",
e=e,
)
static.AUDIO_DBFS = -100
rms_counter = 0 rms_counter = 0
@ -919,20 +1043,16 @@ class RF:
# 3200Hz = 315 # 3200Hz = 315
# define the area, we are detecting busy state # define the area, we are detecting busy state
if static.LOW_BANDWIDTH_MODE: dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231]
dfft = dfft[120:176]
else:
dfft = dfft[65:231]
# Check for signals higher than average by checking for "100" # Check for signals higher than average by checking for "100"
# If we have a signal, increment our channel_busy delay counter # If we have a signal, increment our channel_busy delay counter
# so we have a smoother state toggle # so we have a smoother state toggle
if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING: if np.sum(dfft[dfft > avg + 15]) >= 400 and not static.TRANSMITTING:
static.CHANNEL_BUSY = True static.CHANNEL_BUSY = True
# Limit delay counter to a maximum of 250. The higher this value, # Limit delay counter to a maximum of 200. The higher this value,
# the longer we will wait until releasing state # the longer we will wait until releasing state
channel_busy_delay = min(channel_busy_delay + 10, 250) channel_busy_delay = min(channel_busy_delay + 10, 200)
else: else:
# Decrement channel busy counter if no signal has been detected. # Decrement channel busy counter if no signal has been detected.
channel_busy_delay = max(channel_busy_delay - 1, 0) channel_busy_delay = max(channel_busy_delay - 1, 0)
@ -1032,3 +1152,19 @@ def set_audio_volume(datalist, volume: float) -> np.int16:
# Scale samples by the ratio of volume / 100.0 # Scale samples by the ratio of volume / 100.0
data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore data = np.fromstring(datalist, np.int16) * (volume / 100.0) # type: ignore
return data.astype(np.int16) return data.astype(np.int16)
def get_modem_error_state():
"""
get current state buffer and return True of contains 10
"""
if RECEIVE_DATAC1 and 10 in DAT0_DATAC1_STATE:
DAT0_DATAC1_STATE.clear()
return True
if RECEIVE_DATAC3 and 10 in DAT0_DATAC3_STATE:
DAT0_DATAC3_STATE.clear()
return True
return False

View file

@ -13,3 +13,6 @@ MODEM_TRANSMIT_QUEUE = queue.Queue()
# Initialize FIFO queue to finally store received data # Initialize FIFO queue to finally store received data
RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE) RX_BUFFER = queue.Queue(maxsize=static.RX_BUFFER_SIZE)
# Commands we want to send to rigctld
RIGCTLD_COMMAND_QUEUE = queue.Queue()

View file

@ -4,9 +4,11 @@
# #
# modified and adjusted to FreeDATA needs by DJ2LS # modified and adjusted to FreeDATA needs by DJ2LS
import contextlib
import socket import socket
import time import time
import structlog import structlog
import threading
# set global hamlib version # set global hamlib version
hamlib_version = 0 hamlib_version = 0
@ -19,9 +21,11 @@ class radio:
def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5): def __init__(self, hostname="localhost", port=4532, poll_rate=5, timeout=5):
"""Open a connection to rigctld, and test it for validity""" """Open a connection to rigctld, and test it for validity"""
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ptt_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connected = False self.ptt_connected = False
self.data_connected = False
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
self.connection_attempts = 5 self.connection_attempts = 5
@ -31,7 +35,6 @@ class radio:
self.frequency = '' self.frequency = ''
self.mode = '' self.mode = ''
def open_rig( def open_rig(
self, self,
devicename, devicename,
@ -65,8 +68,20 @@ class radio:
self.hostname = rigctld_ip self.hostname = rigctld_ip
self.port = int(rigctld_port) self.port = int(rigctld_port)
if self.connect(): #_ptt_connect = self.ptt_connect()
self.log.debug("Rigctl initialized") #_data_connect = self.data_connect()
ptt_thread = threading.Thread(target=self.ptt_connect, args=[], daemon=True)
ptt_thread.start()
data_thread = threading.Thread(target=self.data_connect, args=[], daemon=True)
data_thread.start()
# wait some time
threading.Event().wait(0.5)
if self.ptt_connected and self.data_connected:
self.log.debug("Rigctl DATA/PTT initialized")
return True return True
self.log.error( self.log.error(
@ -74,33 +89,58 @@ class radio:
) )
return False return False
def connect(self): def ptt_connect(self):
"""Connect to rigctld instance""" """Connect to rigctld instance"""
if not self.connected: while True:
if not self.ptt_connected:
try: try:
self.connection = socket.create_connection((self.hostname, self.port)) self.ptt_connection = socket.create_connection((self.hostname, self.port))
self.connected = True self.ptt_connected = True
self.log.info( self.log.info(
"[RIGCTLD] Connected to rigctld!", ip=self.hostname, port=self.port "[RIGCTLD] Connected PTT instance to rigctld!", ip=self.hostname, port=self.port
) )
return True
except Exception as err: except Exception as err:
# ConnectionRefusedError: [Errno 111] Connection refused # ConnectionRefusedError: [Errno 111] Connection refused
self.close_rig() self.close_rig()
self.log.warning( self.log.warning(
"[RIGCTLD] Reconnect...", "[RIGCTLD] PTT Reconnect...",
ip=self.hostname, ip=self.hostname,
port=self.port, port=self.port,
e=err, e=err,
) )
return False
threading.Event().wait(0.5)
def data_connect(self):
"""Connect to rigctld instance"""
while True:
if not self.data_connected:
try:
self.data_connection = socket.create_connection((self.hostname, self.port))
self.data_connected = True
self.log.info(
"[RIGCTLD] Connected DATA instance to rigctld!", ip=self.hostname, port=self.port
)
except Exception as err:
# ConnectionRefusedError: [Errno 111] Connection refused
self.close_rig()
self.log.warning(
"[RIGCTLD] DATA Reconnect...",
ip=self.hostname,
port=self.port,
e=err,
)
threading.Event().wait(0.5)
def close_rig(self): def close_rig(self):
""" """ """ """
self.sock.close() self.ptt_sock.close()
self.connected = False self.data_sock.close()
self.ptt_connected = False
self.data_connected = False
def send_command(self, command) -> bytes: def send_ptt_command(self, command, expect_answer) -> bytes:
"""Send a command to the connected rotctld instance, """Send a command to the connected rotctld instance,
and return the return value. and return the return value.
@ -108,9 +148,9 @@ class radio:
command: command:
""" """
if self.connected: if self.ptt_connected:
try: try:
self.connection.sendall(command + b"\n") self.ptt_connection.sendall(command + b"\n")
except Exception: except Exception:
self.log.warning( self.log.warning(
"[RIGCTLD] Command not executed!", "[RIGCTLD] Command not executed!",
@ -118,10 +158,33 @@ class radio:
ip=self.hostname, ip=self.hostname,
port=self.port, port=self.port,
) )
self.connected = False self.ptt_connected = False
return b""
def send_data_command(self, command, expect_answer) -> bytes:
"""Send a command to the connected rotctld instance,
and return the return value.
Args:
command:
"""
if self.data_connected:
try:
self.data_connection.sendall(command + b"\n")
except Exception:
self.log.warning(
"[RIGCTLD] Command not executed!",
command=command,
ip=self.hostname,
port=self.port,
)
self.data_connected = False
try: try:
return self.connection.recv(1024) # recv seems to be blocking so in case of ptt we don't need the response
# maybe this speeds things up and avoids blocking states
return self.data_connection.recv(64) if expect_answer else True
except Exception: except Exception:
self.log.warning( self.log.warning(
"[RIGCTLD] No command response!", "[RIGCTLD] No command response!",
@ -129,27 +192,24 @@ class radio:
ip=self.hostname, ip=self.hostname,
port=self.port, port=self.port,
) )
self.connected = False self.data_connected = False
else:
# reconnecting....
time.sleep(0.5)
self.connect()
return b"" return b""
def get_status(self): def get_status(self):
""" """ """ """
return "connected" if self.connected else "unknown/disconnected" return "connected" if self.data_connected and self.ptt_connected else "unknown/disconnected"
def get_mode(self): def get_mode(self):
""" """ """ """
try: try:
data = self.send_command(b"m") data = self.send_data_command(b"m", True)
data = data.split(b"\n") data = data.split(b"\n")
data = data[0].decode("utf-8") data = data[0].decode("utf-8")
if 'RPRT' not in data: if 'RPRT' not in data:
self.mode = data try:
data = int(data)
except ValueError:
self.mode = str(data)
return self.mode return self.mode
except Exception: except Exception:
@ -158,11 +218,12 @@ class radio:
def get_bandwidth(self): def get_bandwidth(self):
""" """ """ """
try: try:
data = self.send_command(b"m") data = self.send_data_command(b"m", True)
data = data.split(b"\n") data = data.split(b"\n")
data = data[1].decode("utf-8") data = data[1].decode("utf-8")
if 'RPRT' not in data: if 'RPRT' not in data and data not in ['']:
with contextlib.suppress(ValueError):
self.bandwidth = int(data) self.bandwidth = int(data)
return self.bandwidth return self.bandwidth
except Exception: except Exception:
@ -171,11 +232,14 @@ class radio:
def get_frequency(self): def get_frequency(self):
""" """ """ """
try: try:
data = self.send_command(b"f") data = self.send_data_command(b"f", True)
data = data.decode("utf-8") data = data.decode("utf-8")
if 'RPRT' not in data: if 'RPRT' not in data and data not in [0, '0', '']:
with contextlib.suppress(ValueError):
data = int(data)
# make sure we have a frequency and not bandwidth
if data >= 10000:
self.frequency = data self.frequency = data
return self.frequency return self.frequency
except Exception: except Exception:
return self.frequency return self.frequency
@ -183,7 +247,7 @@ class radio:
def get_ptt(self): def get_ptt(self):
""" """ """ """
try: try:
return self.send_command(b"t") return self.send_command(b"t", True)
except Exception: except Exception:
return False return False
@ -198,9 +262,39 @@ class radio:
""" """
try: try:
if state: if state:
self.send_command(b"T 1") self.send_ptt_command(b"T 1", False)
else: else:
self.send_command(b"T 0") self.send_ptt_command(b"T 0", False)
return state return state
except Exception: except Exception:
return False return False
def set_frequency(self, frequency):
"""
Args:
frequency:
Returns:
"""
try:
command = bytes(f"F {frequency}", "utf-8")
self.send_data_command(command, False)
except Exception:
return False
def set_mode(self, mode):
"""
Args:
mode:
Returns:
"""
try:
command = bytes(f"M {mode} {self.bandwidth}", "utf-8")
self.send_data_command(command, False)
except Exception:
return False

View file

@ -30,6 +30,9 @@ class radio:
""" """ """ """
return None return None
def set_bandwidth(self):
""" """
return None
def set_mode(self, mode): def set_mode(self, mode):
""" """
@ -41,6 +44,16 @@ class radio:
""" """
return None return None
def set_frequency(self, frequency):
"""
Args:
mode:
Returns:
"""
return None
def get_status(self): def get_status(self):
""" """

View file

@ -24,13 +24,14 @@ import socketserver
import sys import sys
import threading import threading
import time import time
import wave
import helpers import helpers
import static import static
import structlog import structlog
import ujson as json import ujson as json
from exceptions import NoCallsign from exceptions import NoCallsign
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE
SOCKET_QUEUE = queue.Queue() SOCKET_QUEUE = queue.Queue()
DAEMON_QUEUE = queue.Queue() DAEMON_QUEUE = queue.Queue()
@ -76,7 +77,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
if data != tempdata: if data != tempdata:
tempdata = data tempdata = data
SOCKET_QUEUE.put(data) SOCKET_QUEUE.put(data)
time.sleep(0.5) threading.Event().wait(0.5)
while not SOCKET_QUEUE.empty(): while not SOCKET_QUEUE.empty():
data = SOCKET_QUEUE.get() data = SOCKET_QUEUE.get()
@ -84,20 +85,23 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
sock_data += b"\n" # append line limiter sock_data += b"\n" # append line limiter
# send data to all clients # send data to all clients
# try: try:
for client in CONNECTED_CLIENTS: for client in CONNECTED_CLIENTS:
try: try:
client.send(sock_data) client.send(sock_data)
except Exception as err: except Exception as err:
self.log.info("[SCK] Connection lost", e=err) self.log.info("[SCK] Connection lost", e=err)
# TODO: Check if we really should set connection alive to false. This might disconnect all other clients as well...
self.connection_alive = False self.connection_alive = False
except Exception as err:
self.log.debug("[SCK] catch harmless RuntimeError: Set changed size during iteration", e=err)
# we want to transmit scatter data only once to reduce network traffic # we want to transmit scatter data only once to reduce network traffic
static.SCATTER = [] static.SCATTER = []
# we want to display INFO messages only once # we want to display INFO messages only once
static.INFO = [] static.INFO = []
# self.request.sendall(sock_data) # self.request.sendall(sock_data)
time.sleep(0.15) threading.Event().wait(0.15)
def receive_from_client(self): def receive_from_client(self):
""" """
@ -132,7 +136,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
# we might improve this by only processing one command or # we might improve this by only processing one command or
# doing some kind of selection to determin which commands need to be dropped # doing some kind of selection to determin which commands need to be dropped
# and which one can be processed during a running transmission # and which one can be processed during a running transmission
time.sleep(3) threading.Event().wait(0.5)
# finally delete our rx buffer to be ready for new commands # finally delete our rx buffer to be ready for new commands
data = bytes() data = bytes()
@ -169,7 +173,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
# keep connection alive until we close it # keep connection alive until we close it
while self.connection_alive and not CLOSE_SIGNAL: while self.connection_alive and not CLOSE_SIGNAL:
time.sleep(1) threading.Event().wait(1)
def finish(self): def finish(self):
""" """ """ """
@ -224,6 +228,40 @@ def process_tnc_commands(data):
"[SCK] CQ command execution error", e=err, command=received_json "[SCK] CQ command execution error", e=err, command=received_json
) )
# START STOP AUDIO RECORDING -----------------------------------------------------
if received_json["type"] == "set" and received_json["command"] == "record_audio":
try:
if not static.AUDIO_RECORD:
static.AUDIO_RECORD_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w')
static.AUDIO_RECORD_FILE.setnchannels(1)
static.AUDIO_RECORD_FILE.setsampwidth(2)
static.AUDIO_RECORD_FILE.setframerate(8000)
static.AUDIO_RECORD = True
else:
static.AUDIO_RECORD = False
static.AUDIO_RECORD_FILE.close()
command_response("respond_to_call", True)
except Exception as err:
command_response("respond_to_call", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
# SET ENABLE/DISABLE RESPOND TO CALL -----------------------------------------------------
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
try:
static.RESPOND_TO_CALL = received_json["state"] in ['true', 'True', True]
command_response("respond_to_call", True)
except Exception as err:
command_response("respond_to_call", False)
log.warning(
"[SCK] CQ command execution error", e=err, command=received_json
)
# SET ENABLE RESPOND TO CQ ----------------------------------------------------- # SET ENABLE RESPOND TO CQ -----------------------------------------------------
if received_json["type"] == "set" and received_json["command"] == "respond_to_cq": if received_json["type"] == "set" and received_json["command"] == "respond_to_cq":
try: try:
@ -319,13 +357,22 @@ def process_tnc_commands(data):
if not str(dxcallsign).strip(): if not str(dxcallsign).strip():
raise NoCallsign raise NoCallsign
# additional step for beeing sure our callsign is correctly # additional step for being sure our callsign is correctly
# in case we are not getting a station ssid # in case we are not getting a station ssid
# then we are forcing a station ssid = 0 # then we are forcing a station ssid = 0
dxcallsign = helpers.callsign_to_bytes(dxcallsign) dxcallsign = helpers.callsign_to_bytes(dxcallsign)
dxcallsign = helpers.bytes_to_callsign(dxcallsign) dxcallsign = helpers.bytes_to_callsign(dxcallsign)
DATA_QUEUE_TRANSMIT.put(["PING", dxcallsign]) # check if specific callsign is set with different SSID than the TNC is initialized
try:
mycallsign = received_json["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception:
mycallsign = static.MYCALLSIGN
DATA_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign])
command_response("ping", True) command_response("ping", True)
except NoCallsign: except NoCallsign:
command_response("ping", False) command_response("ping", False)
@ -351,6 +398,15 @@ def process_tnc_commands(data):
dxcallsign = received_json["dxcallsign"] dxcallsign = received_json["dxcallsign"]
# check if specific callsign is set with different SSID than the TNC is initialized
try:
mycallsign = received_json["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception:
mycallsign = static.MYCALLSIGN
# additional step for being sure our callsign is correctly # additional step for being sure our callsign is correctly
# in case we are not getting a station ssid # in case we are not getting a station ssid
# then we are forcing a station ssid = 0 # then we are forcing a station ssid = 0
@ -370,10 +426,8 @@ def process_tnc_commands(data):
# try connecting # try connecting
try: try:
static.DXCALLSIGN = dxcallsign
static.DXCALLSIGN_CRC = helpers.get_crc_24(static.DXCALLSIGN)
DATA_QUEUE_TRANSMIT.put(["CONNECT", dxcallsign, attempts]) DATA_QUEUE_TRANSMIT.put(["CONNECT", mycallsign, dxcallsign, attempts])
command_response("connect", True) command_response("connect", True)
except Exception as err: except Exception as err:
command_response("connect", False) command_response("connect", False)
@ -385,7 +439,6 @@ def process_tnc_commands(data):
# allow beacon transmission again # allow beacon transmission again
static.BEACON_PAUSE = False static.BEACON_PAUSE = False
# allow beacon transmission again # allow beacon transmission again
static.BEACON_PAUSE = False static.BEACON_PAUSE = False
@ -439,6 +492,9 @@ def process_tnc_commands(data):
# check if specific callsign is set with different SSID than the TNC is initialized # check if specific callsign is set with different SSID than the TNC is initialized
try: try:
mycallsign = received_json["parameter"][0]["mycallsign"] mycallsign = received_json["parameter"][0]["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception: except Exception:
mycallsign = static.MYCALLSIGN mycallsign = static.MYCALLSIGN
@ -462,7 +518,7 @@ def process_tnc_commands(data):
binarydata = base64.b64decode(base64data) binarydata = base64.b64decode(base64data)
DATA_QUEUE_TRANSMIT.put( DATA_QUEUE_TRANSMIT.put(
["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, attempts] ["ARQ_RAW", binarydata, mode, n_frames, arq_uuid, mycallsign, dxcallsign, attempts]
) )
except Exception as err: except Exception as err:
@ -538,6 +594,32 @@ def process_tnc_commands(data):
command=received_json, command=received_json,
) )
# SET FREQUENCY -----------------------------------------------------
if received_json["command"] == "frequency" and received_json["type"] == "set":
try:
RIGCTLD_COMMAND_QUEUE.put(["set_frequency", received_json["frequency"]])
command_response("set_frequency", True)
except Exception as err:
command_response("set_frequency", False)
log.warning(
"[SCK] Set frequency command execution error",
e=err,
command=received_json,
)
# SET MODE -----------------------------------------------------
if received_json["command"] == "mode" and received_json["type"] == "set":
try:
RIGCTLD_COMMAND_QUEUE.put(["set_mode", received_json["mode"]])
command_response("set_mode", True)
except Exception as err:
command_response("set_mode", False)
log.warning(
"[SCK] Set mode command execution error",
e=err,
command=received_json,
)
# exception, if JSON cant be decoded # exception, if JSON cant be decoded
except Exception as err: except Exception as err:
log.error("[SCK] JSON decoding error", e=err) log.error("[SCK] JSON decoding error", e=err)
@ -569,16 +651,20 @@ def send_tnc_state():
"rx_msg_buffer_length": str(len(static.RX_MSG_BUFFER)), "rx_msg_buffer_length": str(len(static.RX_MSG_BUFFER)),
"arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE), "arq_bytes_per_minute": str(static.ARQ_BYTES_PER_MINUTE),
"arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST), "arq_bytes_per_minute_burst": str(static.ARQ_BYTES_PER_MINUTE_BURST),
"arq_seconds_until_finish": str(static.ARQ_SECONDS_UNTIL_FINISH),
"arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR), "arq_compression_factor": str(static.ARQ_COMPRESSION_FACTOR),
"arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT), "arq_transmission_percent": str(static.ARQ_TRANSMISSION_PERCENT),
"speed_list": static.SPEED_LIST,
"total_bytes": str(static.TOTAL_BYTES), "total_bytes": str(static.TOTAL_BYTES),
"beacon_state": str(static.BEACON_STATE), "beacon_state": str(static.BEACON_STATE),
"stations": [], "stations": [],
"mycallsign": str(static.MYCALLSIGN, encoding), "mycallsign": str(static.MYCALLSIGN, encoding),
"mygrid": str(static.MYGRID, encoding),
"dxcallsign": str(static.DXCALLSIGN, encoding), "dxcallsign": str(static.DXCALLSIGN, encoding),
"dxgrid": str(static.DXGRID, encoding), "dxgrid": str(static.DXGRID, encoding),
"hamlib_status": static.HAMLIB_STATUS, "hamlib_status": static.HAMLIB_STATUS,
"listen": str(static.LISTEN), "listen": str(static.LISTEN),
"audio_recording": str(static.AUDIO_RECORD),
} }
# add heard stations to heard stations object # add heard stations to heard stations object
@ -594,7 +680,6 @@ def send_tnc_state():
"frequency": heard[6], "frequency": heard[6],
} }
) )
return json.dumps(output) return json.dumps(output)
@ -690,6 +775,16 @@ def process_daemon_commands(data):
rx_buffer_size = str(received_json["parameter"][0]["rx_buffer_size"]) rx_buffer_size = str(received_json["parameter"][0]["rx_buffer_size"])
enable_explorer = str(received_json["parameter"][0]["enable_explorer"]) enable_explorer = str(received_json["parameter"][0]["enable_explorer"])
try:
# convert ssid list to python list
ssid_list = str(received_json["parameter"][0]["ssid_list"])
ssid_list = ssid_list.replace(" ", "")
ssid_list = ssid_list.split(",")
# convert str to int
ssid_list = list(map(int, ssid_list))
except KeyError:
ssid_list = [0]
# print some debugging parameters # print some debugging parameters
for item in received_json["parameter"][0]: for item in received_json["parameter"][0]:
log.debug( log.debug(
@ -725,6 +820,7 @@ def process_daemon_commands(data):
respond_to_cq, respond_to_cq,
rx_buffer_size, rx_buffer_size,
enable_explorer, enable_explorer,
ssid_list,
] ]
) )
command_response("start_tnc", True) command_response("start_tnc", True)
@ -820,3 +916,4 @@ def command_response(command, status):
jsondata = {"command_response": command, "status": s_status} jsondata = {"command_response": command, "status": s_status}
data_out = json.dumps(jsondata) data_out = json.dumps(jsondata)
SOCKET_QUEUE.put(data_out) SOCKET_QUEUE.put(data_out)

View file

@ -11,7 +11,7 @@ Not nice, suggestions are appreciated :-)
import subprocess import subprocess
from enum import Enum from enum import Enum
VERSION = "0.6.7-alpha.1" VERSION = "0.6.12-alpha.6"
ENABLE_EXPLORER = False ENABLE_EXPLORER = False
@ -25,7 +25,7 @@ TNCPROCESS: subprocess.Popen
MYCALLSIGN: bytes = b"AA0AA" MYCALLSIGN: bytes = b"AA0AA"
MYCALLSIGN_CRC: bytes = b"A" MYCALLSIGN_CRC: bytes = b"A"
DXCALLSIGN: bytes = b"AA0AA" DXCALLSIGN: bytes = b"ZZ9YY"
DXCALLSIGN_CRC: bytes = b"A" DXCALLSIGN_CRC: bytes = b"A"
MYGRID: bytes = b"" MYGRID: bytes = b""
@ -73,6 +73,7 @@ SCATTER: list = []
ENABLE_SCATTER: bool = False ENABLE_SCATTER: bool = False
ENABLE_FSK: bool = False ENABLE_FSK: bool = False
RESPOND_TO_CQ: bool = False RESPOND_TO_CQ: bool = False
RESPOND_TO_CALL: bool = True # respond to cq, ping, connection request, file request if not in session
# --------------------------------- # ---------------------------------
# Audio Defaults # Audio Defaults
@ -81,25 +82,31 @@ AUDIO_INPUT_DEVICES: list = []
AUDIO_OUTPUT_DEVICES: list = [] AUDIO_OUTPUT_DEVICES: list = []
AUDIO_INPUT_DEVICE: int = -2 AUDIO_INPUT_DEVICE: int = -2
AUDIO_OUTPUT_DEVICE: int = -2 AUDIO_OUTPUT_DEVICE: int = -2
AUDIO_RECORD: bool = False
AUDIO_RECORD_FILE = ''
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0] BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
AUDIO_DBFS: int = 0 AUDIO_DBFS: int = 0
FFT: list = [0] FFT: list = [0]
ENABLE_FFT: bool = False ENABLE_FFT: bool = True
CHANNEL_BUSY: bool = False CHANNEL_BUSY: bool = False
# ARQ PROTOCOL VERSION # ARQ PROTOCOL VERSION
ARQ_PROTOCOL_VERSION: int = 3 ARQ_PROTOCOL_VERSION: int = 5
# ARQ statistics # ARQ statistics
SPEED_LIST: list = []
ARQ_BYTES_PER_MINUTE_BURST: int = 0 ARQ_BYTES_PER_MINUTE_BURST: int = 0
ARQ_BYTES_PER_MINUTE: int = 0 ARQ_BYTES_PER_MINUTE: int = 0
ARQ_BITS_PER_SECOND_BURST: int = 0 ARQ_BITS_PER_SECOND_BURST: int = 0
ARQ_BITS_PER_SECOND: int = 0 ARQ_BITS_PER_SECOND: int = 0
ARQ_COMPRESSION_FACTOR: int = 0 ARQ_COMPRESSION_FACTOR: int = 0
ARQ_TRANSMISSION_PERCENT: int = 0 ARQ_TRANSMISSION_PERCENT: int = 0
ARQ_SECONDS_UNTIL_FINISH: int = 0
ARQ_SPEED_LEVEL: int = 0 ARQ_SPEED_LEVEL: int = 0
TOTAL_BYTES: int = 0 TOTAL_BYTES: int = 0
# set save to folder state for allowing downloading files to local file system
ARQ_SAVE_TO_FOLDER: bool = False
# CHANNEL_STATE = 'RECEIVING_SIGNALLING' # CHANNEL_STATE = 'RECEIVING_SIGNALLING'
TNC_STATE: str = "IDLE" TNC_STATE: str = "IDLE"

104
tools/freedata_cli_tools.py Executable file
View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: DJ2LS
"""
import argparse
import socket
import base64
import json
from pick import pick
import time
import sounddevice as sd
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC')
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
args = parser.parse_args()
HOST, PORT = args.socket_host, args.socket_port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server
sock.connect((HOST, PORT))
def main_menu():
while True:
time.sleep(0.1)
title = 'Please select a command you want to run: '
options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES']
option, index = pick(options, title)
# BEACON AREA
if option == 'BEACON':
option, index = pick(['5',
'10',
'15',
'30',
'45',
'60',
'90',
'120',
'300',
'600',
'900',
'1800',
'3600',
'STOP BEACON',
'----- BACK -----'], "Select beacon interval [seconds]")
if option == '----- BACK -----':
main_menu()
elif option == 'STOP BEACON':
run_network_command({"type": "broadcast", "command": "stop_beacon"})
else:
run_network_command({"type": "broadcast", "command": "start_beacon", "parameter": str(option)})
elif option == 'PING':
pass
elif option == 'ARQ':
option, index = pick(['GET RX BUFFER', 'DISCONNECT', '----- BACK -----'], "Select ARQ command")
if option == '----- BACK -----':
main_menu()
elif option == 'GET RX BUFFER':
run_network_command({"type": "get", "command": "rx_buffer"})
else:
run_network_command({"type": "arq", "command": "disconnect"})
elif option == 'LIST AUDIO DEVICES':
devices = sd.query_devices(device=None, kind=None)
device_list = []
for device in devices:
device_list.append(
f"{device['index']} - "
f"{sd.query_hostapis(device['hostapi'])['name']} - "
f"Channels (In/Out):{device['max_input_channels']}/{device['max_output_channels']} - "
f"{device['name']}")
device_list.append('----- BACK -----')
option, index = pick(device_list, "Audio devices")
if option == '----- BACK -----':
main_menu()
else:
print("no menu point found...")
def run_network_command(command):
command = json.dumps(command)
command = bytes(command + "\n", 'utf-8')
sock.sendall(command)
if __name__ == "__main__":
main_menu()

View file

@ -0,0 +1,114 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
daemon.py
Author: DJ2LS, January 2022
daemon for providing basic information for the tnc like audio or serial devices
"""
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
# pylint: disable=import-outside-toplevel
import argparse
import socket
import structlog
import queue
import json
import base64
import os
log = structlog.get_logger("CLIENT")
split_char = b"\x00;"
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC')
parser.add_argument('--port', dest="socket_port", default=3000, help="Set the port, the socket is listening on.", type=int)
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
args = parser.parse_args()
ip, port = args.socket_host, args.socket_port
connected = True
data = bytes()
"""
Nachricht
{'command': 'rx_buffer', 'data-array': [{'uuid': '8dde227d-3a09-4f39-b34c-5f8281d719d1', 'timestamp': 1672043316, 'dxcallsign': 'DJ2LS-1', 'dxgrid': 'JN48cs', 'data': 'bQA7c2VuZF9tZXNzYWdlADsxMjMAO2VkY2NjZDAyLTUzMTQtNDc3Ni1hMjlkLTFmY2M1ZDI4OTM4ZAA7VGVzdAoAOwA7cGxhaW4vdGV4dAA7ADsxNjcyMDQzMzA5'}]}
"""
def decode_and_save_data(encoded_data):
decoded_data = base64.b64decode(encoded_data)
decoded_data = decoded_data.split(split_char)
if decoded_data[0] == b'm':
print(jsondata)
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", uuid=decoded_data[3])
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", message=decoded_data[4])
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", filename=decoded_data[5])
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", filetype=decoded_data[6])
log.info(f"{jsondata.get('mycallsign')} <<< {jsondata.get('dxcallsign')}", data=decoded_data[7])
try:
folderpath = "received"
if not os.path.exists(folderpath):
os.makedirs(folderpath)
filename = decoded_data[8].decode("utf-8") + "_" + decoded_data[5].decode("utf-8")
with open(f"{folderpath}/{filename}", "wb") as file:
file.write(decoded_data[7])
with open(f"{folderpath}/{decoded_data[8].decode('utf-8')}_msg.txt", "wb") as file:
file.write(decoded_data[4])
except Exception as e:
print(e)
else:
print(decoded_data)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
print(sock)
while connected:
chunk = sock.recv(1024)
data += chunk
if data.startswith(b"{") and data.endswith(b"}\n"):
# split data by \n if we have multiple commands in socket buffer
data = data.split(b"\n")
# remove empty data
data.remove(b"")
# iterate through data list
for command in data:
jsondata = json.loads(command)
if jsondata.get('command') == "tnc_state":
pass
if jsondata.get('freedata') == "tnc-message":
log.info(jsondata)
if jsondata.get('ping') == "acknowledge":
log.info(f"PING {jsondata.get('mycallsign')} >><< {jsondata.get('dxcallsign')}", snr=jsondata.get('snr'), dxsnr=jsondata.get('dxsnr'))
if jsondata.get('status') == 'receiving':
log.info(jsondata)
if jsondata.get('command') == 'rx_buffer':
for rxdata in jsondata["data-array"]:
log.info(f"rx buffer {rxdata.get('uuid')}")
decode_and_save_data(rxdata.get('data'))
if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission':
decode_and_save_data(jsondata["data"])
# clear data buffer as soon as data has been read
data = bytes()

117
tools/send_file.py Executable file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: DJ2LS
python3 send_file.py --file cleanup.sh --dxcallsign DN2LS-0 --mycallsign DN2LS-2 --attempts 3
"""
import argparse
import socket
import base64
import json
import uuid
import time
import crcengine
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC')
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
parser.add_argument('--file', dest="filename", default='', help="Select the file we want to send", type=str)
parser.add_argument('--msg', dest="chatmessage", default='file from cli tool', help="Additional text message appended to file", type=str)
parser.add_argument('--dxcallsign', dest="dxcallsign", default='AA0AA', help="Select the destination callsign", type=str)
parser.add_argument('--mycallsign', dest="mycallsign", default='AA0AA', help="Select the own callsign", type=str)
parser.add_argument('--attempts', dest="attempts", default='5', help="Amount of connection attempts", type=int)
args = parser.parse_args()
HOST, PORT = args.socket_host, args.socket_port
filename = args.filename
dxcallsign = args.dxcallsign
mycallsign = args.mycallsign
attempts = args.attempts
chatmessage = bytes(args.chatmessage, "utf-8")
if filename != "":
# open file by name
f = open(filename, "rb")
file = f.read()
filename = bytes(filename, "utf-8")
else:
file = b""
filename = b""
# convert binary data to base64
#base64_data = base64.b64encode(file).decode("UTF-8")
split_char = b'\0;\1;'
filetype = b"unknown"
timestamp = str(int(time.time()))
# timestamp = timestamp.to_bytes(4, byteorder="big")
timestamp = bytes(timestamp, "utf-8")
msg_with_attachment = timestamp + \
split_char + \
chatmessage + \
split_char + \
filename + \
split_char + \
filetype + \
split_char + \
file
# calculate checksum
crc_algorithm = crcengine.new("crc32") # load crc32 library
crc_data = crc_algorithm(file)
crc_data = crc_data.to_bytes(4, byteorder="big")
datatype = b"m"
command = b"send_message"
checksum = bytes(crc_data.hex(), "utf-8")
uuid_4 = bytes(str(uuid.uuid4()), "utf-8")
data = datatype + \
split_char + \
command + \
split_char + \
checksum + \
split_char + \
uuid_4 + \
split_char + \
msg_with_attachment
data = base64.b64encode(data).decode("UTF-8")
# message
# our command we are going to send
command = {"type": "arq",
"command": "send_raw",
"parameter":
[{"dxcallsign": dxcallsign,
"mycallsign": mycallsign,
"attempts": str(attempts),
"mode": "255",
"n_frames": "1",
"data": data}
]
}
command = json.dumps(command)
print(command)
command = bytes(command + "\n", 'utf-8')
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(command)
timeout = time.time() + 5
while time.time() < timeout:
pass

47
tools/send_ping_cq.py Normal file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: DJ2LS
python3 send_file.py --file cleanup.sh --dxcallsign DN2LS-0 --mycallsign DN2LS-2 --attempts 3
"""
import argparse
import socket
import json
# --------------------------------------------GET PARAMETER INPUTS
parser = argparse.ArgumentParser(description='Simons TEST TNC')
parser.add_argument('--port', dest="socket_port", default=3000, help="Set socket listening port.", type=int)
parser.add_argument('--host', dest="socket_host", default='localhost', help="Set the host, the socket is listening on.", type=str)
parser.add_argument('--dxcallsign', dest="dxcallsign", default='AA0AA', help="Select the destination callsign", type=str)
parser.add_argument('--mycallsign', dest="mycallsign", default='AA0AA', help="Select the own callsign", type=str)
parser.add_argument('--ping', dest="ping", action="store_true", help="Send PING")
parser.add_argument('--cq', dest="cq", action="store_true", help="Send CQ")
args = parser.parse_args()
HOST, PORT = args.socket_host, args.socket_port
dxcallsign = args.dxcallsign
mycallsign = args.mycallsign
# our command we are going to send
if args.ping:
command = {"type": "ping",
"command": "ping",
"dxcallsign": dxcallsign,
"mycallsign": mycallsign,
}
if args.cq:
command = {"type": "broadcast",
"command": "cqcqcq",
"mycallsign": mycallsign,
}
command = json.dumps(command)
command = bytes(command + "\n", 'utf-8')
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(command)